github.com/seeker-insurance/kit@v0.0.13/jsonapi/embeded_structs_test.go (about)

     1  package jsonapi
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"reflect"
     7  	"testing"
     8  )
     9  
    10  func TestMergeNode(t *testing.T) {
    11  	parent := &Node{
    12  		Type:       "Good",
    13  		ID:         "99",
    14  		Attributes: map[string]interface{}{"fizz": "buzz"},
    15  	}
    16  
    17  	child := &Node{
    18  		Type:       "Better",
    19  		ClientID:   "1111",
    20  		Attributes: map[string]interface{}{"timbuk": 2},
    21  	}
    22  
    23  	expected := &Node{
    24  		Type:       "Better",
    25  		ID:         "99",
    26  		ClientID:   "1111",
    27  		Attributes: map[string]interface{}{"fizz": "buzz", "timbuk": 2},
    28  	}
    29  
    30  	parent.merge(child)
    31  
    32  	if !reflect.DeepEqual(expected, parent) {
    33  		t.Errorf("Got %+v Expected %+v", parent, expected)
    34  	}
    35  }
    36  
    37  func TestIsEmbeddedStruct(t *testing.T) {
    38  	type foo struct{}
    39  
    40  	structType := reflect.TypeOf(foo{})
    41  	stringType := reflect.TypeOf("")
    42  	if structType.Kind() != reflect.Struct {
    43  		t.Fatal("structType.Kind() is not a struct.")
    44  	}
    45  	if stringType.Kind() != reflect.String {
    46  		t.Fatal("stringType.Kind() is not a string.")
    47  	}
    48  
    49  	type test struct {
    50  		scenario    string
    51  		input       reflect.StructField
    52  		expectedRes bool
    53  	}
    54  
    55  	tests := []test{
    56  		test{
    57  			scenario:    "success",
    58  			input:       reflect.StructField{Anonymous: true, Type: structType},
    59  			expectedRes: true,
    60  		},
    61  		test{
    62  			scenario:    "wrong type",
    63  			input:       reflect.StructField{Anonymous: true, Type: stringType},
    64  			expectedRes: false,
    65  		},
    66  		test{
    67  			scenario:    "not embedded",
    68  			input:       reflect.StructField{Type: structType},
    69  			expectedRes: false,
    70  		},
    71  	}
    72  
    73  	for _, test := range tests {
    74  		res := isEmbeddedStruct(test.input)
    75  		if res != test.expectedRes {
    76  			t.Errorf(
    77  				"Scenario -> %s\nGot -> %v\nExpected -> %v\n",
    78  				test.scenario, res, test.expectedRes)
    79  		}
    80  	}
    81  }
    82  
    83  func TestShouldIgnoreField(t *testing.T) {
    84  	type test struct {
    85  		scenario    string
    86  		input       string
    87  		expectedRes bool
    88  	}
    89  
    90  	tests := []test{
    91  		test{
    92  			scenario:    "opt-out",
    93  			input:       annotationIgnore,
    94  			expectedRes: true,
    95  		},
    96  		test{
    97  			scenario:    "no tag",
    98  			input:       "",
    99  			expectedRes: false,
   100  		},
   101  		test{
   102  			scenario:    "wrong tag",
   103  			input:       "wrong,tag",
   104  			expectedRes: false,
   105  		},
   106  	}
   107  
   108  	for _, test := range tests {
   109  		res := shouldIgnoreField(test.input)
   110  		if res != test.expectedRes {
   111  			t.Errorf(
   112  				"Scenario -> %s\nGot -> %v\nExpected -> %v\n",
   113  				test.scenario, res, test.expectedRes)
   114  		}
   115  	}
   116  }
   117  
   118  func TestIsValidEmbeddedStruct(t *testing.T) {
   119  	type foo struct{}
   120  
   121  	structType := reflect.TypeOf(foo{})
   122  	stringType := reflect.TypeOf("")
   123  	if structType.Kind() != reflect.Struct {
   124  		t.Fatal("structType.Kind() is not a struct.")
   125  	}
   126  	if stringType.Kind() != reflect.String {
   127  		t.Fatal("stringType.Kind() is not a string.")
   128  	}
   129  
   130  	type test struct {
   131  		scenario    string
   132  		input       reflect.StructField
   133  		expectedRes bool
   134  	}
   135  
   136  	tests := []test{
   137  		test{
   138  			scenario:    "success",
   139  			input:       reflect.StructField{Anonymous: true, Type: structType},
   140  			expectedRes: true,
   141  		},
   142  		test{
   143  			scenario: "opt-out",
   144  			input: reflect.StructField{
   145  				Anonymous: true,
   146  				Tag:       "jsonapi:\"-\"",
   147  				Type:      structType,
   148  			},
   149  			expectedRes: false,
   150  		},
   151  		test{
   152  			scenario:    "wrong type",
   153  			input:       reflect.StructField{Anonymous: true, Type: stringType},
   154  			expectedRes: false,
   155  		},
   156  		test{
   157  			scenario:    "not embedded",
   158  			input:       reflect.StructField{Type: structType},
   159  			expectedRes: false,
   160  		},
   161  	}
   162  
   163  	for _, test := range tests {
   164  		res := (isEmbeddedStruct(test.input) &&
   165  			!shouldIgnoreField(test.input.Tag.Get(annotationJSONAPI)))
   166  		if res != test.expectedRes {
   167  			t.Errorf(
   168  				"Scenario -> %s\nGot -> %v\nExpected -> %v\n",
   169  				test.scenario, res, test.expectedRes)
   170  		}
   171  	}
   172  }
   173  
   174  // TestEmbeddedUnmarshalOrder tests the behavior of the marshaler/unmarshaler of
   175  // embedded structs when a struct has an embedded struct w/ competing
   176  // attributes, the top-level attributes take precedence it compares the behavior
   177  // against the standard json package
   178  func TestEmbeddedUnmarshalOrder(t *testing.T) {
   179  	type Bar struct {
   180  		Name int `jsonapi:"attr,Name"`
   181  	}
   182  
   183  	type Foo struct {
   184  		Bar
   185  		ID   string `jsonapi:"primary,foos"`
   186  		Name string `jsonapi:"attr,Name"`
   187  	}
   188  
   189  	f := &Foo{
   190  		ID:   "1",
   191  		Name: "foo",
   192  		Bar: Bar{
   193  			Name: 5,
   194  		},
   195  	}
   196  
   197  	// marshal f (Foo) using jsonapi marshaler
   198  	jsonAPIData := bytes.NewBuffer(nil)
   199  	if err := MarshalPayload(jsonAPIData, f); err != nil {
   200  		t.Fatal(err)
   201  	}
   202  
   203  	// marshal f (Foo) using json marshaler
   204  	jsonData, err := json.Marshal(f)
   205  	if err != nil {
   206  		t.Fatal(err)
   207  	}
   208  
   209  	// convert bytes to map[string]interface{} so that we can do a semantic JSON
   210  	// comparison
   211  	var jsonAPIVal, jsonVal map[string]interface{}
   212  	if err = json.Unmarshal(jsonAPIData.Bytes(), &jsonAPIVal); err != nil {
   213  		t.Fatal(err)
   214  	}
   215  	if err = json.Unmarshal(jsonData, &jsonVal); err != nil {
   216  		t.Fatal(err)
   217  	}
   218  
   219  	// get to the jsonapi attribute map
   220  	jDataMap, ok := jsonAPIVal["data"].(map[string]interface{})
   221  	if !ok {
   222  		t.Fatal("Could not parse `data`")
   223  	}
   224  	jAttrMap, ok := jDataMap["attributes"].(map[string]interface{})
   225  	if !ok {
   226  		t.Fatal("Could not parse `attributes`")
   227  	}
   228  
   229  	// compare
   230  	if !reflect.DeepEqual(jAttrMap["Name"], jsonVal["Name"]) {
   231  		t.Errorf("Got\n%s\nExpected\n%s\n", jAttrMap["Name"], jsonVal["Name"])
   232  	}
   233  }
   234  
   235  // TestEmbeddedMarshalOrder tests the behavior of the marshaler/unmarshaler of
   236  // embedded structs when a struct has an embedded struct w/ competing
   237  // attributes, the top-level attributes take precedence it compares the
   238  // behavior against the standard json package
   239  func TestEmbeddedMarshalOrder(t *testing.T) {
   240  	type Bar struct {
   241  		Name int `jsonapi:"attr,Name"`
   242  	}
   243  
   244  	type Foo struct {
   245  		Bar
   246  		ID   string `jsonapi:"primary,foos"`
   247  		Name string `jsonapi:"attr,Name"`
   248  	}
   249  
   250  	// get a jsonapi payload w/ Name attribute of an int type
   251  	payloadWithInt, err := json.Marshal(&OnePayload{
   252  		Data: &Node{
   253  			Type: "foos",
   254  			ID:   "1",
   255  			Attributes: map[string]interface{}{
   256  				"Name": 5,
   257  			},
   258  		},
   259  	})
   260  	if err != nil {
   261  		t.Fatal(err)
   262  	}
   263  
   264  	// get a jsonapi payload w/ Name attribute of an string type
   265  	payloadWithString, err := json.Marshal(&OnePayload{
   266  		Data: &Node{
   267  			Type: "foos",
   268  			ID:   "1",
   269  			Attributes: map[string]interface{}{
   270  				"Name": "foo",
   271  			},
   272  		},
   273  	})
   274  	if err != nil {
   275  		t.Fatal(err)
   276  	}
   277  
   278  	// unmarshal payloadWithInt to f (Foo) using jsonapi unmarshaler; expecting an error
   279  	f := &Foo{}
   280  	if err = UnmarshalPayload(bytes.NewReader(payloadWithInt), f); err == nil {
   281  		t.Errorf("expected an error: int value of 5 should attempt to map to Foo.Name (string) and error")
   282  	}
   283  
   284  	// unmarshal payloadWithString to f (Foo) using jsonapi unmarshaler; expecting no error
   285  	f = &Foo{}
   286  	if err = UnmarshalPayload(bytes.NewReader(payloadWithString), f); err != nil {
   287  		t.Error(err)
   288  	}
   289  	if f.Name != "foo" {
   290  		t.Errorf("Got\n%s\nExpected\n%s\n", "foo", f.Name)
   291  	}
   292  
   293  	// get a json payload w/ Name attribute of an int type
   294  	bWithInt, err := json.Marshal(map[string]interface{}{
   295  		"Name": 5,
   296  	})
   297  	if err != nil {
   298  		t.Fatal(err)
   299  	}
   300  
   301  	// get a json payload w/ Name attribute of an string type
   302  	bWithString, err := json.Marshal(map[string]interface{}{
   303  		"Name": "foo",
   304  	})
   305  	if err != nil {
   306  		t.Fatal(err)
   307  	}
   308  
   309  	// unmarshal bWithInt to f (Foo) using json unmarshaler; expecting an error
   310  	f = &Foo{}
   311  	if err := json.Unmarshal(bWithInt, f); err == nil {
   312  		t.Errorf("expected an error: int value of 5 should attempt to map to Foo.Name (string) and error")
   313  	}
   314  	// unmarshal bWithString to f (Foo) using json unmarshaler; expecting no error
   315  	f = &Foo{}
   316  	if err := json.Unmarshal(bWithString, f); err != nil {
   317  		t.Error(err)
   318  	}
   319  	if f.Name != "foo" {
   320  		t.Errorf("Got\n%s\nExpected\n%s\n", "foo", f.Name)
   321  	}
   322  }
   323  
   324  func TestMarshalUnmarshalCompositeStruct(t *testing.T) {
   325  	type Thing struct {
   326  		ID   int    `jsonapi:"primary,things"`
   327  		Fizz string `jsonapi:"attr,fizz"`
   328  		Buzz int    `jsonapi:"attr,buzz"`
   329  	}
   330  
   331  	type Model struct {
   332  		Thing
   333  		Foo string `jsonapi:"attr,foo"`
   334  		Bar string `jsonapi:"attr,bar"`
   335  		Bat string `jsonapi:"attr,bat"`
   336  	}
   337  
   338  	type test struct {
   339  		name          string
   340  		payload       *OnePayload
   341  		dst, expected interface{}
   342  	}
   343  
   344  	scenarios := []test{}
   345  
   346  	scenarios = append(scenarios, test{
   347  		name: "Model embeds Thing, models have no annotation overlaps",
   348  		dst:  &Model{},
   349  		payload: &OnePayload{
   350  			Data: &Node{
   351  				Type: "things",
   352  				ID:   "1",
   353  				Attributes: map[string]interface{}{
   354  					"bar":  "barry",
   355  					"bat":  "batty",
   356  					"buzz": 99,
   357  					"fizz": "fizzy",
   358  					"foo":  "fooey",
   359  				},
   360  			},
   361  		},
   362  		expected: &Model{
   363  			Foo: "fooey",
   364  			Bar: "barry",
   365  			Bat: "batty",
   366  			Thing: Thing{
   367  				ID:   1,
   368  				Fizz: "fizzy",
   369  				Buzz: 99,
   370  			},
   371  		},
   372  	})
   373  
   374  	{
   375  		type Model struct {
   376  			Thing
   377  			Foo  string `jsonapi:"attr,foo"`
   378  			Bar  string `jsonapi:"attr,bar"`
   379  			Bat  string `jsonapi:"attr,bat"`
   380  			Buzz int    `jsonapi:"attr,buzz"` // overrides Thing.Buzz
   381  		}
   382  
   383  		scenarios = append(scenarios, test{
   384  			name: "Model embeds Thing, overlap Buzz attribute",
   385  			dst:  &Model{},
   386  			payload: &OnePayload{
   387  				Data: &Node{
   388  					Type: "things",
   389  					ID:   "1",
   390  					Attributes: map[string]interface{}{
   391  						"bar":  "barry",
   392  						"bat":  "batty",
   393  						"buzz": 99,
   394  						"fizz": "fizzy",
   395  						"foo":  "fooey",
   396  					},
   397  				},
   398  			},
   399  			expected: &Model{
   400  				Foo:  "fooey",
   401  				Bar:  "barry",
   402  				Bat:  "batty",
   403  				Buzz: 99,
   404  				Thing: Thing{
   405  					ID:   1,
   406  					Fizz: "fizzy",
   407  				},
   408  			},
   409  		})
   410  	}
   411  
   412  	{
   413  		type Model struct {
   414  			Thing
   415  			ModelID int    `jsonapi:"primary,models"` //overrides Thing.ID due to primary annotation
   416  			Foo     string `jsonapi:"attr,foo"`
   417  			Bar     string `jsonapi:"attr,bar"`
   418  			Bat     string `jsonapi:"attr,bat"`
   419  			Buzz    int    `jsonapi:"attr,buzz"` // overrides Thing.Buzz
   420  		}
   421  
   422  		scenarios = append(scenarios, test{
   423  			name: "Model embeds Thing, attribute, and primary annotation overlap",
   424  			dst:  &Model{},
   425  			payload: &OnePayload{
   426  				Data: &Node{
   427  					Type: "models",
   428  					ID:   "1",
   429  					Attributes: map[string]interface{}{
   430  						"bar":  "barry",
   431  						"bat":  "batty",
   432  						"buzz": 99,
   433  						"fizz": "fizzy",
   434  						"foo":  "fooey",
   435  					},
   436  				},
   437  			},
   438  			expected: &Model{
   439  				ModelID: 1,
   440  				Foo:     "fooey",
   441  				Bar:     "barry",
   442  				Bat:     "batty",
   443  				Buzz:    99,
   444  				Thing: Thing{
   445  					Fizz: "fizzy",
   446  				},
   447  			},
   448  		})
   449  	}
   450  
   451  	{
   452  		type Model struct {
   453  			Thing   `jsonapi:"-"`
   454  			ModelID int    `jsonapi:"primary,models"`
   455  			Foo     string `jsonapi:"attr,foo"`
   456  			Bar     string `jsonapi:"attr,bar"`
   457  			Bat     string `jsonapi:"attr,bat"`
   458  			Buzz    int    `jsonapi:"attr,buzz"`
   459  		}
   460  
   461  		scenarios = append(scenarios, test{
   462  			name: "Model embeds Thing, but is annotated w/ ignore",
   463  			dst:  &Model{},
   464  			payload: &OnePayload{
   465  				Data: &Node{
   466  					Type: "models",
   467  					ID:   "1",
   468  					Attributes: map[string]interface{}{
   469  						"bar":  "barry",
   470  						"bat":  "batty",
   471  						"buzz": 99,
   472  						"foo":  "fooey",
   473  					},
   474  				},
   475  			},
   476  			expected: &Model{
   477  				ModelID: 1,
   478  				Foo:     "fooey",
   479  				Bar:     "barry",
   480  				Bat:     "batty",
   481  				Buzz:    99,
   482  			},
   483  		})
   484  	}
   485  	{
   486  		type Model struct {
   487  			*Thing
   488  			ModelID int    `jsonapi:"primary,models"`
   489  			Foo     string `jsonapi:"attr,foo"`
   490  			Bar     string `jsonapi:"attr,bar"`
   491  			Bat     string `jsonapi:"attr,bat"`
   492  		}
   493  
   494  		scenarios = append(scenarios, test{
   495  			name: "Model embeds pointer of Thing; Thing is initialized in advance",
   496  			dst:  &Model{Thing: &Thing{}},
   497  			payload: &OnePayload{
   498  				Data: &Node{
   499  					Type: "models",
   500  					ID:   "1",
   501  					Attributes: map[string]interface{}{
   502  						"bar":  "barry",
   503  						"bat":  "batty",
   504  						"foo":  "fooey",
   505  						"buzz": 99,
   506  						"fizz": "fizzy",
   507  					},
   508  				},
   509  			},
   510  			expected: &Model{
   511  				Thing: &Thing{
   512  					Fizz: "fizzy",
   513  					Buzz: 99,
   514  				},
   515  				ModelID: 1,
   516  				Foo:     "fooey",
   517  				Bar:     "barry",
   518  				Bat:     "batty",
   519  			},
   520  		})
   521  	}
   522  	{
   523  		type Model struct {
   524  			*Thing
   525  			ModelID int    `jsonapi:"primary,models"`
   526  			Foo     string `jsonapi:"attr,foo"`
   527  			Bar     string `jsonapi:"attr,bar"`
   528  			Bat     string `jsonapi:"attr,bat"`
   529  		}
   530  
   531  		scenarios = append(scenarios, test{
   532  			name: "Model embeds pointer of Thing; Thing is initialized w/ Unmarshal",
   533  			dst:  &Model{},
   534  			payload: &OnePayload{
   535  				Data: &Node{
   536  					Type: "models",
   537  					ID:   "1",
   538  					Attributes: map[string]interface{}{
   539  						"bar":  "barry",
   540  						"bat":  "batty",
   541  						"foo":  "fooey",
   542  						"buzz": 99,
   543  						"fizz": "fizzy",
   544  					},
   545  				},
   546  			},
   547  			expected: &Model{
   548  				Thing: &Thing{
   549  					Fizz: "fizzy",
   550  					Buzz: 99,
   551  				},
   552  				ModelID: 1,
   553  				Foo:     "fooey",
   554  				Bar:     "barry",
   555  				Bat:     "batty",
   556  			},
   557  		})
   558  	}
   559  	{
   560  		type Model struct {
   561  			*Thing
   562  			ModelID int    `jsonapi:"primary,models"`
   563  			Foo     string `jsonapi:"attr,foo"`
   564  			Bar     string `jsonapi:"attr,bar"`
   565  			Bat     string `jsonapi:"attr,bat"`
   566  		}
   567  
   568  		scenarios = append(scenarios, test{
   569  			name: "Model embeds pointer of Thing; jsonapi model doesn't assign anything to Thing; *Thing is nil",
   570  			dst:  &Model{},
   571  			payload: &OnePayload{
   572  				Data: &Node{
   573  					Type: "models",
   574  					ID:   "1",
   575  					Attributes: map[string]interface{}{
   576  						"bar": "barry",
   577  						"bat": "batty",
   578  						"foo": "fooey",
   579  					},
   580  				},
   581  			},
   582  			expected: &Model{
   583  				ModelID: 1,
   584  				Foo:     "fooey",
   585  				Bar:     "barry",
   586  				Bat:     "batty",
   587  			},
   588  		})
   589  	}
   590  
   591  	{
   592  		type Model struct {
   593  			*Thing
   594  			ModelID int    `jsonapi:"primary,models"`
   595  			Foo     string `jsonapi:"attr,foo"`
   596  			Bar     string `jsonapi:"attr,bar"`
   597  			Bat     string `jsonapi:"attr,bat"`
   598  		}
   599  
   600  		scenarios = append(scenarios, test{
   601  			name: "Model embeds pointer of Thing; *Thing is nil",
   602  			dst:  &Model{},
   603  			payload: &OnePayload{
   604  				Data: &Node{
   605  					Type: "models",
   606  					ID:   "1",
   607  					Attributes: map[string]interface{}{
   608  						"bar": "barry",
   609  						"bat": "batty",
   610  						"foo": "fooey",
   611  					},
   612  				},
   613  			},
   614  			expected: &Model{
   615  				ModelID: 1,
   616  				Foo:     "fooey",
   617  				Bar:     "barry",
   618  				Bat:     "batty",
   619  			},
   620  		})
   621  	}
   622  	for _, scenario := range scenarios {
   623  		t.Logf("running scenario: %s\n", scenario.name)
   624  
   625  		// get the expected model and marshal to jsonapi
   626  		buf := bytes.NewBuffer(nil)
   627  		if err := MarshalPayload(buf, scenario.expected); err != nil {
   628  			t.Fatal(err)
   629  		}
   630  
   631  		// get the node model representation and marshal to jsonapi
   632  		payload, err := json.Marshal(scenario.payload)
   633  		if err != nil {
   634  			t.Fatal(err)
   635  		}
   636  
   637  		// assert that we're starting w/ the same payload
   638  		isJSONEqual, err := isJSONEqual(payload, buf.Bytes())
   639  		if err != nil {
   640  			t.Fatal(err)
   641  		}
   642  		if !isJSONEqual {
   643  			t.Errorf("Got\n%s\nExpected\n%s\n", buf.Bytes(), payload)
   644  		}
   645  
   646  		// run jsonapi unmarshal
   647  		if err := UnmarshalPayload(bytes.NewReader(payload), scenario.dst); err != nil {
   648  			t.Fatal(err)
   649  		}
   650  
   651  		// assert decoded and expected models are equal
   652  		if !reflect.DeepEqual(scenario.expected, scenario.dst) {
   653  			t.Errorf("Got\n%#v\nExpected\n%#v\n", scenario.dst, scenario.expected)
   654  		}
   655  	}
   656  }
   657  
   658  func TestMarshal_duplicatePrimaryAnnotationFromEmbeddedStructs(t *testing.T) {
   659  	type Outer struct {
   660  		ID string `jsonapi:"primary,outer"`
   661  		Comment
   662  		*Post
   663  	}
   664  
   665  	o := Outer{
   666  		ID:      "outer",
   667  		Comment: Comment{ID: 1},
   668  		Post:    &Post{ID: 5},
   669  	}
   670  	var payloadData map[string]interface{}
   671  
   672  	// Test the standard libraries JSON handling of dup (ID) fields - it uses
   673  	// the Outer's ID
   674  	jsonData, err := json.Marshal(o)
   675  	if err != nil {
   676  		t.Fatal(err)
   677  	}
   678  	if err := json.Unmarshal(jsonData, &payloadData); err != nil {
   679  		t.Fatal(err)
   680  	}
   681  	if e, a := o.ID, payloadData["ID"]; e != a {
   682  		t.Fatalf("Was expecting ID to be %v, got %v", e, a)
   683  	}
   684  
   685  	// Test the JSONAPI lib handling of dup (ID) fields
   686  	jsonAPIData := new(bytes.Buffer)
   687  	if err := MarshalPayload(jsonAPIData, &o); err != nil {
   688  		t.Fatal(err)
   689  	}
   690  	if err := json.Unmarshal(jsonAPIData.Bytes(), &payloadData); err != nil {
   691  		t.Fatal(err)
   692  	}
   693  	data := payloadData["data"].(map[string]interface{})
   694  	id := data["id"].(string)
   695  	if e, a := o.ID, id; e != a {
   696  		t.Fatalf("Was expecting ID to be %v, got %v", e, a)
   697  	}
   698  }
   699  
   700  func TestMarshal_duplicateAttributeAnnotationFromEmbeddedStructs(t *testing.T) {
   701  	type Foo struct {
   702  		Count uint `json:"count" jsonapi:"attr,count"`
   703  	}
   704  	type Bar struct {
   705  		Count uint `json:"count" jsonapi:"attr,count"`
   706  	}
   707  	type Outer struct {
   708  		ID uint `json:"id" jsonapi:"primary,outer"`
   709  		Foo
   710  		Bar
   711  	}
   712  	o := Outer{
   713  		ID:  1,
   714  		Foo: Foo{Count: 1},
   715  		Bar: Bar{Count: 2},
   716  	}
   717  
   718  	var payloadData map[string]interface{}
   719  
   720  	// The standard JSON lib will not serialize either embedded struct's fields if
   721  	// a duplicate is encountered
   722  	jsonData, err := json.Marshal(o)
   723  	if err != nil {
   724  		t.Fatal(err)
   725  	}
   726  	if err := json.Unmarshal(jsonData, &payloadData); err != nil {
   727  		t.Fatal(err)
   728  	}
   729  	if _, found := payloadData["count"]; found {
   730  		t.Fatalf("Was not expecting to find the `count` key in the JSON")
   731  	}
   732  
   733  	// Test the JSONAPI lib handling of dup (attr) fields
   734  	jsonAPIData := new(bytes.Buffer)
   735  	if err := MarshalPayload(jsonAPIData, &o); err != nil {
   736  		t.Fatal(err)
   737  	}
   738  	if err := json.Unmarshal(jsonAPIData.Bytes(), &payloadData); err != nil {
   739  		t.Fatal(err)
   740  	}
   741  	data := payloadData["data"].(map[string]interface{})
   742  	if _, found := data["attributes"]; found {
   743  		t.Fatal("Was not expecting to find any `attributes` in the JSON API")
   744  	}
   745  }
   746  
   747  func TestMarshal_duplicateAttributeAnnotationFromEmbeddedStructsPtrs(t *testing.T) {
   748  	type Foo struct {
   749  		Count uint `json:"count" jsonapi:"attr,count"`
   750  	}
   751  	type Bar struct {
   752  		Count uint `json:"count" jsonapi:"attr,count"`
   753  	}
   754  	type Outer struct {
   755  		ID uint `json:"id" jsonapi:"primary,outer"`
   756  		*Foo
   757  		*Bar
   758  	}
   759  	o := Outer{
   760  		ID:  1,
   761  		Foo: &Foo{Count: 1},
   762  		Bar: &Bar{Count: 2},
   763  	}
   764  
   765  	var payloadData map[string]interface{}
   766  
   767  	// The standard JSON lib will not serialize either embedded struct's fields if
   768  	// a duplicate is encountered
   769  	jsonData, err := json.Marshal(o)
   770  	if err != nil {
   771  		t.Fatal(err)
   772  	}
   773  	if err := json.Unmarshal(jsonData, &payloadData); err != nil {
   774  		t.Fatal(err)
   775  	}
   776  	if _, found := payloadData["count"]; found {
   777  		t.Fatalf("Was not expecting to find the `count` key in the JSON")
   778  	}
   779  
   780  	// Test the JSONAPI lib handling of dup (attr) fields
   781  	jsonAPIData := new(bytes.Buffer)
   782  	if err := MarshalPayload(jsonAPIData, &o); err != nil {
   783  		t.Fatal(err)
   784  	}
   785  	if err := json.Unmarshal(jsonAPIData.Bytes(), &payloadData); err != nil {
   786  		t.Fatal(err)
   787  	}
   788  	data := payloadData["data"].(map[string]interface{})
   789  	if _, found := data["attributes"]; found {
   790  		t.Fatal("Was not expecting to find any `attributes` in the JSON API")
   791  	}
   792  }
   793  
   794  func TestMarshal_duplicateAttributeAnnotationFromEmbeddedStructsMixed(t *testing.T) {
   795  	type Foo struct {
   796  		Count uint `json:"count" jsonapi:"attr,count"`
   797  	}
   798  	type Bar struct {
   799  		Count uint `json:"count" jsonapi:"attr,count"`
   800  	}
   801  	type Outer struct {
   802  		ID uint `json:"id" jsonapi:"primary,outer"`
   803  		*Foo
   804  		Bar
   805  	}
   806  	o := Outer{
   807  		ID:  1,
   808  		Foo: &Foo{Count: 1},
   809  		Bar: Bar{Count: 2},
   810  	}
   811  
   812  	var payloadData map[string]interface{}
   813  
   814  	// The standard JSON lib will not serialize either embedded struct's fields if
   815  	// a duplicate is encountered
   816  	jsonData, err := json.Marshal(o)
   817  	if err != nil {
   818  		t.Fatal(err)
   819  	}
   820  	if err := json.Unmarshal(jsonData, &payloadData); err != nil {
   821  		t.Fatal(err)
   822  	}
   823  	if _, found := payloadData["count"]; found {
   824  		t.Fatalf("Was not expecting to find the `count` key in the JSON")
   825  	}
   826  
   827  	// Test the JSONAPI lib handling of dup (attr) fields; it should serialize
   828  	// neither
   829  	jsonAPIData := new(bytes.Buffer)
   830  	if err := MarshalPayload(jsonAPIData, &o); err != nil {
   831  		t.Fatal(err)
   832  	}
   833  	if err := json.Unmarshal(jsonAPIData.Bytes(), &payloadData); err != nil {
   834  		t.Fatal(err)
   835  	}
   836  	data := payloadData["data"].(map[string]interface{})
   837  	if _, found := data["attributes"]; found {
   838  		t.Fatal("Was not expecting to find any `attributes` in the JSON API")
   839  	}
   840  }
   841  
   842  func TestMarshal_duplicateFieldFromEmbeddedStructs_serializationNameDiffers(t *testing.T) {
   843  	type Foo struct {
   844  		Count uint `json:"foo-count" jsonapi:"attr,foo-count"`
   845  	}
   846  	type Bar struct {
   847  		Count uint `json:"bar-count" jsonapi:"attr,bar-count"`
   848  	}
   849  	type Outer struct {
   850  		ID uint `json:"id" jsonapi:"primary,outer"`
   851  		Foo
   852  		Bar
   853  	}
   854  	o := Outer{
   855  		ID:  1,
   856  		Foo: Foo{Count: 1},
   857  		Bar: Bar{Count: 2},
   858  	}
   859  
   860  	var payloadData map[string]interface{}
   861  
   862  	// The standard JSON lib will both the fields since their annotation name
   863  	// differs
   864  	jsonData, err := json.Marshal(o)
   865  	if err != nil {
   866  		t.Fatal(err)
   867  	}
   868  	if err := json.Unmarshal(jsonData, &payloadData); err != nil {
   869  		t.Fatal(err)
   870  	}
   871  	fooJSON, fooFound := payloadData["foo-count"]
   872  	if !fooFound {
   873  		t.Fatal("Was expecting to find the `foo-count` key in the JSON")
   874  	}
   875  	if e, a := o.Foo.Count, fooJSON.(float64); e != uint(a) {
   876  		t.Fatalf("Was expecting the `foo-count` value to be %v, got %v", e, a)
   877  	}
   878  	barJSON, barFound := payloadData["bar-count"]
   879  	if !barFound {
   880  		t.Fatal("Was expecting to find the `bar-count` key in the JSON")
   881  	}
   882  	if e, a := o.Bar.Count, barJSON.(float64); e != uint(a) {
   883  		t.Fatalf("Was expecting the `bar-count` value to be %v, got %v", e, a)
   884  	}
   885  
   886  	// Test the JSONAPI lib handling; it should serialize both
   887  	jsonAPIData := new(bytes.Buffer)
   888  	if err := MarshalPayload(jsonAPIData, &o); err != nil {
   889  		t.Fatal(err)
   890  	}
   891  	if err := json.Unmarshal(jsonAPIData.Bytes(), &payloadData); err != nil {
   892  		t.Fatal(err)
   893  	}
   894  	data := payloadData["data"].(map[string]interface{})
   895  	attributes := data["attributes"].(map[string]interface{})
   896  	fooJSONAPI, fooFound := attributes["foo-count"]
   897  	if !fooFound {
   898  		t.Fatal("Was expecting to find the `foo-count` attribute in the JSON API")
   899  	}
   900  	if e, a := o.Foo.Count, fooJSONAPI.(float64); e != uint(e) {
   901  		t.Fatalf("Was expecting the `foo-count` attrobute to be %v, got %v", e, a)
   902  	}
   903  	barJSONAPI, barFound := attributes["bar-count"]
   904  	if !barFound {
   905  		t.Fatal("Was expecting to find the `bar-count` attribute in the JSON API")
   906  	}
   907  	if e, a := o.Bar.Count, barJSONAPI.(float64); e != uint(e) {
   908  		t.Fatalf("Was expecting the `bar-count` attrobute to be %v, got %v", e, a)
   909  	}
   910  }
   911  
   912  func TestMarshal_embeddedStruct_providesDuplicateAttr(t *testing.T) {
   913  	type Foo struct {
   914  		Number uint `json:"count" jsonapi:"attr,count"`
   915  	}
   916  	type Outer struct {
   917  		Foo
   918  		ID    uint `json:"id" jsonapi:"primary,outer"`
   919  		Count uint `json:"count" jsonapi:"attr,count"`
   920  	}
   921  	o := Outer{
   922  		ID:    1,
   923  		Count: 1,
   924  		Foo:   Foo{Number: 5},
   925  	}
   926  	var payloadData map[string]interface{}
   927  
   928  	// The standard JSON lib will take the count annotated field from the Outer
   929  	jsonData, err := json.Marshal(o)
   930  	if err != nil {
   931  		t.Fatal(err)
   932  	}
   933  	if err := json.Unmarshal(jsonData, &payloadData); err != nil {
   934  		t.Fatal(err)
   935  	}
   936  	if e, a := o.Count, payloadData["count"].(float64); e != uint(a) {
   937  		t.Fatalf("Was expecting a JSON `count` of %v, got %v", e, a)
   938  	}
   939  
   940  	// In JSON API the handling should be that the Outer annotated count field is
   941  	// serialized into `attributes`
   942  	jsonAPIData := new(bytes.Buffer)
   943  	if err := MarshalPayload(jsonAPIData, &o); err != nil {
   944  		t.Fatal(err)
   945  	}
   946  	if err := json.Unmarshal(jsonAPIData.Bytes(), &payloadData); err != nil {
   947  		t.Fatal(err)
   948  	}
   949  	data := payloadData["data"].(map[string]interface{})
   950  	attributes := data["attributes"].(map[string]interface{})
   951  	if e, a := o.Count, attributes["count"].(float64); e != uint(a) {
   952  		t.Fatalf("Was expecting a JSON API `count` attribute of %v, got %v", e, a)
   953  	}
   954  }
   955  
   956  func TestMarshal_embeddedStructPtr_providesDuplicateAttr(t *testing.T) {
   957  	type Foo struct {
   958  		Number uint `json:"count" jsonapi:"attr,count"`
   959  	}
   960  	type Outer struct {
   961  		*Foo
   962  		ID    uint `json:"id" jsonapi:"primary,outer"`
   963  		Count uint `json:"count" jsonapi:"attr,count"`
   964  	}
   965  	o := Outer{
   966  		ID:    1,
   967  		Count: 1,
   968  		Foo:   &Foo{Number: 5},
   969  	}
   970  	var payloadData map[string]interface{}
   971  
   972  	// The standard JSON lib will take the count annotated field from the Outer
   973  	jsonData, err := json.Marshal(o)
   974  	if err != nil {
   975  		t.Fatal(err)
   976  	}
   977  	if err := json.Unmarshal(jsonData, &payloadData); err != nil {
   978  		t.Fatal(err)
   979  	}
   980  	if e, a := o.Count, payloadData["count"].(float64); e != uint(a) {
   981  		t.Fatalf("Was expecting a JSON `count` of %v, got %v", e, a)
   982  	}
   983  
   984  	// In JSON API the handling should be that the Outer annotated count field is
   985  	// serialized into `attributes`
   986  	jsonAPIData := new(bytes.Buffer)
   987  	if err := MarshalPayload(jsonAPIData, &o); err != nil {
   988  		t.Fatal(err)
   989  	}
   990  	if err := json.Unmarshal(jsonAPIData.Bytes(), &payloadData); err != nil {
   991  		t.Fatal(err)
   992  	}
   993  	data := payloadData["data"].(map[string]interface{})
   994  	attributes := data["attributes"].(map[string]interface{})
   995  	if e, a := o.Count, attributes["count"].(float64); e != uint(a) {
   996  		t.Fatalf("Was expecting a JSON API `count` attribute of %v, got %v", e, a)
   997  	}
   998  }
   999  
  1000  // TODO: test permutation of relations with embedded structs