merge v0.23.0-rc changes

This commit is contained in:
Gani Georgiev
2024-09-29 19:23:19 +03:00
parent ad92992324
commit 844f18cac3
753 changed files with 85141 additions and 63396 deletions
+53
View File
@@ -35,6 +35,59 @@ func (d DateTime) Time() time.Time {
return d.t
}
// Add returns a new DateTime based on the current DateTime + the specified duration.
func (d DateTime) Add(duration time.Duration) DateTime {
d.t = d.t.Add(duration)
return d
}
// Sub returns a [time.Duration] by subtracting the specified DateTime from the current one.
//
// If the result exceeds the maximum (or minimum) value that can be stored in a [time.Duration],
// the maximum (or minimum) duration will be returned.
func (d DateTime) Sub(u DateTime) time.Duration {
return d.Time().Sub(u.Time())
}
// AddDate returns a new DateTime based on the current one + duration.
//
// It follows the same rules as [time.AddDate].
func (d DateTime) AddDate(years, months, days int) DateTime {
d.t = d.t.AddDate(years, months, days)
return d
}
// After reports whether the current DateTime instance is after u.
func (d DateTime) After(u DateTime) bool {
return d.Time().After(u.Time())
}
// Before reports whether the current DateTime instance is before u.
func (d DateTime) Before(u DateTime) bool {
return d.Time().Before(u.Time())
}
// Compare compares the current DateTime instance with u.
// If the current instance is before u, it returns -1.
// If the current instance is after u, it returns +1.
// If they're the same, it returns 0.
func (d DateTime) Compare(u DateTime) int {
return d.Time().Compare(u.Time())
}
// Equal reports whether the current DateTime and u represent the same time instant.
// Two DateTime can be equal even if they are in different locations.
// For example, 6:00 +0200 and 4:00 UTC are Equal.
func (d DateTime) Equal(u DateTime) bool {
return d.Time().Equal(u.Time())
}
// Unix returns the current DateTime as a Unix time, aka.
// the number of seconds elapsed since January 1, 1970 UTC.
func (d DateTime) Unix() int64 {
return d.Time().Unix()
}
// IsZero checks whether the current DateTime instance has zero time value.
func (d DateTime) IsZero() bool {
return d.Time().IsZero()
+255 -43
View File
@@ -1,6 +1,7 @@
package types_test
import (
"fmt"
"strings"
"testing"
"time"
@@ -41,15 +42,16 @@ func TestParseDateTime(t *testing.T) {
}
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
}
t.Run(fmt.Sprintf("%d_%#v", i, s.value), func(t *testing.T) {
dt, err := types.ParseDateTime(s.value)
if err != nil {
t.Fatalf("Failed to parse %v: %v", s.value, err)
}
if dt.String() != s.expected {
t.Errorf("(%d) Expected %q, got %q", i, s.expected, dt.String())
}
if dt.String() != s.expected {
t.Fatalf("Expected %q, got %q", s.expected, dt.String())
}
})
}
}
@@ -69,7 +71,210 @@ func TestDateTimeTime(t *testing.T) {
result := dt.Time()
if !expected.Equal(result) {
t.Errorf("Expected time %v, got %v", expected, result)
t.Fatalf("Expected time %v, got %v", expected, result)
}
}
func TestDateTimeAdd(t *testing.T) {
t.Parallel()
d1, _ := types.ParseDateTime("2024-01-01 10:00:00.123Z")
d2 := d1.Add(1 * time.Hour)
if d1.String() != "2024-01-01 10:00:00.123Z" {
t.Fatalf("Expected d1 to remain unchanged, got %s", d1.String())
}
expected := "2024-01-01 11:00:00.123Z"
if d2.String() != expected {
t.Fatalf("Expected d2 %s, got %s", expected, d2.String())
}
}
func TestDateTimeSub(t *testing.T) {
t.Parallel()
d1, _ := types.ParseDateTime("2024-01-01 10:00:00.123Z")
d2, _ := types.ParseDateTime("2024-01-01 10:30:00.123Z")
result := d2.Sub(d1)
if result.Minutes() != 30 {
t.Fatalf("Expected %v minutes diff, got %v", 30, result.Minutes())
}
}
func TestDateTimeAddDate(t *testing.T) {
t.Parallel()
d1, _ := types.ParseDateTime("2024-01-01 10:00:00.123Z")
d2 := d1.AddDate(1, 2, 3)
if d1.String() != "2024-01-01 10:00:00.123Z" {
t.Fatalf("Expected d1 to remain unchanged, got %s", d1.String())
}
expected := "2025-03-04 10:00:00.123Z"
if d2.String() != expected {
t.Fatalf("Expected d2 %s, got %s", expected, d2.String())
}
}
func TestDateTimeAfter(t *testing.T) {
t.Parallel()
d1, _ := types.ParseDateTime("2024-01-01 10:00:00.123Z")
d2, _ := types.ParseDateTime("2024-01-02 10:00:00.123Z")
d3, _ := types.ParseDateTime("2024-01-03 10:00:00.123Z")
scenarios := []struct {
a types.DateTime
b types.DateTime
expect bool
}{
// d1
{d1, d1, false},
{d1, d2, false},
{d1, d3, false},
// d2
{d2, d1, true},
{d2, d2, false},
{d2, d3, false},
// d3
{d3, d1, true},
{d3, d2, true},
{d3, d3, false},
}
for i, s := range scenarios {
t.Run(fmt.Sprintf("after_%d", i), func(t *testing.T) {
if v := s.a.After(s.b); v != s.expect {
t.Fatalf("Expected %v, got %v", s.expect, v)
}
})
}
}
func TestDateTimeBefore(t *testing.T) {
t.Parallel()
d1, _ := types.ParseDateTime("2024-01-01 10:00:00.123Z")
d2, _ := types.ParseDateTime("2024-01-02 10:00:00.123Z")
d3, _ := types.ParseDateTime("2024-01-03 10:00:00.123Z")
scenarios := []struct {
a types.DateTime
b types.DateTime
expect bool
}{
// d1
{d1, d1, false},
{d1, d2, true},
{d1, d3, true},
// d2
{d2, d1, false},
{d2, d2, false},
{d2, d3, true},
// d3
{d3, d1, false},
{d3, d2, false},
{d3, d3, false},
}
for i, s := range scenarios {
t.Run(fmt.Sprintf("before_%d", i), func(t *testing.T) {
if v := s.a.Before(s.b); v != s.expect {
t.Fatalf("Expected %v, got %v", s.expect, v)
}
})
}
}
func TestDateTimeCompare(t *testing.T) {
t.Parallel()
d1, _ := types.ParseDateTime("2024-01-01 10:00:00.123Z")
d2, _ := types.ParseDateTime("2024-01-02 10:00:00.123Z")
d3, _ := types.ParseDateTime("2024-01-03 10:00:00.123Z")
scenarios := []struct {
a types.DateTime
b types.DateTime
expect int
}{
// d1
{d1, d1, 0},
{d1, d2, -1},
{d1, d3, -1},
// d2
{d2, d1, 1},
{d2, d2, 0},
{d2, d3, -1},
// d3
{d3, d1, 1},
{d3, d2, 1},
{d3, d3, 0},
}
for i, s := range scenarios {
t.Run(fmt.Sprintf("compare_%d", i), func(t *testing.T) {
if v := s.a.Compare(s.b); v != s.expect {
t.Fatalf("Expected %v, got %v", s.expect, v)
}
})
}
}
func TestDateTimeEqual(t *testing.T) {
t.Parallel()
d1, _ := types.ParseDateTime("2024-01-01 10:00:00.123Z")
d2, _ := types.ParseDateTime("2024-01-01 10:00:00.123Z")
d3, _ := types.ParseDateTime("2024-01-01 10:00:00.124Z")
scenarios := []struct {
a types.DateTime
b types.DateTime
expect bool
}{
{d1, d1, true},
{d1, d2, true},
{d1, d3, false},
}
for i, s := range scenarios {
t.Run(fmt.Sprintf("equal_%d", i), func(t *testing.T) {
if v := s.a.Equal(s.b); v != s.expect {
t.Fatalf("Expected %v, got %v", s.expect, v)
}
})
}
}
func TestDateTimeUnix(t *testing.T) {
scenarios := []struct {
date string
expected int64
}{
{"", -62135596800},
{"2022-01-01 11:23:45.678Z", 1641036225},
}
for i, s := range scenarios {
t.Run(fmt.Sprintf("%d_%s", i, s.date), func(t *testing.T) {
dt, err := types.ParseDateTime(s.date)
if err != nil {
t.Fatal(err)
}
v := dt.Unix()
if v != s.expected {
t.Fatalf("Expected %d, got %d", s.expected, v)
}
})
}
}
@@ -108,19 +313,21 @@ func TestDateTimeMarshalJSON(t *testing.T) {
}
for i, s := range scenarios {
dt, err := types.ParseDateTime(s.date)
if err != nil {
t.Errorf("(%d) %v", i, err)
}
t.Run(fmt.Sprintf("%d_%s", i, s.date), func(t *testing.T) {
dt, err := types.ParseDateTime(s.date)
if err != nil {
t.Fatal(err)
}
result, err := dt.MarshalJSON()
if err != nil {
t.Errorf("(%d) %v", i, err)
}
result, err := dt.MarshalJSON()
if err != nil {
t.Fatal(err)
}
if string(result) != s.expected {
t.Errorf("(%d) Expected %q, got %q", i, s.expected, string(result))
}
if string(result) != s.expected {
t.Fatalf("Expected %q, got %q", s.expected, string(result))
}
})
}
}
@@ -137,12 +344,14 @@ func TestDateTimeUnmarshalJSON(t *testing.T) {
}
for i, s := range scenarios {
dt := types.DateTime{}
dt.UnmarshalJSON([]byte(s.date))
t.Run(fmt.Sprintf("%d_%s", i, s.date), func(t *testing.T) {
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())
}
if dt.String() != s.expected {
t.Fatalf("Expected %q, got %q", s.expected, dt.String())
}
})
}
}
@@ -159,16 +368,18 @@ func TestDateTimeValue(t *testing.T) {
}
for i, s := range scenarios {
dt, _ := types.ParseDateTime(s.value)
result, err := dt.Value()
if err != nil {
t.Errorf("(%d) %v", i, err)
continue
}
t.Run(fmt.Sprintf("%d_%s", i, s.value), func(t *testing.T) {
dt, _ := types.ParseDateTime(s.value)
if result != s.expected {
t.Errorf("(%d) Expected %q, got %q", i, s.expected, result)
}
result, err := dt.Value()
if err != nil {
t.Fatal(err)
}
if result != s.expected {
t.Fatalf("Expected %q, got %q", s.expected, result)
}
})
}
}
@@ -190,16 +401,17 @@ func TestDateTimeScan(t *testing.T) {
}
for i, s := range scenarios {
dt := types.DateTime{}
t.Run(fmt.Sprintf("%d_%#v", i, s.value), func(t *testing.T) {
dt := types.DateTime{}
err := dt.Scan(s.value)
if err != nil {
t.Errorf("(%d) Failed to parse %v: %v", i, s.value, err)
continue
}
err := dt.Scan(s.value)
if err != nil {
t.Fatalf("Failed to parse %v: %v", s.value, err)
}
if !strings.Contains(dt.String(), s.expected) {
t.Errorf("(%d) Expected %q, got %q", i, s.expected, dt.String())
}
if !strings.Contains(dt.String(), s.expected) {
t.Fatalf("Expected %q, got %q", s.expected, dt.String())
}
})
}
}
+15 -9
View File
@@ -6,32 +6,38 @@ import (
"fmt"
)
// JsonArray defines a slice that is safe for json and db read/write.
type JsonArray[T any] []T
// JSONArray defines a slice that is safe for json and db read/write.
type JSONArray[T any] []T
// internal alias to prevent recursion during marshalization.
type jsonArrayAlias[T any] JsonArray[T]
type jsonArrayAlias[T any] JSONArray[T]
// MarshalJSON implements the [json.Marshaler] interface.
func (m JsonArray[T]) MarshalJSON() ([]byte, error) {
func (m JSONArray[T]) MarshalJSON() ([]byte, error) {
// initialize an empty map to ensure that `[]` is returned as json
if m == nil {
m = JsonArray[T]{}
m = JSONArray[T]{}
}
return json.Marshal(jsonArrayAlias[T](m))
}
// String returns the string representation of the current json array.
func (m JSONArray[T]) String() string {
v, _ := m.MarshalJSON()
return string(v)
}
// Value implements the [driver.Valuer] interface.
func (m JsonArray[T]) Value() (driver.Value, error) {
func (m JSONArray[T]) Value() (driver.Value, error) {
data, err := json.Marshal(m)
return string(data), err
}
// Scan implements [sql.Scanner] interface to scan the provided value
// into the current JsonArray[T] instance.
func (m *JsonArray[T]) Scan(value any) error {
// into the current JSONArray[T] instance.
func (m *JSONArray[T]) Scan(value any) error {
var data []byte
switch v := value.(type) {
case nil:
@@ -41,7 +47,7 @@ func (m *JsonArray[T]) Scan(value any) error {
case string:
data = []byte(v)
default:
return fmt.Errorf("failed to unmarshal JsonArray value: %q", value)
return fmt.Errorf("Failed to unmarshal JSONArray value: %q.", value)
}
if len(data) == 0 {
+61 -33
View File
@@ -3,64 +3,92 @@ package types_test
import (
"database/sql/driver"
"encoding/json"
"fmt"
"testing"
"github.com/pocketbase/pocketbase/tools/types"
)
func TestJsonArrayMarshalJSON(t *testing.T) {
func TestJSONArrayMarshalJSON(t *testing.T) {
scenarios := []struct {
json json.Marshaler
expected string
}{
{new(types.JsonArray[any]), "[]"},
{types.JsonArray[any]{}, `[]`},
{types.JsonArray[int]{1, 2, 3}, `[1,2,3]`},
{types.JsonArray[string]{"test1", "test2", "test3"}, `["test1","test2","test3"]`},
{types.JsonArray[any]{1, "test"}, `[1,"test"]`},
{new(types.JSONArray[any]), "[]"},
{types.JSONArray[any]{}, `[]`},
{types.JSONArray[int]{1, 2, 3}, `[1,2,3]`},
{types.JSONArray[string]{"test1", "test2", "test3"}, `["test1","test2","test3"]`},
{types.JSONArray[any]{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))
}
t.Run(fmt.Sprintf("%d_%#v", i, s.expected), func(t *testing.T) {
result, err := s.json.MarshalJSON()
if err != nil {
t.Fatal(err)
}
if string(result) != s.expected {
t.Fatalf("Expected %s, got %s", s.expected, result)
}
})
}
}
func TestJsonArrayValue(t *testing.T) {
func TestJSONArrayString(t *testing.T) {
scenarios := []struct {
json fmt.Stringer
expected string
}{
{new(types.JSONArray[any]), "[]"},
{types.JSONArray[any]{}, `[]`},
{types.JSONArray[int]{1, 2, 3}, `[1,2,3]`},
{types.JSONArray[string]{"test1", "test2", "test3"}, `["test1","test2","test3"]`},
{types.JSONArray[any]{1, "test"}, `[1,"test"]`},
}
for i, s := range scenarios {
t.Run(fmt.Sprintf("%d_%#v", i, s.expected), func(t *testing.T) {
result := s.json.String()
if result != s.expected {
t.Fatalf("Expected\n%s\ngot\n%s", s.expected, result)
}
})
}
}
func TestJSONArrayValue(t *testing.T) {
scenarios := []struct {
json driver.Valuer
expected driver.Value
}{
{new(types.JsonArray[any]), `[]`},
{types.JsonArray[any]{}, `[]`},
{types.JsonArray[int]{1, 2, 3}, `[1,2,3]`},
{types.JsonArray[string]{"test1", "test2", "test3"}, `["test1","test2","test3"]`},
{types.JsonArray[any]{1, "test"}, `[1,"test"]`},
{new(types.JSONArray[any]), `[]`},
{types.JSONArray[any]{}, `[]`},
{types.JSONArray[int]{1, 2, 3}, `[1,2,3]`},
{types.JSONArray[string]{"test1", "test2", "test3"}, `["test1","test2","test3"]`},
{types.JSONArray[any]{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)
}
t.Run(fmt.Sprintf("%d_%#v", i, s.expected), func(t *testing.T) {
result, err := s.json.Value()
if err != nil {
t.Fatal(err)
}
if result != s.expected {
t.Fatalf("Expected %s, got %#v", s.expected, result)
}
})
}
}
func TestJsonArrayScan(t *testing.T) {
func TestJSONArrayScan(t *testing.T) {
scenarios := []struct {
value any
expectError bool
expectJson string
expectJSON string
}{
{``, false, `[]`},
{[]byte{}, false, `[]`},
@@ -78,7 +106,7 @@ func TestJsonArrayScan(t *testing.T) {
}
for i, s := range scenarios {
arr := types.JsonArray[any]{}
arr := types.JSONArray[any]{}
scanErr := arr.Scan(s.value)
hasErr := scanErr != nil
@@ -89,8 +117,8 @@ func TestJsonArrayScan(t *testing.T) {
result, _ := arr.MarshalJSON()
if string(result) != s.expectJson {
t.Errorf("(%d) Expected %s, got %v", i, s.expectJson, string(result))
if string(result) != s.expectJSON {
t.Errorf("(%d) Expected %s, got %v", i, s.expectJSON, string(result))
}
}
}
+19 -13
View File
@@ -6,47 +6,53 @@ import (
"fmt"
)
// JsonMap defines a map that is safe for json and db read/write.
type JsonMap map[string]any
// JSONMap defines a map that is safe for json and db read/write.
type JSONMap[T any] map[string]T
// MarshalJSON implements the [json.Marshaler] interface.
func (m JsonMap) MarshalJSON() ([]byte, error) {
type alias JsonMap // prevent recursion
func (m JSONMap[T]) MarshalJSON() ([]byte, error) {
type alias JSONMap[T] // prevent recursion
// initialize an empty map to ensure that `{}` is returned as json
if m == nil {
m = JsonMap{}
m = JSONMap[T]{}
}
return json.Marshal(alias(m))
}
// Get retrieves a single value from the current JsonMap.
// String returns the string representation of the current json map.
func (m JSONMap[T]) String() string {
v, _ := m.MarshalJSON()
return string(v)
}
// Get retrieves a single value from the current JSONMap[T].
//
// This helper was added primarily to assist the goja integration since custom map types
// don't have direct access to the map keys (https://pkg.go.dev/github.com/dop251/goja#hdr-Maps_with_methods).
func (m JsonMap) Get(key string) any {
func (m JSONMap[T]) Get(key string) T {
return m[key]
}
// Set sets a single value in the current JsonMap.
// Set sets a single value in the current JSONMap[T].
//
// This helper was added primarily to assist the goja integration since custom map types
// don't have direct access to the map keys (https://pkg.go.dev/github.com/dop251/goja#hdr-Maps_with_methods).
func (m JsonMap) Set(key string, value any) {
func (m JSONMap[T]) Set(key string, value T) {
m[key] = value
}
// Value implements the [driver.Valuer] interface.
func (m JsonMap) Value() (driver.Value, error) {
func (m JSONMap[T]) Value() (driver.Value, error) {
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 {
// into the current JSONMap[T] instance.
func (m *JSONMap[T]) Scan(value any) error {
var data []byte
switch v := value.(type) {
case nil:
@@ -56,7 +62,7 @@ func (m *JsonMap) Scan(value any) error {
case string:
data = []byte(v)
default:
return fmt.Errorf("failed to unmarshal JsonMap value: %q", value)
return fmt.Errorf("failed to unmarshal JSONMap[T] value: %q", value)
}
if len(data) == 0 {
+85 -53
View File
@@ -2,54 +2,81 @@ package types_test
import (
"database/sql/driver"
"fmt"
"testing"
"github.com/pocketbase/pocketbase/tools/types"
)
func TestJsonMapMarshalJSON(t *testing.T) {
func TestJSONMapMarshalJSON(t *testing.T) {
scenarios := []struct {
json types.JsonMap
json types.JSONMap[any]
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]}`},
{types.JSONMap[any]{}, `{}`},
{types.JSONMap[any]{"test1": 123, "test2": "lorem"}, `{"test1":123,"test2":"lorem"}`},
{types.JSONMap[any]{"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))
}
t.Run(fmt.Sprintf("%d_%#v", i, s.expected), func(t *testing.T) {
result, err := s.json.MarshalJSON()
if err != nil {
t.Fatal(err)
}
if string(result) != s.expected {
t.Fatalf("Expected\n%s\ngot\n%s", s.expected, result)
}
})
}
}
func TestJsonMapGet(t *testing.T) {
func TestJSONMapMarshalString(t *testing.T) {
scenarios := []struct {
json types.JsonMap
json types.JSONMap[any]
expected string
}{
{nil, "{}"},
{types.JSONMap[any]{}, `{}`},
{types.JSONMap[any]{"test1": 123, "test2": "lorem"}, `{"test1":123,"test2":"lorem"}`},
{types.JSONMap[any]{"test": []int{1, 2, 3}}, `{"test":[1,2,3]}`},
}
for i, s := range scenarios {
t.Run(fmt.Sprintf("%d_%#v", i, s.expected), func(t *testing.T) {
result := s.json.String()
if result != s.expected {
t.Fatalf("Expected\n%s\ngot\n%s", s.expected, result)
}
})
}
}
func TestJSONMapGet(t *testing.T) {
scenarios := []struct {
json types.JSONMap[any]
key string
expected any
}{
{nil, "test", nil},
{types.JsonMap{"test": 123}, "test", 123},
{types.JsonMap{"test": 123}, "missing", nil},
{types.JSONMap[any]{"test": 123}, "test", 123},
{types.JSONMap[any]{"test": 123}, "missing", nil},
}
for i, s := range scenarios {
result := s.json.Get(s.key)
if result != s.expected {
t.Errorf("(%d) Expected %s, got %v", i, s.expected, result)
}
t.Run(fmt.Sprintf("%d_%s", i, s.key), func(t *testing.T) {
result := s.json.Get(s.key)
if result != s.expected {
t.Fatalf("Expected %s, got %#v", s.expected, result)
}
})
}
}
func TestJsonMapSet(t *testing.T) {
func TestJSONMapSet(t *testing.T) {
scenarios := []struct {
key string
value any
@@ -60,44 +87,48 @@ func TestJsonMapSet(t *testing.T) {
}
for i, s := range scenarios {
j := types.JsonMap{}
t.Run(fmt.Sprintf("%d_%s", i, s.key), func(t *testing.T) {
j := types.JSONMap[any]{}
j.Set(s.key, s.value)
j.Set(s.key, s.value)
if v := j[s.key]; v != s.value {
t.Errorf("(%d) Expected %s, got %v", i, s.value, v)
}
if v := j[s.key]; v != s.value {
t.Fatalf("Expected %s, got %#v", s.value, v)
}
})
}
}
func TestJsonMapValue(t *testing.T) {
func TestJSONMapValue(t *testing.T) {
scenarios := []struct {
json types.JsonMap
json types.JSONMap[any]
expected driver.Value
}{
{nil, `{}`},
{types.JsonMap{}, `{}`},
{types.JsonMap{"test1": 123, "test2": "lorem"}, `{"test1":123,"test2":"lorem"}`},
{types.JsonMap{"test": []int{1, 2, 3}}, `{"test":[1,2,3]}`},
{types.JSONMap[any]{}, `{}`},
{types.JSONMap[any]{"test1": 123, "test2": "lorem"}, `{"test1":123,"test2":"lorem"}`},
{types.JSONMap[any]{"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)
}
t.Run(fmt.Sprintf("%d_%#v", i, s.expected), func(t *testing.T) {
result, err := s.json.Value()
if err != nil {
t.Fatal(err)
}
if result != s.expected {
t.Fatalf("Expected %s, got %#v", s.expected, result)
}
})
}
}
func TestJsonArrayMapScan(t *testing.T) {
func TestJSONArrayMapScan(t *testing.T) {
scenarios := []struct {
value any
expectError bool
expectJson string
expectJSON string
}{
{``, false, `{}`},
{nil, false, `{}`},
@@ -114,19 +145,20 @@ func TestJsonArrayMapScan(t *testing.T) {
}
for i, s := range scenarios {
arr := types.JsonMap{}
scanErr := arr.Scan(s.value)
t.Run(fmt.Sprintf("%d_%#v", i, s.value), func(t *testing.T) {
arr := types.JSONMap[any]{}
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
}
hasErr := scanErr != nil
if hasErr != s.expectError {
t.Fatalf("Expected %v, got %v (%v)", s.expectError, hasErr, scanErr)
}
result, _ := arr.MarshalJSON()
result, _ := arr.MarshalJSON()
if string(result) != s.expectJson {
t.Errorf("(%d) Expected %s, got %v", i, s.expectJson, string(result))
}
if string(result) != s.expectJSON {
t.Fatalf("Expected %s, got %s", s.expectJSON, result)
}
})
}
}
+17 -16
View File
@@ -6,24 +6,25 @@ import (
"errors"
)
// JsonRaw defines a json value type that is safe for db read/write.
type JsonRaw []byte
// 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{}
// 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)
// String returns the current JSONRaw instance as a json encoded string.
func (j JSONRaw) String() string {
raw, _ := j.MarshalJSON()
return string(raw)
}
// MarshalJSON implements the [json.Marshaler] interface.
func (j JsonRaw) MarshalJSON() ([]byte, error) {
func (j JSONRaw) MarshalJSON() ([]byte, error) {
if len(j) == 0 {
return []byte("null"), nil
}
@@ -32,9 +33,9 @@ func (j JsonRaw) MarshalJSON() ([]byte, error) {
}
// UnmarshalJSON implements the [json.Unmarshaler] interface.
func (j *JsonRaw) UnmarshalJSON(b []byte) error {
func (j *JSONRaw) UnmarshalJSON(b []byte) error {
if j == nil {
return errors.New("JsonRaw: UnmarshalJSON on nil pointer")
return errors.New("JSONRaw: UnmarshalJSON on nil pointer")
}
*j = append((*j)[0:0], b...)
@@ -43,7 +44,7 @@ func (j *JsonRaw) UnmarshalJSON(b []byte) error {
}
// Value implements the [driver.Valuer] interface.
func (j JsonRaw) Value() (driver.Value, error) {
func (j JSONRaw) Value() (driver.Value, error) {
if len(j) == 0 {
return nil, nil
}
@@ -52,8 +53,8 @@ func (j JsonRaw) Value() (driver.Value, error) {
}
// Scan implements [sql.Scanner] interface to scan the provided value
// into the current JsonRaw instance.
func (j *JsonRaw) Scan(value any) error {
// into the current JSONRaw instance.
func (j *JSONRaw) Scan(value any) error {
var data []byte
switch v := value.(type) {
@@ -67,7 +68,7 @@ func (j *JsonRaw) Scan(value any) error {
if v != "" {
data = []byte(v)
}
case JsonRaw:
case JSONRaw:
if len(v) != 0 {
data = []byte(v)
}
+96 -85
View File
@@ -2,21 +2,22 @@ package types_test
import (
"database/sql/driver"
"fmt"
"testing"
"github.com/pocketbase/pocketbase/tools/types"
)
func TestParseJsonRaw(t *testing.T) {
func TestParseJSONRaw(t *testing.T) {
scenarios := []struct {
value any
expectError bool
expectJson string
expectJSON string
}{
{nil, false, `null`},
{``, false, `null`},
{[]byte{}, false, `null`},
{types.JsonRaw{}, false, `null`},
{types.JSONRaw{}, false, `null`},
{`{}`, false, `{}`},
{`[]`, false, `[]`},
{123, false, `123`},
@@ -30,70 +31,75 @@ func TestParseJsonRaw(t *testing.T) {
}
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
}
t.Run(fmt.Sprintf("%d_%#v", i, s.value), func(t *testing.T) {
raw, parseErr := types.ParseJSONRaw(s.value)
result, _ := raw.MarshalJSON()
hasErr := parseErr != nil
if hasErr != s.expectError {
t.Fatalf("Expected %v, got %v (%v)", s.expectError, hasErr, parseErr)
}
if string(result) != s.expectJson {
t.Errorf("(%d) Expected %s, got %v", i, s.expectJson, string(result))
}
result, _ := raw.MarshalJSON()
if string(result) != s.expectJSON {
t.Fatalf("Expected %s, got %s", s.expectJSON, string(result))
}
})
}
}
func TestJsonRawString(t *testing.T) {
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
json types.JSONRaw
expected string
}{
{nil, `null`},
{types.JsonRaw{}, `null`},
{types.JsonRaw([]byte(`123`)), `123`},
{types.JsonRaw(`{"demo":123}`), `{"demo":123}`},
{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))
}
t.Run(fmt.Sprintf("%d_%s", i, s.expected), func(t *testing.T) {
result := s.json.String()
if result != s.expected {
t.Fatalf("Expected %q, got %q", s.expected, result)
}
})
}
}
func TestJsonRawUnmarshalJSON(t *testing.T) {
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 {
t.Run(fmt.Sprintf("%d_%s", i, s.expected), func(t *testing.T) {
result, err := s.json.MarshalJSON()
if err != nil {
t.Fatal(err)
}
if string(result) != s.expected {
t.Fatalf("Expected %q, got %q", s.expected, string(result))
}
})
}
}
func TestJSONRawUnmarshalJSON(t *testing.T) {
scenarios := []struct {
json []byte
expectString string
}{
{nil, ""},
{nil, `null`},
{[]byte{0, 1, 2}, "\x00\x01\x02"},
{[]byte("123"), "123"},
{[]byte("test"), "test"},
@@ -101,53 +107,57 @@ func TestJsonRawUnmarshalJSON(t *testing.T) {
}
for i, s := range scenarios {
raw := types.JsonRaw{}
err := raw.UnmarshalJSON(s.json)
if err != nil {
t.Errorf("(%d) %v", i, err)
continue
}
t.Run(fmt.Sprintf("%d_%s", i, s.expectString), func(t *testing.T) {
raw := types.JSONRaw{}
if raw.String() != s.expectString {
t.Errorf("(%d) Expected %q, got %q", i, s.expectString, raw.String())
}
err := raw.UnmarshalJSON(s.json)
if err != nil {
t.Fatal(err)
}
if raw.String() != s.expectString {
t.Fatalf("Expected %q, got %q", s.expectString, raw.String())
}
})
}
}
func TestJsonRawValue(t *testing.T) {
func TestJSONRawValue(t *testing.T) {
scenarios := []struct {
json types.JsonRaw
json types.JSONRaw
expected driver.Value
}{
{nil, nil},
{types.JsonRaw{}, nil},
{types.JsonRaw(``), nil},
{types.JsonRaw(`test`), `test`},
{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)
}
t.Run(fmt.Sprintf("%d_%#v", i, s.json), func(t *testing.T) {
result, err := s.json.Value()
if err != nil {
t.Fatal(err)
}
if result != s.expected {
t.Fatalf("Expected %s, got %v", s.expected, result)
}
})
}
}
func TestJsonRawScan(t *testing.T) {
func TestJSONRawScan(t *testing.T) {
scenarios := []struct {
value any
expectError bool
expectJson string
expectJSON string
}{
{nil, false, `null`},
{``, false, `null`},
{[]byte{}, false, `null`},
{types.JsonRaw{}, false, `null`},
{types.JsonRaw(`test`), false, `test`},
{types.JSONRaw{}, false, `null`},
{types.JSONRaw(`test`), false, `test`},
{`{}`, false, `{}`},
{`[]`, false, `[]`},
{123, false, `123`},
@@ -161,18 +171,19 @@ func TestJsonRawScan(t *testing.T) {
}
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
}
t.Run(fmt.Sprintf("%d_%#v", i, s.value), func(t *testing.T) {
raw := types.JSONRaw{}
scanErr := raw.Scan(s.value)
hasErr := scanErr != nil
if hasErr != s.expectError {
t.Fatalf("Expected %v, got %v (%v)", s.expectError, hasErr, scanErr)
}
result, _ := raw.MarshalJSON()
result, _ := raw.MarshalJSON()
if string(result) != s.expectJson {
t.Errorf("(%d) Expected %s, got %v", i, s.expectJson, string(result))
}
if string(result) != s.expectJSON {
t.Fatalf("Expected %s, got %v", s.expectJSON, string(result))
}
})
}
}