forked from Mirrors/jsonapi
1001 lines
24 KiB
Go
1001 lines
24 KiB
Go
package jsonapi
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"reflect"
|
|
"testing"
|
|
)
|
|
|
|
func TestMergeNode(t *testing.T) {
|
|
parent := &Node{
|
|
Type: "Good",
|
|
ID: "99",
|
|
Attributes: map[string]interface{}{"fizz": "buzz"},
|
|
}
|
|
|
|
child := &Node{
|
|
Type: "Better",
|
|
ClientID: "1111",
|
|
Attributes: map[string]interface{}{"timbuk": 2},
|
|
}
|
|
|
|
expected := &Node{
|
|
Type: "Better",
|
|
ID: "99",
|
|
ClientID: "1111",
|
|
Attributes: map[string]interface{}{"fizz": "buzz", "timbuk": 2},
|
|
}
|
|
|
|
parent.merge(child)
|
|
|
|
if !reflect.DeepEqual(expected, parent) {
|
|
t.Errorf("Got %+v Expected %+v", parent, expected)
|
|
}
|
|
}
|
|
|
|
func TestIsEmbeddedStruct(t *testing.T) {
|
|
type foo struct{}
|
|
|
|
structType := reflect.TypeOf(foo{})
|
|
stringType := reflect.TypeOf("")
|
|
if structType.Kind() != reflect.Struct {
|
|
t.Fatal("structType.Kind() is not a struct.")
|
|
}
|
|
if stringType.Kind() != reflect.String {
|
|
t.Fatal("stringType.Kind() is not a string.")
|
|
}
|
|
|
|
type test struct {
|
|
scenario string
|
|
input reflect.StructField
|
|
expectedRes bool
|
|
}
|
|
|
|
tests := []test{
|
|
test{
|
|
scenario: "success",
|
|
input: reflect.StructField{Anonymous: true, Type: structType},
|
|
expectedRes: true,
|
|
},
|
|
test{
|
|
scenario: "wrong type",
|
|
input: reflect.StructField{Anonymous: true, Type: stringType},
|
|
expectedRes: false,
|
|
},
|
|
test{
|
|
scenario: "not embedded",
|
|
input: reflect.StructField{Type: structType},
|
|
expectedRes: false,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
res := isEmbeddedStruct(test.input)
|
|
if res != test.expectedRes {
|
|
t.Errorf(
|
|
"Scenario -> %s\nGot -> %v\nExpected -> %v\n",
|
|
test.scenario, res, test.expectedRes)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestShouldIgnoreField(t *testing.T) {
|
|
type test struct {
|
|
scenario string
|
|
input string
|
|
expectedRes bool
|
|
}
|
|
|
|
tests := []test{
|
|
test{
|
|
scenario: "opt-out",
|
|
input: annotationIgnore,
|
|
expectedRes: true,
|
|
},
|
|
test{
|
|
scenario: "no tag",
|
|
input: "",
|
|
expectedRes: false,
|
|
},
|
|
test{
|
|
scenario: "wrong tag",
|
|
input: "wrong,tag",
|
|
expectedRes: false,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
res := shouldIgnoreField(test.input)
|
|
if res != test.expectedRes {
|
|
t.Errorf(
|
|
"Scenario -> %s\nGot -> %v\nExpected -> %v\n",
|
|
test.scenario, res, test.expectedRes)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestIsValidEmbeddedStruct(t *testing.T) {
|
|
type foo struct{}
|
|
|
|
structType := reflect.TypeOf(foo{})
|
|
stringType := reflect.TypeOf("")
|
|
if structType.Kind() != reflect.Struct {
|
|
t.Fatal("structType.Kind() is not a struct.")
|
|
}
|
|
if stringType.Kind() != reflect.String {
|
|
t.Fatal("stringType.Kind() is not a string.")
|
|
}
|
|
|
|
type test struct {
|
|
scenario string
|
|
input reflect.StructField
|
|
expectedRes bool
|
|
}
|
|
|
|
tests := []test{
|
|
test{
|
|
scenario: "success",
|
|
input: reflect.StructField{Anonymous: true, Type: structType},
|
|
expectedRes: true,
|
|
},
|
|
test{
|
|
scenario: "opt-out",
|
|
input: reflect.StructField{
|
|
Anonymous: true,
|
|
Tag: "jsonapi:\"-\"",
|
|
Type: structType,
|
|
},
|
|
expectedRes: false,
|
|
},
|
|
test{
|
|
scenario: "wrong type",
|
|
input: reflect.StructField{Anonymous: true, Type: stringType},
|
|
expectedRes: false,
|
|
},
|
|
test{
|
|
scenario: "not embedded",
|
|
input: reflect.StructField{Type: structType},
|
|
expectedRes: false,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
res := (isEmbeddedStruct(test.input) &&
|
|
!shouldIgnoreField(test.input.Tag.Get(annotationJSONAPI)))
|
|
if res != test.expectedRes {
|
|
t.Errorf(
|
|
"Scenario -> %s\nGot -> %v\nExpected -> %v\n",
|
|
test.scenario, res, test.expectedRes)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestEmbeddedUnmarshalOrder tests the behavior of the marshaler/unmarshaler of
|
|
// embedded structs when a struct has an embedded struct w/ competing
|
|
// attributes, the top-level attributes take precedence it compares the behavior
|
|
// against the standard json package
|
|
func TestEmbeddedUnmarshalOrder(t *testing.T) {
|
|
type Bar struct {
|
|
Name int `jsonapi:"attr,Name"`
|
|
}
|
|
|
|
type Foo struct {
|
|
Bar
|
|
ID string `jsonapi:"primary,foos"`
|
|
Name string `jsonapi:"attr,Name"`
|
|
}
|
|
|
|
f := &Foo{
|
|
ID: "1",
|
|
Name: "foo",
|
|
Bar: Bar{
|
|
Name: 5,
|
|
},
|
|
}
|
|
|
|
// marshal f (Foo) using jsonapi marshaler
|
|
jsonAPIData := bytes.NewBuffer(nil)
|
|
if err := MarshalPayload(jsonAPIData, f); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// marshal f (Foo) using json marshaler
|
|
jsonData, err := json.Marshal(f)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// convert bytes to map[string]interface{} so that we can do a semantic JSON
|
|
// comparison
|
|
var jsonAPIVal, jsonVal map[string]interface{}
|
|
if err = json.Unmarshal(jsonAPIData.Bytes(), &jsonAPIVal); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err = json.Unmarshal(jsonData, &jsonVal); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// get to the jsonapi attribute map
|
|
jDataMap, ok := jsonAPIVal["data"].(map[string]interface{})
|
|
if !ok {
|
|
t.Fatal("Could not parse `data`")
|
|
}
|
|
jAttrMap, ok := jDataMap["attributes"].(map[string]interface{})
|
|
if !ok {
|
|
t.Fatal("Could not parse `attributes`")
|
|
}
|
|
|
|
// compare
|
|
if !reflect.DeepEqual(jAttrMap["Name"], jsonVal["Name"]) {
|
|
t.Errorf("Got\n%s\nExpected\n%s\n", jAttrMap["Name"], jsonVal["Name"])
|
|
}
|
|
}
|
|
|
|
// TestEmbeddedMarshalOrder tests the behavior of the marshaler/unmarshaler of
|
|
// embedded structs when a struct has an embedded struct w/ competing
|
|
// attributes, the top-level attributes take precedence it compares the
|
|
// behavior against the standard json package
|
|
func TestEmbeddedMarshalOrder(t *testing.T) {
|
|
type Bar struct {
|
|
Name int `jsonapi:"attr,Name"`
|
|
}
|
|
|
|
type Foo struct {
|
|
Bar
|
|
ID string `jsonapi:"primary,foos"`
|
|
Name string `jsonapi:"attr,Name"`
|
|
}
|
|
|
|
// get a jsonapi payload w/ Name attribute of an int type
|
|
payloadWithInt, err := json.Marshal(&OnePayload{
|
|
Data: &Node{
|
|
Type: "foos",
|
|
ID: "1",
|
|
Attributes: map[string]interface{}{
|
|
"Name": 5,
|
|
},
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// get a jsonapi payload w/ Name attribute of an string type
|
|
payloadWithString, err := json.Marshal(&OnePayload{
|
|
Data: &Node{
|
|
Type: "foos",
|
|
ID: "1",
|
|
Attributes: map[string]interface{}{
|
|
"Name": "foo",
|
|
},
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// unmarshal payloadWithInt to f (Foo) using jsonapi unmarshaler; expecting an error
|
|
f := &Foo{}
|
|
if err = UnmarshalPayload(bytes.NewReader(payloadWithInt), f); err == nil {
|
|
t.Errorf("expected an error: int value of 5 should attempt to map to Foo.Name (string) and error")
|
|
}
|
|
|
|
// unmarshal payloadWithString to f (Foo) using jsonapi unmarshaler; expecting no error
|
|
f = &Foo{}
|
|
if err = UnmarshalPayload(bytes.NewReader(payloadWithString), f); err != nil {
|
|
t.Error(err)
|
|
}
|
|
if f.Name != "foo" {
|
|
t.Errorf("Got\n%s\nExpected\n%s\n", "foo", f.Name)
|
|
}
|
|
|
|
// get a json payload w/ Name attribute of an int type
|
|
bWithInt, err := json.Marshal(map[string]interface{}{
|
|
"Name": 5,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// get a json payload w/ Name attribute of an string type
|
|
bWithString, err := json.Marshal(map[string]interface{}{
|
|
"Name": "foo",
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// unmarshal bWithInt to f (Foo) using json unmarshaler; expecting an error
|
|
f = &Foo{}
|
|
if err := json.Unmarshal(bWithInt, f); err == nil {
|
|
t.Errorf("expected an error: int value of 5 should attempt to map to Foo.Name (string) and error")
|
|
}
|
|
// unmarshal bWithString to f (Foo) using json unmarshaler; expecting no error
|
|
f = &Foo{}
|
|
if err := json.Unmarshal(bWithString, f); err != nil {
|
|
t.Error(err)
|
|
}
|
|
if f.Name != "foo" {
|
|
t.Errorf("Got\n%s\nExpected\n%s\n", "foo", f.Name)
|
|
}
|
|
}
|
|
|
|
func TestMarshalUnmarshalCompositeStruct(t *testing.T) {
|
|
type Thing struct {
|
|
ID int `jsonapi:"primary,things"`
|
|
Fizz string `jsonapi:"attr,fizz"`
|
|
Buzz int `jsonapi:"attr,buzz"`
|
|
}
|
|
|
|
type Model struct {
|
|
Thing
|
|
Foo string `jsonapi:"attr,foo"`
|
|
Bar string `jsonapi:"attr,bar"`
|
|
Bat string `jsonapi:"attr,bat"`
|
|
}
|
|
|
|
type test struct {
|
|
name string
|
|
payload *OnePayload
|
|
dst, expected interface{}
|
|
}
|
|
|
|
scenarios := []test{}
|
|
|
|
scenarios = append(scenarios, test{
|
|
name: "Model embeds Thing, models have no annotation overlaps",
|
|
dst: &Model{},
|
|
payload: &OnePayload{
|
|
Data: &Node{
|
|
Type: "things",
|
|
ID: "1",
|
|
Attributes: map[string]interface{}{
|
|
"bar": "barry",
|
|
"bat": "batty",
|
|
"buzz": 99,
|
|
"fizz": "fizzy",
|
|
"foo": "fooey",
|
|
},
|
|
},
|
|
},
|
|
expected: &Model{
|
|
Foo: "fooey",
|
|
Bar: "barry",
|
|
Bat: "batty",
|
|
Thing: Thing{
|
|
ID: 1,
|
|
Fizz: "fizzy",
|
|
Buzz: 99,
|
|
},
|
|
},
|
|
})
|
|
|
|
{
|
|
type Model struct {
|
|
Thing
|
|
Foo string `jsonapi:"attr,foo"`
|
|
Bar string `jsonapi:"attr,bar"`
|
|
Bat string `jsonapi:"attr,bat"`
|
|
Buzz int `jsonapi:"attr,buzz"` // overrides Thing.Buzz
|
|
}
|
|
|
|
scenarios = append(scenarios, test{
|
|
name: "Model embeds Thing, overlap Buzz attribute",
|
|
dst: &Model{},
|
|
payload: &OnePayload{
|
|
Data: &Node{
|
|
Type: "things",
|
|
ID: "1",
|
|
Attributes: map[string]interface{}{
|
|
"bar": "barry",
|
|
"bat": "batty",
|
|
"buzz": 99,
|
|
"fizz": "fizzy",
|
|
"foo": "fooey",
|
|
},
|
|
},
|
|
},
|
|
expected: &Model{
|
|
Foo: "fooey",
|
|
Bar: "barry",
|
|
Bat: "batty",
|
|
Buzz: 99,
|
|
Thing: Thing{
|
|
ID: 1,
|
|
Fizz: "fizzy",
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
{
|
|
type Model struct {
|
|
Thing
|
|
ModelID int `jsonapi:"primary,models"` //overrides Thing.ID due to primary annotation
|
|
Foo string `jsonapi:"attr,foo"`
|
|
Bar string `jsonapi:"attr,bar"`
|
|
Bat string `jsonapi:"attr,bat"`
|
|
Buzz int `jsonapi:"attr,buzz"` // overrides Thing.Buzz
|
|
}
|
|
|
|
scenarios = append(scenarios, test{
|
|
name: "Model embeds Thing, attribute, and primary annotation overlap",
|
|
dst: &Model{},
|
|
payload: &OnePayload{
|
|
Data: &Node{
|
|
Type: "models",
|
|
ID: "1",
|
|
Attributes: map[string]interface{}{
|
|
"bar": "barry",
|
|
"bat": "batty",
|
|
"buzz": 99,
|
|
"fizz": "fizzy",
|
|
"foo": "fooey",
|
|
},
|
|
},
|
|
},
|
|
expected: &Model{
|
|
ModelID: 1,
|
|
Foo: "fooey",
|
|
Bar: "barry",
|
|
Bat: "batty",
|
|
Buzz: 99,
|
|
Thing: Thing{
|
|
Fizz: "fizzy",
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
{
|
|
type Model struct {
|
|
Thing `jsonapi:"-"`
|
|
ModelID int `jsonapi:"primary,models"`
|
|
Foo string `jsonapi:"attr,foo"`
|
|
Bar string `jsonapi:"attr,bar"`
|
|
Bat string `jsonapi:"attr,bat"`
|
|
Buzz int `jsonapi:"attr,buzz"`
|
|
}
|
|
|
|
scenarios = append(scenarios, test{
|
|
name: "Model embeds Thing, but is annotated w/ ignore",
|
|
dst: &Model{},
|
|
payload: &OnePayload{
|
|
Data: &Node{
|
|
Type: "models",
|
|
ID: "1",
|
|
Attributes: map[string]interface{}{
|
|
"bar": "barry",
|
|
"bat": "batty",
|
|
"buzz": 99,
|
|
"foo": "fooey",
|
|
},
|
|
},
|
|
},
|
|
expected: &Model{
|
|
ModelID: 1,
|
|
Foo: "fooey",
|
|
Bar: "barry",
|
|
Bat: "batty",
|
|
Buzz: 99,
|
|
},
|
|
})
|
|
}
|
|
{
|
|
type Model struct {
|
|
*Thing
|
|
ModelID int `jsonapi:"primary,models"`
|
|
Foo string `jsonapi:"attr,foo"`
|
|
Bar string `jsonapi:"attr,bar"`
|
|
Bat string `jsonapi:"attr,bat"`
|
|
}
|
|
|
|
scenarios = append(scenarios, test{
|
|
name: "Model embeds pointer of Thing; Thing is initialized in advance",
|
|
dst: &Model{Thing: &Thing{}},
|
|
payload: &OnePayload{
|
|
Data: &Node{
|
|
Type: "models",
|
|
ID: "1",
|
|
Attributes: map[string]interface{}{
|
|
"bar": "barry",
|
|
"bat": "batty",
|
|
"foo": "fooey",
|
|
"buzz": 99,
|
|
"fizz": "fizzy",
|
|
},
|
|
},
|
|
},
|
|
expected: &Model{
|
|
Thing: &Thing{
|
|
Fizz: "fizzy",
|
|
Buzz: 99,
|
|
},
|
|
ModelID: 1,
|
|
Foo: "fooey",
|
|
Bar: "barry",
|
|
Bat: "batty",
|
|
},
|
|
})
|
|
}
|
|
{
|
|
type Model struct {
|
|
*Thing
|
|
ModelID int `jsonapi:"primary,models"`
|
|
Foo string `jsonapi:"attr,foo"`
|
|
Bar string `jsonapi:"attr,bar"`
|
|
Bat string `jsonapi:"attr,bat"`
|
|
}
|
|
|
|
scenarios = append(scenarios, test{
|
|
name: "Model embeds pointer of Thing; Thing is initialized w/ Unmarshal",
|
|
dst: &Model{},
|
|
payload: &OnePayload{
|
|
Data: &Node{
|
|
Type: "models",
|
|
ID: "1",
|
|
Attributes: map[string]interface{}{
|
|
"bar": "barry",
|
|
"bat": "batty",
|
|
"foo": "fooey",
|
|
"buzz": 99,
|
|
"fizz": "fizzy",
|
|
},
|
|
},
|
|
},
|
|
expected: &Model{
|
|
Thing: &Thing{
|
|
Fizz: "fizzy",
|
|
Buzz: 99,
|
|
},
|
|
ModelID: 1,
|
|
Foo: "fooey",
|
|
Bar: "barry",
|
|
Bat: "batty",
|
|
},
|
|
})
|
|
}
|
|
{
|
|
type Model struct {
|
|
*Thing
|
|
ModelID int `jsonapi:"primary,models"`
|
|
Foo string `jsonapi:"attr,foo"`
|
|
Bar string `jsonapi:"attr,bar"`
|
|
Bat string `jsonapi:"attr,bat"`
|
|
}
|
|
|
|
scenarios = append(scenarios, test{
|
|
name: "Model embeds pointer of Thing; jsonapi model doesn't assign anything to Thing; *Thing is nil",
|
|
dst: &Model{},
|
|
payload: &OnePayload{
|
|
Data: &Node{
|
|
Type: "models",
|
|
ID: "1",
|
|
Attributes: map[string]interface{}{
|
|
"bar": "barry",
|
|
"bat": "batty",
|
|
"foo": "fooey",
|
|
},
|
|
},
|
|
},
|
|
expected: &Model{
|
|
ModelID: 1,
|
|
Foo: "fooey",
|
|
Bar: "barry",
|
|
Bat: "batty",
|
|
},
|
|
})
|
|
}
|
|
|
|
{
|
|
type Model struct {
|
|
*Thing
|
|
ModelID int `jsonapi:"primary,models"`
|
|
Foo string `jsonapi:"attr,foo"`
|
|
Bar string `jsonapi:"attr,bar"`
|
|
Bat string `jsonapi:"attr,bat"`
|
|
}
|
|
|
|
scenarios = append(scenarios, test{
|
|
name: "Model embeds pointer of Thing; *Thing is nil",
|
|
dst: &Model{},
|
|
payload: &OnePayload{
|
|
Data: &Node{
|
|
Type: "models",
|
|
ID: "1",
|
|
Attributes: map[string]interface{}{
|
|
"bar": "barry",
|
|
"bat": "batty",
|
|
"foo": "fooey",
|
|
},
|
|
},
|
|
},
|
|
expected: &Model{
|
|
ModelID: 1,
|
|
Foo: "fooey",
|
|
Bar: "barry",
|
|
Bat: "batty",
|
|
},
|
|
})
|
|
}
|
|
for _, scenario := range scenarios {
|
|
t.Logf("running scenario: %s\n", scenario.name)
|
|
|
|
// get the expected model and marshal to jsonapi
|
|
buf := bytes.NewBuffer(nil)
|
|
if err := MarshalPayload(buf, scenario.expected); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// get the node model representation and marshal to jsonapi
|
|
payload, err := json.Marshal(scenario.payload)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// assert that we're starting w/ the same payload
|
|
isJSONEqual, err := isJSONEqual(payload, buf.Bytes())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !isJSONEqual {
|
|
t.Errorf("Got\n%s\nExpected\n%s\n", buf.Bytes(), payload)
|
|
}
|
|
|
|
// run jsonapi unmarshal
|
|
if err := UnmarshalPayload(bytes.NewReader(payload), scenario.dst); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// assert decoded and expected models are equal
|
|
if !reflect.DeepEqual(scenario.expected, scenario.dst) {
|
|
t.Errorf("Got\n%#v\nExpected\n%#v\n", scenario.dst, scenario.expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMarshal_duplicatePrimaryAnnotationFromEmbeddedStructs(t *testing.T) {
|
|
type Outer struct {
|
|
ID string `jsonapi:"primary,outer"`
|
|
Comment
|
|
*Post
|
|
}
|
|
|
|
o := Outer{
|
|
ID: "outer",
|
|
Comment: Comment{ID: 1},
|
|
Post: &Post{ID: 5},
|
|
}
|
|
var payloadData map[string]interface{}
|
|
|
|
// Test the standard libraries JSON handling of dup (ID) fields - it uses
|
|
// the Outer's ID
|
|
jsonData, err := json.Marshal(o)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := json.Unmarshal(jsonData, &payloadData); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if e, a := o.ID, payloadData["ID"]; e != a {
|
|
t.Fatalf("Was expecting ID to be %v, got %v", e, a)
|
|
}
|
|
|
|
// Test the JSONAPI lib handling of dup (ID) fields
|
|
jsonAPIData := new(bytes.Buffer)
|
|
if err := MarshalPayload(jsonAPIData, &o); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := json.Unmarshal(jsonAPIData.Bytes(), &payloadData); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
data := payloadData["data"].(map[string]interface{})
|
|
id := data["id"].(string)
|
|
if e, a := o.ID, id; e != a {
|
|
t.Fatalf("Was expecting ID to be %v, got %v", e, a)
|
|
}
|
|
}
|
|
|
|
func TestMarshal_duplicateAttributeAnnotationFromEmbeddedStructs(t *testing.T) {
|
|
type Foo struct {
|
|
Count uint `json:"count" jsonapi:"attr,count"`
|
|
}
|
|
type Bar struct {
|
|
Count uint `json:"count" jsonapi:"attr,count"`
|
|
}
|
|
type Outer struct {
|
|
ID uint `json:"id" jsonapi:"primary,outer"`
|
|
Foo
|
|
Bar
|
|
}
|
|
o := Outer{
|
|
ID: 1,
|
|
Foo: Foo{Count: 1},
|
|
Bar: Bar{Count: 2},
|
|
}
|
|
|
|
var payloadData map[string]interface{}
|
|
|
|
// The standard JSON lib will not serialize either embedded struct's fields if
|
|
// a duplicate is encountered
|
|
jsonData, err := json.Marshal(o)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := json.Unmarshal(jsonData, &payloadData); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, found := payloadData["count"]; found {
|
|
t.Fatalf("Was not expecting to find the `count` key in the JSON")
|
|
}
|
|
|
|
// Test the JSONAPI lib handling of dup (attr) fields
|
|
jsonAPIData := new(bytes.Buffer)
|
|
if err := MarshalPayload(jsonAPIData, &o); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := json.Unmarshal(jsonAPIData.Bytes(), &payloadData); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
data := payloadData["data"].(map[string]interface{})
|
|
if _, found := data["attributes"]; found {
|
|
t.Fatal("Was not expecting to find any `attributes` in the JSON API")
|
|
}
|
|
}
|
|
|
|
func TestMarshal_duplicateAttributeAnnotationFromEmbeddedStructsPtrs(t *testing.T) {
|
|
type Foo struct {
|
|
Count uint `json:"count" jsonapi:"attr,count"`
|
|
}
|
|
type Bar struct {
|
|
Count uint `json:"count" jsonapi:"attr,count"`
|
|
}
|
|
type Outer struct {
|
|
ID uint `json:"id" jsonapi:"primary,outer"`
|
|
*Foo
|
|
*Bar
|
|
}
|
|
o := Outer{
|
|
ID: 1,
|
|
Foo: &Foo{Count: 1},
|
|
Bar: &Bar{Count: 2},
|
|
}
|
|
|
|
var payloadData map[string]interface{}
|
|
|
|
// The standard JSON lib will not serialize either embedded struct's fields if
|
|
// a duplicate is encountered
|
|
jsonData, err := json.Marshal(o)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := json.Unmarshal(jsonData, &payloadData); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, found := payloadData["count"]; found {
|
|
t.Fatalf("Was not expecting to find the `count` key in the JSON")
|
|
}
|
|
|
|
// Test the JSONAPI lib handling of dup (attr) fields
|
|
jsonAPIData := new(bytes.Buffer)
|
|
if err := MarshalPayload(jsonAPIData, &o); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := json.Unmarshal(jsonAPIData.Bytes(), &payloadData); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
data := payloadData["data"].(map[string]interface{})
|
|
if _, found := data["attributes"]; found {
|
|
t.Fatal("Was not expecting to find any `attributes` in the JSON API")
|
|
}
|
|
}
|
|
|
|
func TestMarshal_duplicateAttributeAnnotationFromEmbeddedStructsMixed(t *testing.T) {
|
|
type Foo struct {
|
|
Count uint `json:"count" jsonapi:"attr,count"`
|
|
}
|
|
type Bar struct {
|
|
Count uint `json:"count" jsonapi:"attr,count"`
|
|
}
|
|
type Outer struct {
|
|
ID uint `json:"id" jsonapi:"primary,outer"`
|
|
*Foo
|
|
Bar
|
|
}
|
|
o := Outer{
|
|
ID: 1,
|
|
Foo: &Foo{Count: 1},
|
|
Bar: Bar{Count: 2},
|
|
}
|
|
|
|
var payloadData map[string]interface{}
|
|
|
|
// The standard JSON lib will not serialize either embedded struct's fields if
|
|
// a duplicate is encountered
|
|
jsonData, err := json.Marshal(o)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := json.Unmarshal(jsonData, &payloadData); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, found := payloadData["count"]; found {
|
|
t.Fatalf("Was not expecting to find the `count` key in the JSON")
|
|
}
|
|
|
|
// Test the JSONAPI lib handling of dup (attr) fields; it should serialize
|
|
// neither
|
|
jsonAPIData := new(bytes.Buffer)
|
|
if err := MarshalPayload(jsonAPIData, &o); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := json.Unmarshal(jsonAPIData.Bytes(), &payloadData); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
data := payloadData["data"].(map[string]interface{})
|
|
if _, found := data["attributes"]; found {
|
|
t.Fatal("Was not expecting to find any `attributes` in the JSON API")
|
|
}
|
|
}
|
|
|
|
func TestMarshal_duplicateFieldFromEmbeddedStructs_serializationNameDiffers(t *testing.T) {
|
|
type Foo struct {
|
|
Count uint `json:"foo-count" jsonapi:"attr,foo-count"`
|
|
}
|
|
type Bar struct {
|
|
Count uint `json:"bar-count" jsonapi:"attr,bar-count"`
|
|
}
|
|
type Outer struct {
|
|
ID uint `json:"id" jsonapi:"primary,outer"`
|
|
Foo
|
|
Bar
|
|
}
|
|
o := Outer{
|
|
ID: 1,
|
|
Foo: Foo{Count: 1},
|
|
Bar: Bar{Count: 2},
|
|
}
|
|
|
|
var payloadData map[string]interface{}
|
|
|
|
// The standard JSON lib will both the fields since their annotation name
|
|
// differs
|
|
jsonData, err := json.Marshal(o)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := json.Unmarshal(jsonData, &payloadData); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
fooJSON, fooFound := payloadData["foo-count"]
|
|
if !fooFound {
|
|
t.Fatal("Was expecting to find the `foo-count` key in the JSON")
|
|
}
|
|
if e, a := o.Foo.Count, fooJSON.(float64); e != uint(a) {
|
|
t.Fatalf("Was expecting the `foo-count` value to be %v, got %v", e, a)
|
|
}
|
|
barJSON, barFound := payloadData["bar-count"]
|
|
if !barFound {
|
|
t.Fatal("Was expecting to find the `bar-count` key in the JSON")
|
|
}
|
|
if e, a := o.Bar.Count, barJSON.(float64); e != uint(a) {
|
|
t.Fatalf("Was expecting the `bar-count` value to be %v, got %v", e, a)
|
|
}
|
|
|
|
// Test the JSONAPI lib handling; it should serialize both
|
|
jsonAPIData := new(bytes.Buffer)
|
|
if err := MarshalPayload(jsonAPIData, &o); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := json.Unmarshal(jsonAPIData.Bytes(), &payloadData); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
data := payloadData["data"].(map[string]interface{})
|
|
attributes := data["attributes"].(map[string]interface{})
|
|
fooJSONAPI, fooFound := attributes["foo-count"]
|
|
if !fooFound {
|
|
t.Fatal("Was expecting to find the `foo-count` attribute in the JSON API")
|
|
}
|
|
if e, a := o.Foo.Count, fooJSONAPI.(float64); e != uint(e) {
|
|
t.Fatalf("Was expecting the `foo-count` attrobute to be %v, got %v", e, a)
|
|
}
|
|
barJSONAPI, barFound := attributes["bar-count"]
|
|
if !barFound {
|
|
t.Fatal("Was expecting to find the `bar-count` attribute in the JSON API")
|
|
}
|
|
if e, a := o.Bar.Count, barJSONAPI.(float64); e != uint(e) {
|
|
t.Fatalf("Was expecting the `bar-count` attrobute to be %v, got %v", e, a)
|
|
}
|
|
}
|
|
|
|
func TestMarshal_embeddedStruct_providesDuplicateAttr(t *testing.T) {
|
|
type Foo struct {
|
|
Number uint `json:"count" jsonapi:"attr,count"`
|
|
}
|
|
type Outer struct {
|
|
Foo
|
|
ID uint `json:"id" jsonapi:"primary,outer"`
|
|
Count uint `json:"count" jsonapi:"attr,count"`
|
|
}
|
|
o := Outer{
|
|
ID: 1,
|
|
Count: 1,
|
|
Foo: Foo{Number: 5},
|
|
}
|
|
var payloadData map[string]interface{}
|
|
|
|
// The standard JSON lib will take the count annotated field from the Outer
|
|
jsonData, err := json.Marshal(o)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := json.Unmarshal(jsonData, &payloadData); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if e, a := o.Count, payloadData["count"].(float64); e != uint(a) {
|
|
t.Fatalf("Was expecting a JSON `count` of %v, got %v", e, a)
|
|
}
|
|
|
|
// In JSON API the handling should be that the Outer annotated count field is
|
|
// serialized into `attributes`
|
|
jsonAPIData := new(bytes.Buffer)
|
|
if err := MarshalPayload(jsonAPIData, &o); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := json.Unmarshal(jsonAPIData.Bytes(), &payloadData); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
data := payloadData["data"].(map[string]interface{})
|
|
attributes := data["attributes"].(map[string]interface{})
|
|
if e, a := o.Count, attributes["count"].(float64); e != uint(a) {
|
|
t.Fatalf("Was expecting a JSON API `count` attribute of %v, got %v", e, a)
|
|
}
|
|
}
|
|
|
|
func TestMarshal_embeddedStructPtr_providesDuplicateAttr(t *testing.T) {
|
|
type Foo struct {
|
|
Number uint `json:"count" jsonapi:"attr,count"`
|
|
}
|
|
type Outer struct {
|
|
*Foo
|
|
ID uint `json:"id" jsonapi:"primary,outer"`
|
|
Count uint `json:"count" jsonapi:"attr,count"`
|
|
}
|
|
o := Outer{
|
|
ID: 1,
|
|
Count: 1,
|
|
Foo: &Foo{Number: 5},
|
|
}
|
|
var payloadData map[string]interface{}
|
|
|
|
// The standard JSON lib will take the count annotated field from the Outer
|
|
jsonData, err := json.Marshal(o)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := json.Unmarshal(jsonData, &payloadData); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if e, a := o.Count, payloadData["count"].(float64); e != uint(a) {
|
|
t.Fatalf("Was expecting a JSON `count` of %v, got %v", e, a)
|
|
}
|
|
|
|
// In JSON API the handling should be that the Outer annotated count field is
|
|
// serialized into `attributes`
|
|
jsonAPIData := new(bytes.Buffer)
|
|
if err := MarshalPayload(jsonAPIData, &o); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := json.Unmarshal(jsonAPIData.Bytes(), &payloadData); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
data := payloadData["data"].(map[string]interface{})
|
|
attributes := data["attributes"].(map[string]interface{})
|
|
if e, a := o.Count, attributes["count"].(float64); e != uint(a) {
|
|
t.Fatalf("Was expecting a JSON API `count` attribute of %v, got %v", e, a)
|
|
}
|
|
}
|
|
|
|
// TODO: test permutation of relations with embedded structs
|