Skip to content

Commit 2acfcfe

Browse files
committed
tests: update tests
1 parent 93a1527 commit 2acfcfe

File tree

6 files changed

+85
-71
lines changed

6 files changed

+85
-71
lines changed

models_test.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,13 @@ type TimestampModel struct {
3535
RFC3339P *time.Time `jsonapi:"attr,rfc3339p,rfc3339"`
3636
}
3737

38-
type WithNullables struct {
39-
ID int `jsonapi:"primary,with-nullables"`
40-
Name string `jsonapi:"attr,name"`
41-
IntTime Nullable[time.Time] `jsonapi:"attr,int_time,omitempty"`
42-
RFC3339Time Nullable[time.Time] `jsonapi:"attr,rfc3339_time,rfc3339,omitempty"`
43-
ISO8601Time Nullable[time.Time] `jsonapi:"attr,iso8601_time,iso8601,omitempty"`
44-
Bool Nullable[bool] `jsonapi:"attr,bool,omitempty"`
38+
type WithNullableAttrs struct {
39+
ID int `jsonapi:"primary,with-nullables"`
40+
Name string `jsonapi:"attr,name"`
41+
IntTime NullableAttr[time.Time] `jsonapi:"attr,int_time,omitempty"`
42+
RFC3339Time NullableAttr[time.Time] `jsonapi:"attr,rfc3339_time,rfc3339,omitempty"`
43+
ISO8601Time NullableAttr[time.Time] `jsonapi:"attr,iso8601_time,iso8601,omitempty"`
44+
Bool NullableAttr[bool] `jsonapi:"attr,bool,omitempty"`
4545
}
4646

4747
type Car struct {

nullable.go

Lines changed: 26 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@ import (
55
"time"
66
)
77

8-
// Nullable is a generic type, which implements a field that can be one of three states:
8+
// NullableAttr is a generic type, which implements a field that can be one of three states:
99
//
1010
// - field is not set in the request
1111
// - field is explicitly set to `null` in the request
1212
// - field is explicitly set to a valid value in the request
1313
//
14-
// Nullable is intended to be used with JSON marshalling and unmarshalling.
14+
// NullableAttr is intended to be used with JSON marshalling and unmarshalling.
1515
// This is generally useful for PATCH requests, where attributes with zero
1616
// values are intentionally not marshaled into the request payload so that
1717
// existing attribute values are not overwritten.
@@ -22,32 +22,31 @@ import (
2222
// - map[false]T means an explicit null was provided
2323
// - nil or zero map means the field was not provided
2424
//
25-
// If the field is expected to be optional, add the `omitempty` JSON tags. Do NOT use `*Nullable`!
25+
// If the field is expected to be optional, add the `omitempty` JSON tags. Do NOT use `*NullableAttr`!
2626
//
2727
// Adapted from https://www.jvt.me/posts/2024/01/09/go-json-nullable/
28+
type NullableAttr[T any] map[bool]T
2829

29-
type Nullable[T any] map[bool]T
30-
31-
// NewNullableWithValue is a convenience helper to allow constructing a
32-
// Nullable with a given value, for instance to construct a field inside a
30+
// NewNullableAttrWithValue is a convenience helper to allow constructing a
31+
// NullableAttr with a given value, for instance to construct a field inside a
3332
// struct without introducing an intermediate variable.
34-
func NewNullableWithValue[T any](t T) Nullable[T] {
35-
var n Nullable[T]
33+
func NewNullableAttrWithValue[T any](t T) NullableAttr[T] {
34+
var n NullableAttr[T]
3635
n.Set(t)
3736
return n
3837
}
3938

40-
// NewNullNullable is a convenience helper to allow constructing a Nullable with
39+
// NewNullNullableAttr is a convenience helper to allow constructing a NullableAttr with
4140
// an explicit `null`, for instance to construct a field inside a struct
4241
// without introducing an intermediate variable
43-
func NewNullNullable[T any]() Nullable[T] {
44-
var n Nullable[T]
42+
func NewNullNullableAttr[T any]() NullableAttr[T] {
43+
var n NullableAttr[T]
4544
n.SetNull()
4645
return n
4746
}
4847

4948
// Get retrieves the underlying value, if present, and returns an error if the value was not present
50-
func (t Nullable[T]) Get() (T, error) {
49+
func (t NullableAttr[T]) Get() (T, error) {
5150
var empty T
5251
if t.IsNull() {
5352
return empty, errors.New("value is null")
@@ -59,49 +58,49 @@ func (t Nullable[T]) Get() (T, error) {
5958
}
6059

6160
// Set sets the underlying value to a given value
62-
func (t *Nullable[T]) Set(value T) {
61+
func (t *NullableAttr[T]) Set(value T) {
6362
*t = map[bool]T{true: value}
6463
}
6564

6665
// Set sets the underlying value to a given value
67-
func (t *Nullable[T]) SetInterface(value interface{}) {
66+
func (t *NullableAttr[T]) SetInterface(value interface{}) {
6867
t.Set(value.(T))
6968
}
7069

7170
// IsNull indicate whether the field was sent, and had a value of `null`
72-
func (t Nullable[T]) IsNull() bool {
71+
func (t NullableAttr[T]) IsNull() bool {
7372
_, foundNull := t[false]
7473
return foundNull
7574
}
7675

7776
// SetNull indicate that the field was sent, and had a value of `null`
78-
func (t *Nullable[T]) SetNull() {
77+
func (t *NullableAttr[T]) SetNull() {
7978
var empty T
8079
*t = map[bool]T{false: empty}
8180
}
8281

8382
// IsSpecified indicates whether the field was sent
84-
func (t Nullable[T]) IsSpecified() bool {
83+
func (t NullableAttr[T]) IsSpecified() bool {
8584
return len(t) != 0
8685
}
8786

8887
// SetUnspecified indicate whether the field was sent
89-
func (t *Nullable[T]) SetUnspecified() {
88+
func (t *NullableAttr[T]) SetUnspecified() {
9089
*t = map[bool]T{}
9190
}
9291

93-
func NullableBool(v bool) Nullable[bool] {
94-
return NewNullableWithValue[bool](v)
92+
func NullableBool(v bool) NullableAttr[bool] {
93+
return NewNullableAttrWithValue[bool](v)
9594
}
9695

97-
func NullBool() Nullable[bool] {
98-
return NewNullNullable[bool]()
96+
func NullBool() NullableAttr[bool] {
97+
return NewNullNullableAttr[bool]()
9998
}
10099

101-
func NullableTime(v time.Time) Nullable[time.Time] {
102-
return NewNullableWithValue[time.Time](v)
100+
func NullableTime(v time.Time) NullableAttr[time.Time] {
101+
return NewNullableAttrWithValue[time.Time](v)
103102
}
104103

105-
func NullTime() Nullable[time.Time] {
106-
return NewNullNullable[time.Time]()
104+
func NullTime() NullableAttr[time.Time] {
105+
return NewNullNullableAttr[time.Time]()
107106
}

request.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -589,8 +589,8 @@ func unmarshalAttribute(
589589
value = reflect.ValueOf(attribute)
590590
fieldType := structField.Type
591591

592-
// Handle Nullable[T]
593-
if strings.HasPrefix(fieldValue.Type().Name(), "Nullable[") {
592+
// Handle NullableAttr[T]
593+
if strings.HasPrefix(fieldValue.Type().Name(), "NullableAttr[") {
594594
value, err = handleNullable(attribute, args, structField, fieldValue)
595595
return
596596
}
@@ -746,7 +746,7 @@ func handleTime(attribute interface{}, args []string, fieldValue reflect.Value)
746746
return reflect.ValueOf(time.Now()), ErrInvalidTime
747747
}
748748

749-
t := time.Unix(at, 0).UTC()
749+
t := time.Unix(at, 0)
750750

751751
return reflect.ValueOf(t), nil
752752
}

request_test.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ func TestStringPointerField(t *testing.T) {
303303
func TestUnmarshalNullableTime(t *testing.T) {
304304
aTime := time.Date(2016, 8, 17, 8, 27, 12, 23849, time.UTC)
305305

306-
out := new(WithNullables)
306+
out := new(WithNullableAttrs)
307307

308308
attrs := map[string]interface{}{
309309
"name": "Name",
@@ -312,7 +312,7 @@ func TestUnmarshalNullableTime(t *testing.T) {
312312
"iso8601_time": aTime.Format(iso8601TimeFormat),
313313
}
314314

315-
if err := UnmarshalPayload(samplePayloadWithNullables(attrs), out); err != nil {
315+
if err := UnmarshalPayload(samplePayloadWithNullableAttrs(attrs), out); err != nil {
316316
t.Fatal(err)
317317
}
318318

@@ -355,7 +355,7 @@ func TestUnmarshalNullableTime(t *testing.T) {
355355
}
356356

357357
func TestUnmarshalNullableBool(t *testing.T) {
358-
out := new(WithNullables)
358+
out := new(WithNullableAttrs)
359359

360360
aBool := false
361361

@@ -364,7 +364,7 @@ func TestUnmarshalNullableBool(t *testing.T) {
364364
"bool": aBool,
365365
}
366366

367-
if err := UnmarshalPayload(samplePayloadWithNullables(attrs), out); err != nil {
367+
if err := UnmarshalPayload(samplePayloadWithNullableAttrs(attrs), out); err != nil {
368368
t.Fatal(err)
369369
}
370370

@@ -1508,7 +1508,7 @@ func sampleWithPointerPayload(m map[string]interface{}) io.Reader {
15081508
return out
15091509
}
15101510

1511-
func samplePayloadWithNullables(m map[string]interface{}) io.Reader {
1511+
func samplePayloadWithNullableAttrs(m map[string]interface{}) io.Reader {
15121512
payload := &OnePayload{
15131513
Data: &Node{
15141514
ID: "5",

response.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,7 @@ func visitModelNode(model interface{}, included *map[string]*Node,
332332
}
333333

334334
// Handle Nullable[T]
335-
if strings.HasPrefix(fieldValue.Type().Name(), "Nullable[") {
335+
if strings.HasPrefix(fieldValue.Type().Name(), "NullableAttr[") {
336336
// handle unspecified
337337
if fieldValue.IsNil() {
338338
continue

response_test.go

Lines changed: 43 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -820,17 +820,17 @@ func TestMarshal_Times(t *testing.T) {
820820
}
821821
}
822822

823-
func TestCustomMarshal_Time(t *testing.T) {
823+
func TestNullableAttr_Time(t *testing.T) {
824824
aTime := time.Date(2016, 8, 17, 8, 27, 12, 23849, time.UTC)
825825

826826
for _, tc := range []struct {
827827
desc string
828-
input *WithNullables
828+
input *WithNullableAttrs
829829
verification func(data map[string]interface{}) error
830830
}{
831831
{
832-
desc: "time_nil",
833-
input: &WithNullables{
832+
desc: "time_unspecified",
833+
input: &WithNullableAttrs{
834834
ID: 5,
835835
RFC3339Time: nil,
836836
},
@@ -843,8 +843,22 @@ func TestCustomMarshal_Time(t *testing.T) {
843843
},
844844
},
845845
{
846-
desc: "time_value_rfc3339",
847-
input: &WithNullables{
846+
desc: "time_null",
847+
input: &WithNullableAttrs{
848+
ID: 5,
849+
RFC3339Time: NullTime(),
850+
},
851+
verification: func(root map[string]interface{}) error {
852+
v := root["data"].(map[string]interface{})["attributes"].(map[string]interface{})["rfc3339_time"]
853+
if got, want := v, (interface{})(nil); got != want {
854+
return fmt.Errorf("got %v, want %v", got, want)
855+
}
856+
return nil
857+
},
858+
},
859+
{
860+
desc: "time_not_null_rfc3339",
861+
input: &WithNullableAttrs{
848862
ID: 5,
849863
RFC3339Time: NullableTime(aTime),
850864
},
@@ -857,8 +871,8 @@ func TestCustomMarshal_Time(t *testing.T) {
857871
},
858872
},
859873
{
860-
desc: "time_value_iso8601",
861-
input: &WithNullables{
874+
desc: "time_not_null_iso8601",
875+
input: &WithNullableAttrs{
862876
ID: 5,
863877
ISO8601Time: NullableTime(aTime),
864878
},
@@ -871,14 +885,14 @@ func TestCustomMarshal_Time(t *testing.T) {
871885
},
872886
},
873887
{
874-
desc: "time_null_integer",
875-
input: &WithNullables{
888+
desc: "time_not_null_int",
889+
input: &WithNullableAttrs{
876890
ID: 5,
877-
IntTime: NullTime(),
891+
IntTime: NullableTime(aTime),
878892
},
879893
verification: func(root map[string]interface{}) error {
880-
v := root["data"].(map[string]interface{})["attributes"].(map[string]interface{})["int_time"]
881-
if got, want := v, (interface{})(nil); got != want {
894+
v := root["data"].(map[string]interface{})["attributes"].(map[string]interface{})["int_time"].(float64)
895+
if got, want := int64(v), aTime.Unix(); got != want {
882896
return fmt.Errorf("got %v, want %v", got, want)
883897
}
884898
return nil
@@ -901,17 +915,17 @@ func TestCustomMarshal_Time(t *testing.T) {
901915
}
902916
}
903917

904-
func TestCustomMarshal_Bool(t *testing.T) {
918+
func TestNullableAttr_Bool(t *testing.T) {
905919
aBool := true
906920

907921
for _, tc := range []struct {
908922
desc string
909-
input *WithNullables
923+
input *WithNullableAttrs
910924
verification func(data map[string]interface{}) error
911925
}{
912926
{
913-
desc: "bool_nil",
914-
input: &WithNullables{
927+
desc: "bool_unspecified",
928+
input: &WithNullableAttrs{
915929
ID: 5,
916930
Bool: nil,
917931
},
@@ -924,33 +938,34 @@ func TestCustomMarshal_Bool(t *testing.T) {
924938
},
925939
},
926940
{
927-
desc: "unsetable_value_present",
928-
input: &WithNullables{
941+
desc: "bool_null",
942+
input: &WithNullableAttrs{
929943
ID: 5,
930-
Bool: NullableBool(aBool),
944+
Bool: NullBool(),
931945
},
932946
verification: func(root map[string]interface{}) error {
933-
v := root["data"].(map[string]interface{})["attributes"].(map[string]interface{})["bool"].(bool)
934-
if got, want := v, aBool; got != want {
947+
v := root["data"].(map[string]interface{})["attributes"].(map[string]interface{})["bool"]
948+
if got, want := v, (interface{})(nil); got != want {
935949
return fmt.Errorf("got %v, want %v", got, want)
936950
}
937951
return nil
938952
},
939953
},
940954
{
941-
desc: "unsetable_nil_value",
942-
input: &WithNullables{
955+
desc: "bool_not_null",
956+
input: &WithNullableAttrs{
943957
ID: 5,
944-
Bool: NullBool(),
958+
Bool: NullableBool(aBool),
945959
},
946960
verification: func(root map[string]interface{}) error {
947-
v := root["data"].(map[string]interface{})["attributes"].(map[string]interface{})["bool"]
948-
if got, want := v, (interface{})(nil); got != want {
961+
v := root["data"].(map[string]interface{})["attributes"].(map[string]interface{})["bool"].(bool)
962+
if got, want := v, aBool; got != want {
949963
return fmt.Errorf("got %v, want %v", got, want)
950964
}
951965
return nil
952966
},
953-
}} {
967+
},
968+
} {
954969
t.Run(tc.desc, func(t *testing.T) {
955970
out := bytes.NewBuffer(nil)
956971
if err := MarshalPayload(out, tc.input); err != nil {

0 commit comments

Comments
 (0)