Skip to content

Commit 4dedb19

Browse files
author
Karl Kirch
committed
Remove support for NullableRelationship[[]*...] slice types
1 parent ec17fee commit 4dedb19

File tree

6 files changed

+32
-118
lines changed

6 files changed

+32
-118
lines changed

models_test.go

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,13 @@ type TimestampModel struct {
3636
}
3737

3838
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"`
45-
NullableComment NullableRelationship[*Comment] `jsonapi:"relation,nullable_comment,omitempty"`
46-
NullableComments NullableRelationship[[]*Comment] `jsonapi:"relation,nullable_comments,omitempty"`
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"`
45+
NullableComment NullableRelationship[*Comment] `jsonapi:"relation,nullable_comment,omitempty"`
4746
}
4847

4948
type Car struct {

nullable.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,13 @@ type NullableAttr[T any] map[bool]T
4545
//
4646
// If the relationship is expected to be optional, add the `omitempty` JSON tags. Do NOT use `*NullableRelationship`!
4747
//
48-
// Slice types are allowed for NullableRelationships.
48+
// Slice types are not currently supported for NullableRelationships as the nullable nature can be expressed via empty array
4949
// `polyrelation` JSON tags are NOT currently supported.
5050
//
5151
// NullableRelationships must have an inner type of pointer:
5252
//
5353
// - NullableRelationship[*Comment] - valid
54-
// - NullableRelationship[[]*Comment] - valid
54+
// - NullableRelationship[[]*Comment] - invalid
5555
// - NullableRelationship[Comment] - invalid
5656
type NullableRelationship[T any] map[bool]T
5757

request.go

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -443,14 +443,6 @@ func unmarshalNode(data *Node, model reflect.Value, included *map[string]*Node)
443443
// model, depending on annotation
444444
m := reflect.New(sliceType.Elem().Elem())
445445

446-
// Nullable relationships have an extra pointer indirection
447-
// unwind that here
448-
if strings.HasPrefix(fieldType.Type.Name(), "NullableRelationship[") {
449-
if m.Kind() == reflect.Ptr {
450-
m = reflect.New(sliceType.Elem().Elem().Elem())
451-
}
452-
}
453-
454446
err = unmarshalNodeMaybeChoice(&m, n, annotation, choiceMapping, included)
455447
if err != nil {
456448
er = err
@@ -471,13 +463,13 @@ func unmarshalNode(data *Node, model reflect.Value, included *map[string]*Node)
471463
json.NewEncoder(buf).Encode(relDataStr)
472464

473465
isExplicitNull := false
474-
if err := json.NewDecoder(buf).Decode(relationship); err != nil {
475-
// We couldn't decode the data into the relationship type
476-
// check if this is a string "null" which indicates
477-
// disassociating the relationship
478-
if relDataStr == "null" {
479-
isExplicitNull = true
480-
}
466+
relationshipDecodeErr := json.NewDecoder(buf).Decode(relationship)
467+
if relationshipDecodeErr == nil && relationship.Data == nil {
468+
// If the relationship was a valid node and relationship data was null
469+
// this indicates disassociating the relationship
470+
isExplicitNull = true
471+
} else if relationshipDecodeErr != nil {
472+
fmt.Printf("decode err %v\n", relationshipDecodeErr)
481473
}
482474

483475
// This will hold either the value of the choice type model or the actual

request_test.go

Lines changed: 4 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -428,13 +428,15 @@ func TestUnmarshalNullableRelationshipsNonNullValue(t *testing.T) {
428428
}
429429
}
430430

431-
func TestUnmarshalNullableRelationshipsNullStringValue(t *testing.T) {
431+
func TestUnmarshalNullableRelationshipsExplicitNullValue(t *testing.T) {
432432
payload := &OnePayload{
433433
Data: &Node{
434434
ID: "10",
435435
Type: "with-nullables",
436436
Relationships: map[string]interface{}{
437-
"nullable_comment": "null",
437+
"nullable_comment": &RelationshipOneNode{
438+
Data: nil,
439+
},
438440
},
439441
},
440442
}
@@ -455,32 +457,6 @@ func TestUnmarshalNullableRelationshipsNullStringValue(t *testing.T) {
455457

456458
}
457459

458-
func TestUnmarshalNullableRelationshipsNilValue(t *testing.T) {
459-
payload := &OnePayload{
460-
Data: &Node{
461-
ID: "10",
462-
Type: "with-nullables",
463-
Relationships: map[string]interface{}{
464-
"nullable_comment": nil,
465-
},
466-
},
467-
}
468-
469-
outBuf := bytes.NewBuffer(nil)
470-
json.NewEncoder(outBuf).Encode(payload)
471-
472-
out := new(WithNullableAttrs)
473-
474-
if err := UnmarshalPayload(outBuf, out); err != nil {
475-
t.Fatal(err)
476-
}
477-
478-
nullableCommentOpt := out.NullableComment
479-
if nullableCommentOpt.IsSpecified() || nullableCommentOpt.IsNull() {
480-
t.Fatal("Expected NullableComment to NOT be specified and NOT be explicit null")
481-
}
482-
}
483-
484460
func TestUnmarshalNullableRelationshipsNonExistentValue(t *testing.T) {
485461
payload := &OnePayload{
486462
Data: &Node{

response.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -415,9 +415,15 @@ func visitModelNode(model interface{}, included *map[string]*Node,
415415

416416
// Handle NullableRelationship[T]
417417
if strings.HasPrefix(fieldValue.Type().Name(), "NullableRelationship[") {
418+
418419
if fieldValue.MapIndex(reflect.ValueOf(false)).IsValid() {
420+
innerTypeIsSlice := fieldValue.MapIndex(reflect.ValueOf(false)).Type().Kind() == reflect.Slice
419421
// handle explicit null
420-
node.Relationships[args[1]] = json.RawMessage("null")
422+
if innerTypeIsSlice {
423+
node.Relationships[args[1]] = json.RawMessage("[]")
424+
} else {
425+
node.Relationships[args[1]] = json.RawMessage("{\"data\":null}")
426+
}
421427
continue
422428
} else if fieldValue.MapIndex(reflect.ValueOf(true)).IsValid() {
423429
// handle value

response_test.go

Lines changed: 5 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -994,17 +994,6 @@ func TestNullableRelationship(t *testing.T) {
994994
Body: "Hello World",
995995
}
996996

997-
comments := []*Comment{
998-
{
999-
ID: 6,
1000-
Body: "Hello World",
1001-
},
1002-
{
1003-
ID: 7,
1004-
Body: "Hello World",
1005-
},
1006-
}
1007-
1008997
for _, tc := range []struct {
1009998
desc string
1010999
input *WithNullableAttrs
@@ -1032,11 +1021,15 @@ func TestNullableRelationship(t *testing.T) {
10321021
NullableComment: NewNullNullableRelationship[*Comment](),
10331022
},
10341023
verification: func(root map[string]interface{}) error {
1035-
_, ok := root["data"].(map[string]interface{})["relationships"].(map[string]interface{})["nullable_comment"]
1024+
commentData, ok := root["data"].(map[string]interface{})["relationships"].(map[string]interface{})["nullable_comment"].(map[string]interface{})["data"]
10361025

10371026
if got, want := ok, true; got != want {
10381027
return fmt.Errorf("got %v, want %v", got, want)
10391028
}
1029+
1030+
if commentData != nil {
1031+
return fmt.Errorf("Expected nil data for nullable_comment but was '%v'", commentData)
1032+
}
10401033
return nil
10411034
},
10421035
},
@@ -1057,58 +1050,6 @@ func TestNullableRelationship(t *testing.T) {
10571050
return nil
10581051
},
10591052
},
1060-
{
1061-
desc: "nullable_comments_unspecified",
1062-
input: &WithNullableAttrs{
1063-
ID: 5,
1064-
NullableComments: nil,
1065-
},
1066-
verification: func(root map[string]interface{}) error {
1067-
_, ok := root["data"].(map[string]interface{})["relationships"]
1068-
1069-
if got, want := ok, false; got != want {
1070-
return fmt.Errorf("got %v, want %v", got, want)
1071-
}
1072-
return nil
1073-
},
1074-
},
1075-
{
1076-
desc: "nullable_comments_null",
1077-
input: &WithNullableAttrs{
1078-
ID: 5,
1079-
NullableComments: NewNullNullableRelationship[[]*Comment](),
1080-
},
1081-
verification: func(root map[string]interface{}) error {
1082-
_, ok := root["data"].(map[string]interface{})["relationships"].(map[string]interface{})["nullable_comments"]
1083-
1084-
if got, want := ok, true; got != want {
1085-
return fmt.Errorf("got %v, want %v", got, want)
1086-
}
1087-
return nil
1088-
},
1089-
},
1090-
{
1091-
desc: "nullable_comments_not_null",
1092-
input: &WithNullableAttrs{
1093-
ID: 5,
1094-
NullableComments: NewNullableRelationshipWithValue(comments),
1095-
},
1096-
verification: func(root map[string]interface{}) error {
1097-
relationships := root["data"].(map[string]interface{})["relationships"]
1098-
nullableComments := relationships.(map[string]interface{})["nullable_comments"].(map[string]interface{})["data"].([]interface{})
1099-
1100-
for i := 0; i < len(comments); i++ {
1101-
c := nullableComments[i].(map[string]interface{})
1102-
idStr := c["id"].(string)
1103-
id, _ := strconv.Atoi(idStr)
1104-
if got, want := id, comments[i].ID; got != want {
1105-
return fmt.Errorf("got %v, want %v", got, want)
1106-
}
1107-
}
1108-
1109-
return nil
1110-
},
1111-
},
11121053
} {
11131054
t.Run(tc.desc, func(t *testing.T) {
11141055
out := bytes.NewBuffer(nil)

0 commit comments

Comments
 (0)