From b112561a72a752dc2389e35df7148ce6d8f61a25 Mon Sep 17 00:00:00 2001 From: Sam Woodard Date: Fri, 10 Jul 2015 11:41:54 -0700 Subject: [PATCH] support unmarshal sideloaded records for two-way function --- request.go | 60 +++++++++++++++++++++----- request_test.go | 110 +++++++++++++++++++++++++++++++++++++++++++++--- response.go | 16 +++---- 3 files changed, 162 insertions(+), 24 deletions(-) diff --git a/request.go b/request.go index f45e09c..7c9aa48 100644 --- a/request.go +++ b/request.go @@ -18,10 +18,21 @@ func UnmarshalPayload(in io.Reader, model interface{}) error { return err } - return unmarshalNode(payload.Data, reflect.ValueOf(model)) + if payload.Included != nil { + includedMap := make(map[string]*Node) + for _, included := range payload.Included { + key := fmt.Sprintf("%s,%s", included.Type, included.Id) + includedMap[key] = included + } + + return unmarshalNode(payload.Data, reflect.ValueOf(model), &includedMap) + } else { + return unmarshalNode(payload.Data, reflect.ValueOf(model), nil) + } + } -func unmarshalNode(data *Node, model reflect.Value) error { +func unmarshalNode(data *Node, model reflect.Value, included *map[string]*Node) error { modelValue := model.Elem() modelType := model.Type().Elem() @@ -128,9 +139,8 @@ func unmarshalNode(data *Node, model reflect.Value) error { for _, r := range data { m := reflect.New(fieldValue.Type().Elem().Elem()) - h := r.(map[string]interface{}) - if err := unmarshalNode(mapToNode(h), m); err != nil { + if err := unmarshalMap(r, m, included); err != nil { er = err return false } @@ -141,9 +151,7 @@ func unmarshalNode(data *Node, model reflect.Value) error { fieldValue.Set(models) } else { m := reflect.New(fieldValue.Type().Elem()) - h := relationship["data"].(map[string]interface{}) - - if err := unmarshalNode(mapToNode(h), m); err != nil { + if err := unmarshalMap(relationship["data"], m, included); err != nil { er = err return false } @@ -166,18 +174,50 @@ func unmarshalNode(data *Node, model reflect.Value) error { return nil } -func mapToNode(m map[string]interface{}) *Node { +func unmarshalMap(nodeData interface{}, model reflect.Value, included *map[string]*Node) error { + h := nodeData.(map[string]interface{}) + node := fetchNode(h, included) + + if err := unmarshalNode(node, model, included); err != nil { + return err + } + + return nil +} + +func fetchNode(m map[string]interface{}, included *map[string]*Node) *Node { + var attributes map[string]interface{} + var relationships map[string]interface{} + + if included != nil { + key := fmt.Sprintf("%s,%s", m["type"].(string), m["id"].(string)) + node := (*included)[key] + + if node != nil { + attributes = node.Attributes + relationships = node.Relationships + } + } + + return mapToNode(m, attributes, relationships) +} + +func mapToNode(m map[string]interface{}, attributes map[string]interface{}, relationships map[string]interface{}) *Node { node := &Node{Type: m["type"].(string)} if m["id"] != nil { node.Id = m["id"].(string) } - if m["attributes"] != nil { + if m["attributes"] == nil { + node.Attributes = attributes + } else { node.Attributes = m["attributes"].(map[string]interface{}) } - if m["relationships"] != nil { + if m["relationships"] == nil { + node.Relationships = relationships + } else { node.Relationships = m["relationships"].(map[string]interface{}) } diff --git a/request_test.go b/request_test.go index c8d980d..cf7eef2 100644 --- a/request_test.go +++ b/request_test.go @@ -6,6 +6,7 @@ import ( "io" "regexp" "testing" + "time" ) type BadModel struct { @@ -45,11 +46,6 @@ func TestUnmarshalSetsAttrs(t *testing.T) { t.Fatal(err) } - //o := bytes.NewBuffer(nil) - //json.NewEncoder(o).Encode(out) - - //fmt.Printf("%s\n", o.Bytes()) - if out.CreatedAt.IsZero() { t.Fatalf("Did not parse time") } @@ -70,7 +66,7 @@ func TestUnmarshalRelationships(t *testing.T) { } if out.CurrentPost.Title != "Bas" || out.CurrentPost.Body != "Fuubar" { - t.Fatalf("Attributes where not set") + t.Fatalf("Attributes were not set") } if len(out.Posts) != 2 { @@ -97,6 +93,48 @@ func TestUnmarshalNestedRelationships(t *testing.T) { } } +func TestUnmarshalRelationshipsSideloaded(t *testing.T) { + payload := samplePayloadWithSideloaded() + out := new(Blog) + + if err := UnmarshalPayload(payload, out); err != nil { + t.Fatal(err) + } + + if out.CurrentPost == nil { + t.Fatalf("Current post was not materialized") + } + + if out.CurrentPost.Title != "Foo" || out.CurrentPost.Body != "Bar" { + t.Fatalf("Attributes were not set") + } + + if len(out.Posts) != 2 { + t.Fatalf("There should have been 2 posts") + } +} + +func TestUnmarshalNestedRelationshipsSideloaded(t *testing.T) { + payload := samplePayloadWithSideloaded() + out := new(Blog) + + if err := UnmarshalPayload(payload, out); err != nil { + t.Fatal(err) + } + + if out.CurrentPost == nil { + t.Fatalf("Current post was not materialized") + } + + if out.CurrentPost.Comments == nil { + t.Fatalf("Did not materialize nested records, comments") + } + + if len(out.CurrentPost.Comments) != 2 { + t.Fatalf("Wrong number of comments") + } +} + func unmarshalSamplePayload() (*Blog, error) { in := samplePayload() out := new(Blog) @@ -192,3 +230,63 @@ func samplePayloadWithId() io.Reader { return out } + +func samplePayloadWithSideloaded() io.Reader { + testModel := &Blog{ + Id: 5, + Title: "Title 1", + CreatedAt: time.Now(), + Posts: []*Post{ + &Post{ + Id: 1, + Title: "Foo", + Body: "Bar", + Comments: []*Comment{ + &Comment{ + Id: 1, + Body: "foo", + }, + &Comment{ + Id: 2, + Body: "bar", + }, + }, + }, + &Post{ + Id: 2, + Title: "Fuubar", + Body: "Bas", + Comments: []*Comment{ + &Comment{ + Id: 1, + Body: "foo", + }, + &Comment{ + Id: 3, + Body: "bas", + }, + }, + }, + }, + CurrentPost: &Post{ + Id: 1, + Title: "Foo", + Body: "Bar", + Comments: []*Comment{ + &Comment{ + Id: 1, + Body: "foo", + }, + &Comment{ + Id: 2, + Body: "bar", + }, + }, + }, + } + + out := bytes.NewBuffer(nil) + MarshalOnePayload(out, testModel) + + return out +} diff --git a/response.go b/response.go index 23f177a..8f3f3ff 100644 --- a/response.go +++ b/response.go @@ -163,10 +163,10 @@ func visitModelNode(model interface{}, sideload bool) (*Node, []*Node, error) { if err == nil { if sideload { + included = append(included, d...) shallowNodes := make([]*Node, 0) for _, node := range d { - included = append(included, node) - shallowNodes = append(shallowNodes, cloneAndRemoveAttributes(node)) + shallowNodes = append(shallowNodes, toShallowNode(node)) } node.Relationships[args[1]] = &RelationshipManyNode{Data: shallowNodes} @@ -182,7 +182,7 @@ func visitModelNode(model interface{}, sideload bool) (*Node, []*Node, error) { if err == nil { if sideload { included = append(included, relationship) - node.Relationships[args[1]] = &RelationshipOneNode{Data: cloneAndRemoveAttributes(relationship)} + node.Relationships[args[1]] = &RelationshipOneNode{Data: toShallowNode(relationship)} } else { node.Relationships[args[1]] = &RelationshipOneNode{Data: relationship} } @@ -208,11 +208,11 @@ func visitModelNode(model interface{}, sideload bool) (*Node, []*Node, error) { return node, included, nil } -func cloneAndRemoveAttributes(node *Node) *Node { - n := *node - n.Attributes = nil - - return &n +func toShallowNode(node *Node) *Node { + return &Node{ + Id: node.Id, + Type: node.Type, + } } func visitModelNodeRelationships(relationName string, models reflect.Value, sideload bool) (map[string]*RelationshipManyNode, error) {