Skip to content

Commit

Permalink
Make the Meta test also check for the value of the detail key. Movin…
Browse files Browse the repository at this point in the history
…g 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{}.
  • Loading branch information
aren55555 committed Feb 17, 2017
1 parent 9babeb5 commit 0a2decb
Show file tree
Hide file tree
Showing 7 changed files with 246 additions and 190 deletions.
48 changes: 29 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

[![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)

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

## Installation
Expand Down Expand Up @@ -365,26 +365,36 @@ func (post Post) JSONAPIRelationshipLinks(relation string) *Links {
```

### Meta

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:

```go
func (post Post) JSONAPIMeta() *Meta {
return &Meta{
"details": "sample details here",
}
}

// Invoked for each relationship defined on the Post struct when marshaled
func (post Post) JSONAPIRelationshipMeta(relation string) *Meta {
if relation == "comments" {
return &Meta{
"details": "comment meta details here",
}
}
return nil
}
```
func (post Post) JSONAPIMeta() *Meta {
return &Meta{
"details": "sample details here",
}
}

// Invoked for each relationship defined on the Post struct when marshaled
func (post Post) JSONAPIRelationshipMeta(relation string) *Meta {
if relation == "comments" {
return &Meta{
"this": map[string]interface{}{
"can": map[string]interface{}{
"go": []interface{}{
"as",
"deep",
map[string]interface{}{
"as": "required",
},
},
},
},
}
}
return nil
}
```

### Errors
This package also implements support for JSON API compatible `errors` payloads using the following types.
Expand Down
8 changes: 4 additions & 4 deletions examples/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func exerciseHandler() {
// list
req, _ := http.NewRequest(http.MethodGet, "/blogs", nil)

req.Header.Set("Accept", jsonapi.MediaType)
req.Header.Set(headerAccept, jsonapi.MediaType)

w := httptest.NewRecorder()

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

req.Header.Set("Accept", jsonapi.MediaType)
req.Header.Set(headerAccept, jsonapi.MediaType)

w = httptest.NewRecorder()

Expand All @@ -81,7 +81,7 @@ func exerciseHandler() {

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

req.Header.Set("Accept", jsonapi.MediaType)
req.Header.Set(headerAccept, jsonapi.MediaType)

w = httptest.NewRecorder()

Expand All @@ -107,7 +107,7 @@ func exerciseHandler() {

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

req.Header.Set("Accept", jsonapi.MediaType)
req.Header.Set(headerAccept, jsonapi.MediaType)

w = httptest.NewRecorder()

Expand Down
33 changes: 28 additions & 5 deletions examples/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package main
import (
"fmt"
"time"

"github.com/google/jsonapi"
)

type Blog struct {
Expand Down Expand Up @@ -30,22 +32,43 @@ type Comment struct {
}

// Blog Links
func (blog Blog) JSONAPILinks() *map[string]interface{} {
return &map[string]interface{}{
func (blog Blog) JSONAPILinks() *jsonapi.Links {
return &jsonapi.Links{
"self": fmt.Sprintf("https://example.com/blogs/%d", blog.ID),
}
}

func (blog Blog) JSONAPIRelationshipLinks(relation string) *map[string]interface{} {
func (blog Blog) JSONAPIRelationshipLinks(relation string) *jsonapi.Links {
if relation == "posts" {
return &map[string]interface{}{
return &jsonapi.Links{
"related": fmt.Sprintf("https://example.com/blogs/%d/posts", blog.ID),
}
}
if relation == "current_post" {
return &map[string]interface{}{
return &jsonapi.Links{
"related": fmt.Sprintf("https://example.com/blogs/%d/current_post", blog.ID),
}
}
return nil
}

// Blog Meta
func (blog Blog) JSONAPIMeta() *jsonapi.Meta {
return &jsonapi.Meta{
"detail": "extra details regarding the blog",
}
}

func (blog Blog) JSONAPIRelationshipMeta(relation string) *jsonapi.Meta {
if relation == "posts" {
return &jsonapi.Meta{
"detail": "posts meta information",
}
}
if relation == "current_post" {
return &jsonapi.Meta{
"detail": "current post meta information",
}
}
return nil
}
157 changes: 157 additions & 0 deletions models_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
package jsonapi

import (
"fmt"
"time"
)

type BadModel struct {
ID int `jsonapi:"primary"`
}

type ModelBadTypes struct {
ID string `jsonapi:"primary,badtypes"`
StringField string `jsonapi:"attr,string_field"`
FloatField float64 `jsonapi:"attr,float_field"`
TimeField time.Time `jsonapi:"attr,time_field"`
TimePtrField *time.Time `jsonapi:"attr,time_ptr_field"`
}

type WithPointer struct {
ID *uint64 `jsonapi:"primary,with-pointers"`
Name *string `jsonapi:"attr,name"`
IsActive *bool `jsonapi:"attr,is-active"`
IntVal *int `jsonapi:"attr,int-val"`
FloatVal *float32 `jsonapi:"attr,float-val"`
}

type Timestamp struct {
ID int `jsonapi:"primary,timestamps"`
Time time.Time `jsonapi:"attr,timestamp,iso8601"`
Next *time.Time `jsonapi:"attr,next,iso8601"`
}

type Car struct {
ID *string `jsonapi:"primary,cars"`
Make *string `jsonapi:"attr,make,omitempty"`
Model *string `jsonapi:"attr,model,omitempty"`
Year *uint `jsonapi:"attr,year,omitempty"`
}

type Post struct {
Blog
ID uint64 `jsonapi:"primary,posts"`
BlogID int `jsonapi:"attr,blog_id"`
ClientID string `jsonapi:"client-id"`
Title string `jsonapi:"attr,title"`
Body string `jsonapi:"attr,body"`
Comments []*Comment `jsonapi:"relation,comments"`
LatestComment *Comment `jsonapi:"relation,latest_comment"`
}

type Comment struct {
ID int `jsonapi:"primary,comments"`
ClientID string `jsonapi:"client-id"`
PostID int `jsonapi:"attr,post_id"`
Body string `jsonapi:"attr,body"`
}

type Book struct {
ID uint64 `jsonapi:"primary,books"`
Author string `jsonapi:"attr,author"`
ISBN string `jsonapi:"attr,isbn"`
Title string `jsonapi:"attr,title,omitempty"`
Description *string `jsonapi:"attr,description"`
Pages *uint `jsonapi:"attr,pages,omitempty"`
PublishedAt time.Time
Tags []string `jsonapi:"attr,tags"`
}

type Blog struct {
ID int `jsonapi:"primary,blogs"`
ClientID string `jsonapi:"client-id"`
Title string `jsonapi:"attr,title"`
Posts []*Post `jsonapi:"relation,posts"`
CurrentPost *Post `jsonapi:"relation,current_post"`
CurrentPostID int `jsonapi:"attr,current_post_id"`
CreatedAt time.Time `jsonapi:"attr,created_at"`
ViewCount int `jsonapi:"attr,view_count"`
}

func (b *Blog) JSONAPILinks() *Links {
return &Links{
"self": fmt.Sprintf("https://example.com/api/blogs/%d", b.ID),
"comments": Link{
Href: fmt.Sprintf("https://example.com/api/blogs/%d/comments", b.ID),
Meta: Meta{
"counts": map[string]uint{
"likes": 4,
"comments": 20,
},
},
},
}
}

func (b *Blog) JSONAPIRelationshipLinks(relation string) *Links {
if relation == "posts" {
return &Links{
"related": Link{
Href: fmt.Sprintf("https://example.com/api/blogs/%d/posts", b.ID),
Meta: Meta{
"count": len(b.Posts),
},
},
}
}
if relation == "current_post" {
return &Links{
"self": fmt.Sprintf("https://example.com/api/posts/%s", "3"),
"related": Link{
Href: fmt.Sprintf("https://example.com/api/blogs/%d/current_post", b.ID),
},
}
}
return nil
}

func (b *Blog) JSONAPIMeta() *Meta {
return &Meta{
"detail": "extra details regarding the blog",
}
}

func (b *Blog) JSONAPIRelationshipMeta(relation string) *Meta {
if relation == "posts" {
return &Meta{
"this": map[string]interface{}{
"can": map[string]interface{}{
"go": []interface{}{
"as",
"deep",
map[string]interface{}{
"as": "required",
},
},
},
},
}
}
if relation == "current_post" {
return &Meta{
"detail": "extra current_post detail",
}
}
return nil
}

type BadComment struct {
ID uint64 `jsonapi:"primary,bad-comment"`
Body string `jsonapi:"attr,body"`
}

func (bc *BadComment) JSONAPILinks() *Links {
return &Links{
"self": []string{"invalid", "should error"},
}
}
14 changes: 8 additions & 6 deletions node.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,6 @@ type RelationshipManyNode struct {
// http://jsonapi.org/format/#document-links
type Links map[string]interface{}

// Meta is used to represent a `meta` object.
// http://jsonapi.org/format/#document-meta
type Meta map[string]interface{}

func (l *Links) validate() (err error) {
// Each member of a links object is a “link”. A link MUST be represented as
// either:
Expand All @@ -78,8 +74,8 @@ func (l *Links) validate() (err error) {

// Link is used to represent a member of the `links` object.
type Link struct {
Href string `json:"href"`
Meta map[string]interface{} `json:"meta,omitempty"`
Href string `json:"href"`
Meta Meta `json:"meta,omitempty"`
}

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

// Meta is used to represent a `meta` object.
// http://jsonapi.org/format/#document-meta
type Meta map[string]interface{}

// Metable is used to include document meta in response data
// e.g. {"foo": "bar"}
type Metable interface {
JSONAPIMeta() *Meta
}
Expand Down
20 changes: 0 additions & 20 deletions request_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,6 @@ import (
"time"
)

type BadModel struct {
ID int `jsonapi:"primary"`
}

type WithPointer struct {
ID *uint64 `jsonapi:"primary,with-pointers"`
Name *string `jsonapi:"attr,name"`
IsActive *bool `jsonapi:"attr,is-active"`
IntVal *int `jsonapi:"attr,int-val"`
FloatVal *float32 `jsonapi:"attr,float-val"`
}

type ModelBadTypes struct {
ID string `jsonapi:"primary,badtypes"`
StringField string `jsonapi:"attr,string_field"`
FloatField float64 `jsonapi:"attr,float_field"`
TimeField time.Time `jsonapi:"attr,time_field"`
TimePtrField *time.Time `jsonapi:"attr,time_ptr_field"`
}

func TestUnmarshall_attrStringSlice(t *testing.T) {
out := &Book{}
tags := []string{"fiction", "sale"}
Expand Down
Loading

0 comments on commit 0a2decb

Please sign in to comment.