initial public commit
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cast"
|
||||
)
|
||||
|
||||
// DefaultDateLayout specifies the default app date strings layout.
|
||||
const DefaultDateLayout = "2006-01-02 15:04:05.000"
|
||||
|
||||
// NowDateTime returns new DateTime instance with the current local time.
|
||||
func NowDateTime() DateTime {
|
||||
return DateTime{t: time.Now()}
|
||||
}
|
||||
|
||||
// ParseDateTime creates a new DateTime from the provided value
|
||||
// (could be [cast.ToTime] supported string, [time.Time], etc.).
|
||||
func ParseDateTime(value any) (DateTime, error) {
|
||||
d := DateTime{}
|
||||
err := d.Scan(value)
|
||||
return d, err
|
||||
}
|
||||
|
||||
// DateTime represents a [time.Time] instance in UTC that is wrapped
|
||||
// and serialized using the app default date layout.
|
||||
type DateTime struct {
|
||||
t time.Time
|
||||
}
|
||||
|
||||
// Time returns the internal [time.Time] instance.
|
||||
func (d DateTime) Time() time.Time {
|
||||
return d.t
|
||||
}
|
||||
|
||||
// IsZero checks whether the current DateTime instance has zero time value.
|
||||
func (d DateTime) IsZero() bool {
|
||||
return d.Time().IsZero()
|
||||
}
|
||||
|
||||
// String serializes the current DateTime instance into a formated
|
||||
// UTC date string.
|
||||
//
|
||||
// The zero value is serialized to an empty string.
|
||||
func (d DateTime) String() string {
|
||||
if d.IsZero() {
|
||||
return ""
|
||||
}
|
||||
return d.Time().UTC().Format(DefaultDateLayout)
|
||||
}
|
||||
|
||||
// MarshalJSON implements the [json.Marshaler] interface.
|
||||
func (d DateTime) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(d.String())
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the [json.Unmarshaler] interface.
|
||||
func (d *DateTime) UnmarshalJSON(b []byte) error {
|
||||
var raw string
|
||||
if err := json.Unmarshal(b, &raw); err != nil {
|
||||
return err
|
||||
}
|
||||
return d.Scan(raw)
|
||||
}
|
||||
|
||||
// Value implements the [driver.Valuer] interface.
|
||||
func (d DateTime) Value() (driver.Value, error) {
|
||||
return d.String(), nil
|
||||
}
|
||||
|
||||
// Scan implements [sql.Scanner] interface to scan the provided value
|
||||
// into the current DateTime instance.
|
||||
func (d *DateTime) Scan(value any) error {
|
||||
switch v := value.(type) {
|
||||
case DateTime:
|
||||
d.t = v.Time()
|
||||
case time.Time:
|
||||
d.t = v
|
||||
case int:
|
||||
d.t = cast.ToTime(v)
|
||||
default:
|
||||
str := cast.ToString(v)
|
||||
if str == "" {
|
||||
d.t = time.Time{}
|
||||
} else {
|
||||
d.t = cast.ToTime(str)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
package types_test
|
||||
|
||||
import (
|
||||
"github.com/pocketbase/pocketbase/tools/types"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestNowDateTime(t *testing.T) {
|
||||
now := time.Now().UTC().Format("2006-01-02 15:04:05") // without ms part for test consistency
|
||||
dt := types.NowDateTime()
|
||||
|
||||
if !strings.Contains(dt.String(), now) {
|
||||
t.Fatalf("Expected %q, got %q", now, dt.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseDateTime(t *testing.T) {
|
||||
nowTime := time.Now().UTC()
|
||||
nowDateTime, _ := types.ParseDateTime(nowTime)
|
||||
nowStr := nowTime.Format(types.DefaultDateLayout)
|
||||
|
||||
scenarios := []struct {
|
||||
value any
|
||||
expected string
|
||||
}{
|
||||
{nil, ""},
|
||||
{"", ""},
|
||||
{"invalid", ""},
|
||||
{nowDateTime, nowStr},
|
||||
{nowTime, nowStr},
|
||||
{1641024040, "2022-01-01 08:00:40.000"},
|
||||
{"2022-01-01 11:23:45.678", "2022-01-01 11:23:45.678"},
|
||||
}
|
||||
|
||||
for i, s := range scenarios {
|
||||
dt, err := types.ParseDateTime(s.value)
|
||||
if err != nil {
|
||||
t.Errorf("(%d) Failed to parse %v: %v", i, s.value, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if dt.String() != s.expected {
|
||||
t.Errorf("(%d) Expected %q, got %q", i, s.expected, dt.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDateTimeTime(t *testing.T) {
|
||||
str := "2022-01-01 11:23:45.678"
|
||||
|
||||
expected, err := time.Parse(types.DefaultDateLayout, str)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
dt, err := types.ParseDateTime(str)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
result := dt.Time()
|
||||
|
||||
if !expected.Equal(result) {
|
||||
t.Errorf("Expected time %v, got %v", expected, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDateTimeIsZero(t *testing.T) {
|
||||
dt0 := types.DateTime{}
|
||||
if !dt0.IsZero() {
|
||||
t.Fatalf("Expected zero datatime, got %v", dt0)
|
||||
}
|
||||
|
||||
dt1 := types.NowDateTime()
|
||||
if dt1.IsZero() {
|
||||
t.Fatalf("Expected non-zero datatime, got %v", dt1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDateTimeString(t *testing.T) {
|
||||
dt0 := types.DateTime{}
|
||||
if dt0.String() != "" {
|
||||
t.Fatalf("Expected empty string for zer datetime, got %q", dt0.String())
|
||||
}
|
||||
|
||||
expected := "2022-01-01 11:23:45.678"
|
||||
dt1, _ := types.ParseDateTime(expected)
|
||||
if dt1.String() != expected {
|
||||
t.Fatalf("Expected %q, got %v", expected, dt1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDateTimeMarshalJSON(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
date string
|
||||
expected string
|
||||
}{
|
||||
{"", `""`},
|
||||
{"2022-01-01 11:23:45.678", `"2022-01-01 11:23:45.678"`},
|
||||
}
|
||||
|
||||
for i, s := range scenarios {
|
||||
dt, err := types.ParseDateTime(s.date)
|
||||
if err != nil {
|
||||
t.Errorf("(%d) %v", i, err)
|
||||
}
|
||||
|
||||
result, err := dt.MarshalJSON()
|
||||
if err != nil {
|
||||
t.Errorf("(%d) %v", i, err)
|
||||
}
|
||||
|
||||
if string(result) != s.expected {
|
||||
t.Errorf("(%d) Expected %q, got %q", i, s.expected, string(result))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDateTimeUnmarshalJSON(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
date string
|
||||
expected string
|
||||
}{
|
||||
{"", ""},
|
||||
{"invalid_json", ""},
|
||||
{"'123'", ""},
|
||||
{"2022-01-01 11:23:45.678", ""},
|
||||
{`"2022-01-01 11:23:45.678"`, "2022-01-01 11:23:45.678"},
|
||||
}
|
||||
|
||||
for i, s := range scenarios {
|
||||
dt := types.DateTime{}
|
||||
dt.UnmarshalJSON([]byte(s.date))
|
||||
|
||||
if dt.String() != s.expected {
|
||||
t.Errorf("(%d) Expected %q, got %q", i, s.expected, dt.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDateTimeValue(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
value any
|
||||
expected string
|
||||
}{
|
||||
{"", ""},
|
||||
{"invalid", ""},
|
||||
{1641024040, "2022-01-01 08:00:40.000"},
|
||||
{"2022-01-01 11:23:45.678", "2022-01-01 11:23:45.678"},
|
||||
{types.NowDateTime(), types.NowDateTime().String()},
|
||||
}
|
||||
|
||||
for i, s := range scenarios {
|
||||
dt, _ := types.ParseDateTime(s.value)
|
||||
result, err := dt.Value()
|
||||
if err != nil {
|
||||
t.Errorf("(%d) %v", i, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if result != s.expected {
|
||||
t.Errorf("(%d) Expected %q, got %q", i, s.expected, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDateTimeScan(t *testing.T) {
|
||||
now := time.Now().UTC().Format("2006-01-02 15:04:05") // without ms part for test consistency
|
||||
|
||||
scenarios := []struct {
|
||||
value any
|
||||
expected string
|
||||
}{
|
||||
{nil, ""},
|
||||
{"", ""},
|
||||
{"invalid", ""},
|
||||
{types.NowDateTime(), now},
|
||||
{time.Now(), now},
|
||||
{1641024040, "2022-01-01 08:00:40.000"},
|
||||
{"2022-01-01 11:23:45.678", "2022-01-01 11:23:45.678"},
|
||||
}
|
||||
|
||||
for i, s := range scenarios {
|
||||
dt := types.DateTime{}
|
||||
|
||||
err := dt.Scan(s.value)
|
||||
if err != nil {
|
||||
t.Errorf("(%d) Failed to parse %v: %v", i, s.value, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if !strings.Contains(dt.String(), s.expected) {
|
||||
t.Errorf("(%d) Expected %q, got %q", i, s.expected, dt.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// JsonArray defines a slice that is safe for json and db read/write.
|
||||
type JsonArray []any
|
||||
|
||||
// MarshalJSON implements the [json.Marshaler] interface.
|
||||
func (m JsonArray) MarshalJSON() ([]byte, error) {
|
||||
type alias JsonArray // prevent recursion
|
||||
|
||||
// inialize an empty map to ensure that `[]` is returned as json
|
||||
if m == nil {
|
||||
m = JsonArray{}
|
||||
}
|
||||
|
||||
return json.Marshal(alias(m))
|
||||
}
|
||||
|
||||
// Value implements the [driver.Valuer] interface.
|
||||
func (m JsonArray) Value() (driver.Value, error) {
|
||||
if m == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
data, err := json.Marshal(m)
|
||||
|
||||
return string(data), err
|
||||
}
|
||||
|
||||
// Scan implements [sql.Scanner] interface to scan the provided value
|
||||
// into the current `JsonArray` instance.
|
||||
func (m *JsonArray) Scan(value any) error {
|
||||
var data []byte
|
||||
switch v := value.(type) {
|
||||
case nil:
|
||||
// no cast needed
|
||||
case []byte:
|
||||
data = v
|
||||
case string:
|
||||
data = []byte(v)
|
||||
default:
|
||||
return fmt.Errorf("Failed to unmarshal JsonArray value: %q.", value)
|
||||
}
|
||||
|
||||
if len(data) == 0 {
|
||||
data = []byte("[]")
|
||||
}
|
||||
|
||||
return json.Unmarshal(data, m)
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package types_test
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"testing"
|
||||
|
||||
"github.com/pocketbase/pocketbase/tools/types"
|
||||
)
|
||||
|
||||
func TestJsonArrayMarshalJSON(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
json types.JsonArray
|
||||
expected string
|
||||
}{
|
||||
{nil, "[]"},
|
||||
{types.JsonArray{}, `[]`},
|
||||
{types.JsonArray{1, 2, 3}, `[1,2,3]`},
|
||||
{types.JsonArray{"test1", "test2", "test3"}, `["test1","test2","test3"]`},
|
||||
{types.JsonArray{1, "test"}, `[1,"test"]`},
|
||||
}
|
||||
|
||||
for i, s := range scenarios {
|
||||
result, err := s.json.MarshalJSON()
|
||||
if err != nil {
|
||||
t.Errorf("(%d) %v", i, err)
|
||||
continue
|
||||
}
|
||||
if string(result) != s.expected {
|
||||
t.Errorf("(%d) Expected %s, got %s", i, s.expected, string(result))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestJsonArrayValue(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
json types.JsonArray
|
||||
expected driver.Value
|
||||
}{
|
||||
{nil, nil},
|
||||
{types.JsonArray{}, `[]`},
|
||||
{types.JsonArray{1, 2, 3}, `[1,2,3]`},
|
||||
{types.JsonArray{"test1", "test2", "test3"}, `["test1","test2","test3"]`},
|
||||
{types.JsonArray{1, "test"}, `[1,"test"]`},
|
||||
}
|
||||
|
||||
for i, s := range scenarios {
|
||||
result, err := s.json.Value()
|
||||
if err != nil {
|
||||
t.Errorf("(%d) %v", i, err)
|
||||
continue
|
||||
}
|
||||
if result != s.expected {
|
||||
t.Errorf("(%d) Expected %s, got %v", i, s.expected, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestJsonArrayScan(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
value any
|
||||
expectError bool
|
||||
expectJson string
|
||||
}{
|
||||
{``, false, `[]`},
|
||||
{[]byte{}, false, `[]`},
|
||||
{nil, false, `[]`},
|
||||
{123, true, `[]`},
|
||||
{`""`, true, `[]`},
|
||||
{`invalid_json`, true, `[]`},
|
||||
{`"test"`, true, `[]`},
|
||||
{`1,2,3`, true, `[]`},
|
||||
{`[1, 2, 3`, true, `[]`},
|
||||
{`[1, 2, 3]`, false, `[1,2,3]`},
|
||||
{[]byte(`[1, 2, 3]`), false, `[1,2,3]`},
|
||||
{`[1, "test"]`, false, `[1,"test"]`},
|
||||
{`[]`, false, `[]`},
|
||||
}
|
||||
|
||||
for i, s := range scenarios {
|
||||
arr := types.JsonArray{}
|
||||
scanErr := arr.Scan(s.value)
|
||||
|
||||
hasErr := scanErr != nil
|
||||
if hasErr != s.expectError {
|
||||
t.Errorf("(%d) Expected %v, got %v (%v)", i, s.expectError, hasErr, scanErr)
|
||||
continue
|
||||
}
|
||||
|
||||
result, _ := arr.MarshalJSON()
|
||||
|
||||
if string(result) != s.expectJson {
|
||||
t.Errorf("(%d) Expected %s, got %v", i, s.expectJson, string(result))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// JsonMap defines a map that is safe for json and db read/write.
|
||||
type JsonMap map[string]any
|
||||
|
||||
// MarshalJSON implements the [json.Marshaler] interface.
|
||||
func (m JsonMap) MarshalJSON() ([]byte, error) {
|
||||
type alias JsonMap // prevent recursion
|
||||
|
||||
// inialize an empty map to ensure that `{}` is returned as json
|
||||
if m == nil {
|
||||
m = JsonMap{}
|
||||
}
|
||||
|
||||
return json.Marshal(alias(m))
|
||||
}
|
||||
|
||||
// Value implements the [driver.Valuer] interface.
|
||||
func (m JsonMap) Value() (driver.Value, error) {
|
||||
if m == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
data, err := json.Marshal(m)
|
||||
|
||||
return string(data), err
|
||||
}
|
||||
|
||||
// Scan implements [sql.Scanner] interface to scan the provided value
|
||||
// into the current `JsonMap` instance.
|
||||
func (m *JsonMap) Scan(value any) error {
|
||||
var data []byte
|
||||
switch v := value.(type) {
|
||||
case nil:
|
||||
// no cast needed
|
||||
case []byte:
|
||||
data = v
|
||||
case string:
|
||||
data = []byte(v)
|
||||
default:
|
||||
return fmt.Errorf("Failed to unmarshal JsonMap value: %q.", value)
|
||||
}
|
||||
|
||||
if len(data) == 0 {
|
||||
data = []byte("{}")
|
||||
}
|
||||
|
||||
return json.Unmarshal(data, m)
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package types_test
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"testing"
|
||||
|
||||
"github.com/pocketbase/pocketbase/tools/types"
|
||||
)
|
||||
|
||||
func TestJsonMapMarshalJSON(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
json types.JsonMap
|
||||
expected string
|
||||
}{
|
||||
{nil, "{}"},
|
||||
{types.JsonMap{}, `{}`},
|
||||
{types.JsonMap{"test1": 123, "test2": "lorem"}, `{"test1":123,"test2":"lorem"}`},
|
||||
{types.JsonMap{"test": []int{1, 2, 3}}, `{"test":[1,2,3]}`},
|
||||
}
|
||||
|
||||
for i, s := range scenarios {
|
||||
result, err := s.json.MarshalJSON()
|
||||
if err != nil {
|
||||
t.Errorf("(%d) %v", i, err)
|
||||
continue
|
||||
}
|
||||
if string(result) != s.expected {
|
||||
t.Errorf("(%d) Expected %s, got %s", i, s.expected, string(result))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestJsonMapValue(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
json types.JsonMap
|
||||
expected driver.Value
|
||||
}{
|
||||
{nil, nil},
|
||||
{types.JsonMap{}, `{}`},
|
||||
{types.JsonMap{"test1": 123, "test2": "lorem"}, `{"test1":123,"test2":"lorem"}`},
|
||||
{types.JsonMap{"test": []int{1, 2, 3}}, `{"test":[1,2,3]}`},
|
||||
}
|
||||
|
||||
for i, s := range scenarios {
|
||||
result, err := s.json.Value()
|
||||
if err != nil {
|
||||
t.Errorf("(%d) %v", i, err)
|
||||
continue
|
||||
}
|
||||
if result != s.expected {
|
||||
t.Errorf("(%d) Expected %s, got %v", i, s.expected, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestJsonArrayMapScan(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
value any
|
||||
expectError bool
|
||||
expectJson string
|
||||
}{
|
||||
{``, false, `{}`},
|
||||
{nil, false, `{}`},
|
||||
{[]byte{}, false, `{}`},
|
||||
{`{}`, false, `{}`},
|
||||
{123, true, `{}`},
|
||||
{`""`, true, `{}`},
|
||||
{`invalid_json`, true, `{}`},
|
||||
{`"test"`, true, `{}`},
|
||||
{`1,2,3`, true, `{}`},
|
||||
{`{"test": 1`, true, `{}`},
|
||||
{`{"test": 1}`, false, `{"test":1}`},
|
||||
{[]byte(`{"test": 1}`), false, `{"test":1}`},
|
||||
}
|
||||
|
||||
for i, s := range scenarios {
|
||||
arr := types.JsonMap{}
|
||||
scanErr := arr.Scan(s.value)
|
||||
|
||||
hasErr := scanErr != nil
|
||||
if hasErr != s.expectError {
|
||||
t.Errorf("(%d) Expected %v, got %v (%v)", i, s.expectError, hasErr, scanErr)
|
||||
continue
|
||||
}
|
||||
|
||||
result, _ := arr.MarshalJSON()
|
||||
|
||||
if string(result) != s.expectJson {
|
||||
t.Errorf("(%d) Expected %s, got %v", i, s.expectJson, string(result))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
)
|
||||
|
||||
// JsonRaw defines a json value type that is safe for db read/write.
|
||||
type JsonRaw []byte
|
||||
|
||||
// ParseJsonRaw creates a new JsonRaw instance from the provided value
|
||||
// (could be JsonRaw, int, float, string, []byte, etc.).
|
||||
func ParseJsonRaw(value any) (JsonRaw, error) {
|
||||
result := JsonRaw{}
|
||||
err := result.Scan(value)
|
||||
return result, err
|
||||
}
|
||||
|
||||
// String returns the current JsonRaw instance as a json encoded string.
|
||||
func (j JsonRaw) String() string {
|
||||
return string(j)
|
||||
}
|
||||
|
||||
// MarshalJSON implements the [json.Marshaler] interface.
|
||||
func (j JsonRaw) MarshalJSON() ([]byte, error) {
|
||||
if len(j) == 0 {
|
||||
return []byte("null"), nil
|
||||
}
|
||||
|
||||
return j, nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the [json.Unmarshaler] interface.
|
||||
func (j *JsonRaw) UnmarshalJSON(b []byte) error {
|
||||
if j == nil {
|
||||
return errors.New("JsonRaw: UnmarshalJSON on nil pointer")
|
||||
}
|
||||
|
||||
*j = append((*j)[0:0], b...)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Value implements the [driver.Valuer] interface.
|
||||
func (j JsonRaw) Value() (driver.Value, error) {
|
||||
if len(j) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return j.String(), nil
|
||||
}
|
||||
|
||||
// Scan implements [sql.Scanner] interface to scan the provided value
|
||||
// into the current JsonRaw instance.
|
||||
func (j *JsonRaw) Scan(value interface{}) error {
|
||||
var data []byte
|
||||
|
||||
switch v := value.(type) {
|
||||
case nil:
|
||||
// no cast is needed
|
||||
case []byte:
|
||||
if len(v) != 0 {
|
||||
data = v
|
||||
}
|
||||
case string:
|
||||
if v != "" {
|
||||
data = []byte(v)
|
||||
}
|
||||
case JsonRaw:
|
||||
if len(v) != 0 {
|
||||
data = []byte(v)
|
||||
}
|
||||
default:
|
||||
bytes, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data = bytes
|
||||
}
|
||||
|
||||
return j.UnmarshalJSON(data)
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
package types_test
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"testing"
|
||||
|
||||
"github.com/pocketbase/pocketbase/tools/types"
|
||||
)
|
||||
|
||||
func TestParseJsonRaw(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
value any
|
||||
expectError bool
|
||||
expectJson string
|
||||
}{
|
||||
{nil, false, `null`},
|
||||
{``, false, `null`},
|
||||
{[]byte{}, false, `null`},
|
||||
{types.JsonRaw{}, false, `null`},
|
||||
{`{}`, false, `{}`},
|
||||
{`[]`, false, `[]`},
|
||||
{123, false, `123`},
|
||||
{`""`, false, `""`},
|
||||
{`test`, false, `test`},
|
||||
{`{"invalid"`, false, `{"invalid"`}, // treated as a byte casted string
|
||||
{`{"test":1}`, false, `{"test":1}`},
|
||||
{[]byte(`[1,2,3]`), false, `[1,2,3]`},
|
||||
{[]int{1, 2, 3}, false, `[1,2,3]`},
|
||||
{map[string]int{"test": 1}, false, `{"test":1}`},
|
||||
}
|
||||
|
||||
for i, s := range scenarios {
|
||||
raw, parseErr := types.ParseJsonRaw(s.value)
|
||||
hasErr := parseErr != nil
|
||||
if hasErr != s.expectError {
|
||||
t.Errorf("(%d) Expected %v, got %v (%v)", i, s.expectError, hasErr, parseErr)
|
||||
continue
|
||||
}
|
||||
|
||||
result, _ := raw.MarshalJSON()
|
||||
|
||||
if string(result) != s.expectJson {
|
||||
t.Errorf("(%d) Expected %s, got %v", i, s.expectJson, string(result))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestJsonRawString(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
json types.JsonRaw
|
||||
expected string
|
||||
}{
|
||||
{nil, ``},
|
||||
{types.JsonRaw{}, ``},
|
||||
{types.JsonRaw([]byte(`123`)), `123`},
|
||||
{types.JsonRaw(`{"demo":123}`), `{"demo":123}`},
|
||||
}
|
||||
|
||||
for i, s := range scenarios {
|
||||
result := s.json.String()
|
||||
if result != s.expected {
|
||||
t.Errorf("(%d) Expected %q, got %q", i, s.expected, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestJsonRawMarshalJSON(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
json types.JsonRaw
|
||||
expected string
|
||||
}{
|
||||
{nil, `null`},
|
||||
{types.JsonRaw{}, `null`},
|
||||
{types.JsonRaw([]byte(`123`)), `123`},
|
||||
{types.JsonRaw(`{"demo":123}`), `{"demo":123}`},
|
||||
}
|
||||
|
||||
for i, s := range scenarios {
|
||||
result, err := s.json.MarshalJSON()
|
||||
if err != nil {
|
||||
t.Errorf("(%d) %v", i, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if string(result) != s.expected {
|
||||
t.Errorf("(%d) Expected %q, got %q", i, s.expected, string(result))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestJsonRawUnmarshalJSON(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
json []byte
|
||||
expectString string
|
||||
}{
|
||||
{nil, ""},
|
||||
{[]byte{0, 1, 2}, "\x00\x01\x02"},
|
||||
{[]byte("123"), "123"},
|
||||
{[]byte("test"), "test"},
|
||||
{[]byte(`{"test":123}`), `{"test":123}`},
|
||||
}
|
||||
|
||||
for i, s := range scenarios {
|
||||
raw := types.JsonRaw{}
|
||||
err := raw.UnmarshalJSON(s.json)
|
||||
if err != nil {
|
||||
t.Errorf("(%d) %v", i, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if raw.String() != s.expectString {
|
||||
t.Errorf("(%d) Expected %q, got %q", i, s.expectString, raw.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestJsonRawValue(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
json types.JsonRaw
|
||||
expected driver.Value
|
||||
}{
|
||||
{nil, nil},
|
||||
{types.JsonRaw{}, nil},
|
||||
{types.JsonRaw(``), nil},
|
||||
{types.JsonRaw(`test`), `test`},
|
||||
}
|
||||
|
||||
for i, s := range scenarios {
|
||||
result, err := s.json.Value()
|
||||
if err != nil {
|
||||
t.Errorf("(%d) %v", i, err)
|
||||
continue
|
||||
}
|
||||
if result != s.expected {
|
||||
t.Errorf("(%d) Expected %s, got %v", i, s.expected, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestJsonRawScan(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
value any
|
||||
expectError bool
|
||||
expectJson string
|
||||
}{
|
||||
{nil, false, `null`},
|
||||
{``, false, `null`},
|
||||
{[]byte{}, false, `null`},
|
||||
{types.JsonRaw{}, false, `null`},
|
||||
{types.JsonRaw(`test`), false, `test`},
|
||||
{`{}`, false, `{}`},
|
||||
{`[]`, false, `[]`},
|
||||
{123, false, `123`},
|
||||
{`""`, false, `""`},
|
||||
{`test`, false, `test`},
|
||||
{`{"invalid"`, false, `{"invalid"`}, // treated as a byte casted string
|
||||
{`{"test":1}`, false, `{"test":1}`},
|
||||
{[]byte(`[1,2,3]`), false, `[1,2,3]`},
|
||||
{[]int{1, 2, 3}, false, `[1,2,3]`},
|
||||
{map[string]int{"test": 1}, false, `{"test":1}`},
|
||||
}
|
||||
|
||||
for i, s := range scenarios {
|
||||
raw := types.JsonRaw{}
|
||||
scanErr := raw.Scan(s.value)
|
||||
hasErr := scanErr != nil
|
||||
if hasErr != s.expectError {
|
||||
t.Errorf("(%d) Expected %v, got %v (%v)", i, s.expectError, hasErr, scanErr)
|
||||
continue
|
||||
}
|
||||
|
||||
result, _ := raw.MarshalJSON()
|
||||
|
||||
if string(result) != s.expectJson {
|
||||
t.Errorf("(%d) Expected %s, got %v", i, s.expectJson, string(result))
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user