Skip to content

Commit 0a2decb

Browse files
committed
Make the Meta test also check for the value of the detail key. Moving all testing models to their own file. Updated the readme to include a deeply nested, varying typed meta example. Convert the map[string]interface to a Meta in the tests. Make the Meta field of a Link of type Meta rather than a map[string]inteface{}. Use the headerAccept constant defined for the example app. Commenting the new Metable interface and moving the Meta type beside it. Example app updated to use jsonapi.Links and jsonapi.Meta types rather than the underlying map[string]interface{}.
1 parent 9babeb5 commit 0a2decb

File tree

7 files changed

+246
-190
lines changed

7 files changed

+246
-190
lines changed

README.md

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
[![Build Status](https://travis-ci.org/google/jsonapi.svg?branch=master)](https://travis-ci.org/google/jsonapi) [![GoDoc](https://godoc.org/github.com/google/jsonapi?status.svg)](http://godoc.org/github.com/google/jsonapi)
44

5-
A serializer/deserializer for json payloads that comply to the
5+
A serializer/deserializer for JSON payloads that comply to the
66
[JSON API - jsonapi.org](http://jsonapi.org) spec in go.
77

88
## Installation
@@ -365,26 +365,36 @@ func (post Post) JSONAPIRelationshipLinks(relation string) *Links {
365365
```
366366

367367
### Meta
368-
368+
369369
If you need to include [meta objects](http://jsonapi.org/format/#document-meta) along with response data, implement the `Metable` interface for document-meta, and `RelationshipMetable` for relationship meta:
370-
370+
371371
```go
372-
func (post Post) JSONAPIMeta() *Meta {
373-
return &Meta{
374-
"details": "sample details here",
375-
}
376-
}
377-
378-
// Invoked for each relationship defined on the Post struct when marshaled
379-
func (post Post) JSONAPIRelationshipMeta(relation string) *Meta {
380-
if relation == "comments" {
381-
return &Meta{
382-
"details": "comment meta details here",
383-
}
384-
}
385-
return nil
386-
}
387-
```
372+
func (post Post) JSONAPIMeta() *Meta {
373+
return &Meta{
374+
"details": "sample details here",
375+
}
376+
}
377+
378+
// Invoked for each relationship defined on the Post struct when marshaled
379+
func (post Post) JSONAPIRelationshipMeta(relation string) *Meta {
380+
if relation == "comments" {
381+
return &Meta{
382+
"this": map[string]interface{}{
383+
"can": map[string]interface{}{
384+
"go": []interface{}{
385+
"as",
386+
"deep",
387+
map[string]interface{}{
388+
"as": "required",
389+
},
390+
},
391+
},
392+
},
393+
}
394+
}
395+
return nil
396+
}
397+
```
388398

389399
### Errors
390400
This package also implements support for JSON API compatible `errors` payloads using the following types.

examples/app.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ func exerciseHandler() {
4343
// list
4444
req, _ := http.NewRequest(http.MethodGet, "/blogs", nil)
4545

46-
req.Header.Set("Accept", jsonapi.MediaType)
46+
req.Header.Set(headerAccept, jsonapi.MediaType)
4747

4848
w := httptest.NewRecorder()
4949

@@ -60,7 +60,7 @@ func exerciseHandler() {
6060
// show
6161
req, _ = http.NewRequest(http.MethodGet, "/blogs?id=1", nil)
6262

63-
req.Header.Set("Accept", jsonapi.MediaType)
63+
req.Header.Set(headerAccept, jsonapi.MediaType)
6464

6565
w = httptest.NewRecorder()
6666

@@ -81,7 +81,7 @@ func exerciseHandler() {
8181

8282
req, _ = http.NewRequest(http.MethodPost, "/blogs", in)
8383

84-
req.Header.Set("Accept", jsonapi.MediaType)
84+
req.Header.Set(headerAccept, jsonapi.MediaType)
8585

8686
w = httptest.NewRecorder()
8787

@@ -107,7 +107,7 @@ func exerciseHandler() {
107107

108108
req, _ = http.NewRequest(http.MethodPut, "/blogs", in)
109109

110-
req.Header.Set("Accept", jsonapi.MediaType)
110+
req.Header.Set(headerAccept, jsonapi.MediaType)
111111

112112
w = httptest.NewRecorder()
113113

examples/models.go

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package main
33
import (
44
"fmt"
55
"time"
6+
7+
"github.com/google/jsonapi"
68
)
79

810
type Blog struct {
@@ -30,22 +32,43 @@ type Comment struct {
3032
}
3133

3234
// Blog Links
33-
func (blog Blog) JSONAPILinks() *map[string]interface{} {
34-
return &map[string]interface{}{
35+
func (blog Blog) JSONAPILinks() *jsonapi.Links {
36+
return &jsonapi.Links{
3537
"self": fmt.Sprintf("https://example.com/blogs/%d", blog.ID),
3638
}
3739
}
3840

39-
func (blog Blog) JSONAPIRelationshipLinks(relation string) *map[string]interface{} {
41+
func (blog Blog) JSONAPIRelationshipLinks(relation string) *jsonapi.Links {
4042
if relation == "posts" {
41-
return &map[string]interface{}{
43+
return &jsonapi.Links{
4244
"related": fmt.Sprintf("https://example.com/blogs/%d/posts", blog.ID),
4345
}
4446
}
4547
if relation == "current_post" {
46-
return &map[string]interface{}{
48+
return &jsonapi.Links{
4749
"related": fmt.Sprintf("https://example.com/blogs/%d/current_post", blog.ID),
4850
}
4951
}
5052
return nil
5153
}
54+
55+
// Blog Meta
56+
func (blog Blog) JSONAPIMeta() *jsonapi.Meta {
57+
return &jsonapi.Meta{
58+
"detail": "extra details regarding the blog",
59+
}
60+
}
61+
62+
func (blog Blog) JSONAPIRelationshipMeta(relation string) *jsonapi.Meta {
63+
if relation == "posts" {
64+
return &jsonapi.Meta{
65+
"detail": "posts meta information",
66+
}
67+
}
68+
if relation == "current_post" {
69+
return &jsonapi.Meta{
70+
"detail": "current post meta information",
71+
}
72+
}
73+
return nil
74+
}

models_test.go

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
package jsonapi
2+
3+
import (
4+
"fmt"
5+
"time"
6+
)
7+
8+
type BadModel struct {
9+
ID int `jsonapi:"primary"`
10+
}
11+
12+
type ModelBadTypes struct {
13+
ID string `jsonapi:"primary,badtypes"`
14+
StringField string `jsonapi:"attr,string_field"`
15+
FloatField float64 `jsonapi:"attr,float_field"`
16+
TimeField time.Time `jsonapi:"attr,time_field"`
17+
TimePtrField *time.Time `jsonapi:"attr,time_ptr_field"`
18+
}
19+
20+
type WithPointer struct {
21+
ID *uint64 `jsonapi:"primary,with-pointers"`
22+
Name *string `jsonapi:"attr,name"`
23+
IsActive *bool `jsonapi:"attr,is-active"`
24+
IntVal *int `jsonapi:"attr,int-val"`
25+
FloatVal *float32 `jsonapi:"attr,float-val"`
26+
}
27+
28+
type Timestamp struct {
29+
ID int `jsonapi:"primary,timestamps"`
30+
Time time.Time `jsonapi:"attr,timestamp,iso8601"`
31+
Next *time.Time `jsonapi:"attr,next,iso8601"`
32+
}
33+
34+
type Car struct {
35+
ID *string `jsonapi:"primary,cars"`
36+
Make *string `jsonapi:"attr,make,omitempty"`
37+
Model *string `jsonapi:"attr,model,omitempty"`
38+
Year *uint `jsonapi:"attr,year,omitempty"`
39+
}
40+
41+
type Post struct {
42+
Blog
43+
ID uint64 `jsonapi:"primary,posts"`
44+
BlogID int `jsonapi:"attr,blog_id"`
45+
ClientID string `jsonapi:"client-id"`
46+
Title string `jsonapi:"attr,title"`
47+
Body string `jsonapi:"attr,body"`
48+
Comments []*Comment `jsonapi:"relation,comments"`
49+
LatestComment *Comment `jsonapi:"relation,latest_comment"`
50+
}
51+
52+
type Comment struct {
53+
ID int `jsonapi:"primary,comments"`
54+
ClientID string `jsonapi:"client-id"`
55+
PostID int `jsonapi:"attr,post_id"`
56+
Body string `jsonapi:"attr,body"`
57+
}
58+
59+
type Book struct {
60+
ID uint64 `jsonapi:"primary,books"`
61+
Author string `jsonapi:"attr,author"`
62+
ISBN string `jsonapi:"attr,isbn"`
63+
Title string `jsonapi:"attr,title,omitempty"`
64+
Description *string `jsonapi:"attr,description"`
65+
Pages *uint `jsonapi:"attr,pages,omitempty"`
66+
PublishedAt time.Time
67+
Tags []string `jsonapi:"attr,tags"`
68+
}
69+
70+
type Blog struct {
71+
ID int `jsonapi:"primary,blogs"`
72+
ClientID string `jsonapi:"client-id"`
73+
Title string `jsonapi:"attr,title"`
74+
Posts []*Post `jsonapi:"relation,posts"`
75+
CurrentPost *Post `jsonapi:"relation,current_post"`
76+
CurrentPostID int `jsonapi:"attr,current_post_id"`
77+
CreatedAt time.Time `jsonapi:"attr,created_at"`
78+
ViewCount int `jsonapi:"attr,view_count"`
79+
}
80+
81+
func (b *Blog) JSONAPILinks() *Links {
82+
return &Links{
83+
"self": fmt.Sprintf("https://example.com/api/blogs/%d", b.ID),
84+
"comments": Link{
85+
Href: fmt.Sprintf("https://example.com/api/blogs/%d/comments", b.ID),
86+
Meta: Meta{
87+
"counts": map[string]uint{
88+
"likes": 4,
89+
"comments": 20,
90+
},
91+
},
92+
},
93+
}
94+
}
95+
96+
func (b *Blog) JSONAPIRelationshipLinks(relation string) *Links {
97+
if relation == "posts" {
98+
return &Links{
99+
"related": Link{
100+
Href: fmt.Sprintf("https://example.com/api/blogs/%d/posts", b.ID),
101+
Meta: Meta{
102+
"count": len(b.Posts),
103+
},
104+
},
105+
}
106+
}
107+
if relation == "current_post" {
108+
return &Links{
109+
"self": fmt.Sprintf("https://example.com/api/posts/%s", "3"),
110+
"related": Link{
111+
Href: fmt.Sprintf("https://example.com/api/blogs/%d/current_post", b.ID),
112+
},
113+
}
114+
}
115+
return nil
116+
}
117+
118+
func (b *Blog) JSONAPIMeta() *Meta {
119+
return &Meta{
120+
"detail": "extra details regarding the blog",
121+
}
122+
}
123+
124+
func (b *Blog) JSONAPIRelationshipMeta(relation string) *Meta {
125+
if relation == "posts" {
126+
return &Meta{
127+
"this": map[string]interface{}{
128+
"can": map[string]interface{}{
129+
"go": []interface{}{
130+
"as",
131+
"deep",
132+
map[string]interface{}{
133+
"as": "required",
134+
},
135+
},
136+
},
137+
},
138+
}
139+
}
140+
if relation == "current_post" {
141+
return &Meta{
142+
"detail": "extra current_post detail",
143+
}
144+
}
145+
return nil
146+
}
147+
148+
type BadComment struct {
149+
ID uint64 `jsonapi:"primary,bad-comment"`
150+
Body string `jsonapi:"attr,body"`
151+
}
152+
153+
func (bc *BadComment) JSONAPILinks() *Links {
154+
return &Links{
155+
"self": []string{"invalid", "should error"},
156+
}
157+
}

node.go

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,6 @@ type RelationshipManyNode struct {
5050
// http://jsonapi.org/format/#document-links
5151
type Links map[string]interface{}
5252

53-
// Meta is used to represent a `meta` object.
54-
// http://jsonapi.org/format/#document-meta
55-
type Meta map[string]interface{}
56-
5753
func (l *Links) validate() (err error) {
5854
// Each member of a links object is a “link”. A link MUST be represented as
5955
// either:
@@ -78,8 +74,8 @@ func (l *Links) validate() (err error) {
7874

7975
// Link is used to represent a member of the `links` object.
8076
type Link struct {
81-
Href string `json:"href"`
82-
Meta map[string]interface{} `json:"meta,omitempty"`
77+
Href string `json:"href"`
78+
Meta Meta `json:"meta,omitempty"`
8379
}
8480

8581
// Linkable is used to include document links in response data
@@ -95,6 +91,12 @@ type RelationshipLinkable interface {
9591
JSONAPIRelationshipLinks(relation string) *Links
9692
}
9793

94+
// Meta is used to represent a `meta` object.
95+
// http://jsonapi.org/format/#document-meta
96+
type Meta map[string]interface{}
97+
98+
// Metable is used to include document meta in response data
99+
// e.g. {"foo": "bar"}
98100
type Metable interface {
99101
JSONAPIMeta() *Meta
100102
}

request_test.go

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,26 +11,6 @@ import (
1111
"time"
1212
)
1313

14-
type BadModel struct {
15-
ID int `jsonapi:"primary"`
16-
}
17-
18-
type WithPointer struct {
19-
ID *uint64 `jsonapi:"primary,with-pointers"`
20-
Name *string `jsonapi:"attr,name"`
21-
IsActive *bool `jsonapi:"attr,is-active"`
22-
IntVal *int `jsonapi:"attr,int-val"`
23-
FloatVal *float32 `jsonapi:"attr,float-val"`
24-
}
25-
26-
type ModelBadTypes struct {
27-
ID string `jsonapi:"primary,badtypes"`
28-
StringField string `jsonapi:"attr,string_field"`
29-
FloatField float64 `jsonapi:"attr,float_field"`
30-
TimeField time.Time `jsonapi:"attr,time_field"`
31-
TimePtrField *time.Time `jsonapi:"attr,time_ptr_field"`
32-
}
33-
3414
func TestUnmarshall_attrStringSlice(t *testing.T) {
3515
out := &Book{}
3616
tags := []string{"fiction", "sale"}

0 commit comments

Comments
 (0)