forked from Mirrors/jsonapi
Add support for 'meta' (#72)
* add Meta field in node structs and Metable/RelationshipMetable interfaces * update response marshalling to add Meta * update tests for presence of Meta * add Metable and RelationshipMetable interfaces to example * update README to include Meta details
This commit is contained in:
parent
52f00735e0
commit
776433d17d
22
README.md
22
README.md
|
@ -364,6 +364,28 @@ 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
|
||||
}
|
||||
```
|
||||
|
||||
### Errors
|
||||
This package also implements support for JSON API compatible `errors` payloads using the following types.
|
||||
|
||||
|
|
|
@ -345,3 +345,24 @@ func (blog Blog) JSONAPIRelationshipLinks(relation string) *map[string]interface
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Blog Meta
|
||||
func (blog Blog) JSONAPIMeta() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"detail": "extra details regarding the blog",
|
||||
}
|
||||
}
|
||||
|
||||
func (blog Blog) JSONAPIRelationshipMeta(relation string) map[string]interface{} {
|
||||
if relation == "posts" {
|
||||
return map[string]interface{}{
|
||||
"detail": "posts meta information",
|
||||
}
|
||||
}
|
||||
if relation == "current_post" {
|
||||
return map[string]interface{}{
|
||||
"detail": "current post meta information",
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
19
node.go
19
node.go
|
@ -8,6 +8,7 @@ type OnePayload struct {
|
|||
Data *Node `json:"data"`
|
||||
Included []*Node `json:"included,omitempty"`
|
||||
Links *Links `json:"links,omitempty"`
|
||||
Meta *Meta `json:"meta,omitempty"`
|
||||
}
|
||||
|
||||
// ManyPayload is used to represent a generic JSON API payload where many
|
||||
|
@ -16,6 +17,7 @@ type ManyPayload struct {
|
|||
Data []*Node `json:"data"`
|
||||
Included []*Node `json:"included,omitempty"`
|
||||
Links *Links `json:"links,omitempty"`
|
||||
Meta *Meta `json:"meta,omitempty"`
|
||||
}
|
||||
|
||||
// Node is used to represent a generic JSON API Resource
|
||||
|
@ -26,12 +28,14 @@ type Node struct {
|
|||
Attributes map[string]interface{} `json:"attributes,omitempty"`
|
||||
Relationships map[string]interface{} `json:"relationships,omitempty"`
|
||||
Links *Links `json:"links,omitempty"`
|
||||
Meta *Meta `json:"meta,omitempty"`
|
||||
}
|
||||
|
||||
// RelationshipOneNode is used to represent a generic has one JSON API relation
|
||||
type RelationshipOneNode struct {
|
||||
Data *Node `json:"data"`
|
||||
Links *Links `json:"links,omitempty"`
|
||||
Meta *Meta `json:"meta,omitempty"`
|
||||
}
|
||||
|
||||
// RelationshipManyNode is used to represent a generic has many JSON API
|
||||
|
@ -39,12 +43,17 @@ type RelationshipOneNode struct {
|
|||
type RelationshipManyNode struct {
|
||||
Data []*Node `json:"data"`
|
||||
Links *Links `json:"links,omitempty"`
|
||||
Meta *Meta `json:"meta,omitempty"`
|
||||
}
|
||||
|
||||
// Links is used to represent a `links` object.
|
||||
// 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:
|
||||
|
@ -85,3 +94,13 @@ type RelationshipLinkable interface {
|
|||
// JSONAPIRelationshipLinks will be invoked for each relationship with the corresponding relation name (e.g. `comments`)
|
||||
JSONAPIRelationshipLinks(relation string) *Links
|
||||
}
|
||||
|
||||
type Metable interface {
|
||||
JSONAPIMeta() *Meta
|
||||
}
|
||||
|
||||
// RelationshipMetable is used to include relationship meta in response data
|
||||
type RelationshipMetable interface {
|
||||
// JSONRelationshipMeta will be invoked for each relationship with the corresponding relation name (e.g. `comments`)
|
||||
JSONAPIRelationshipMeta(relation string) *Meta
|
||||
}
|
||||
|
|
13
response.go
13
response.go
|
@ -373,6 +373,11 @@ func visitModelNode(model interface{}, included *map[string]*Node,
|
|||
relLinks = linkableModel.JSONAPIRelationshipLinks(args[1])
|
||||
}
|
||||
|
||||
var relMeta *Meta
|
||||
if metableModel, ok := model.(RelationshipMetable); ok {
|
||||
relMeta = metableModel.JSONAPIRelationshipMeta(args[1])
|
||||
}
|
||||
|
||||
if isSlice {
|
||||
// to-many relationship
|
||||
relationship, err := visitModelNodeRelationships(
|
||||
|
@ -385,6 +390,7 @@ func visitModelNode(model interface{}, included *map[string]*Node,
|
|||
break
|
||||
}
|
||||
relationship.Links = relLinks
|
||||
relationship.Meta = relMeta
|
||||
|
||||
if sideload {
|
||||
shallowNodes := []*Node{}
|
||||
|
@ -396,6 +402,7 @@ func visitModelNode(model interface{}, included *map[string]*Node,
|
|||
node.Relationships[args[1]] = &RelationshipManyNode{
|
||||
Data: shallowNodes,
|
||||
Links: relationship.Links,
|
||||
Meta: relationship.Meta,
|
||||
}
|
||||
} else {
|
||||
node.Relationships[args[1]] = relationship
|
||||
|
@ -424,11 +431,13 @@ func visitModelNode(model interface{}, included *map[string]*Node,
|
|||
node.Relationships[args[1]] = &RelationshipOneNode{
|
||||
Data: toShallowNode(relationship),
|
||||
Links: relLinks,
|
||||
Meta: relMeta,
|
||||
}
|
||||
} else {
|
||||
node.Relationships[args[1]] = &RelationshipOneNode{
|
||||
Data: relationship,
|
||||
Links: relLinks,
|
||||
Meta: relMeta,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -451,6 +460,10 @@ func visitModelNode(model interface{}, included *map[string]*Node,
|
|||
node.Links = linkableModel.JSONAPILinks()
|
||||
}
|
||||
|
||||
if metableModel, ok := model.(Metable); ok {
|
||||
node.Meta = metableModel.JSONAPIMeta()
|
||||
}
|
||||
|
||||
return node, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -58,6 +58,26 @@ func (b *Blog) JSONAPIRelationshipLinks(relation string) *Links {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (blog Blog) JSONAPIMeta() *Meta {
|
||||
return &Meta{
|
||||
"detail": "extra details regarding the blog",
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Blog) JSONAPIRelationshipMeta(relation string) *Meta {
|
||||
if relation == "posts" {
|
||||
return &Meta{
|
||||
"detail": "extra posts detail",
|
||||
}
|
||||
}
|
||||
if relation == "current_post" {
|
||||
return &Meta{
|
||||
"detail": "extra current_post detail",
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Post struct {
|
||||
Blog
|
||||
ID uint64 `jsonapi:"primary,posts"`
|
||||
|
@ -557,6 +577,30 @@ func TestSupportsLinkable(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestSupportsMetable(t *testing.T) {
|
||||
testModel := &Blog{
|
||||
ID: 5,
|
||||
Title: "Title 1",
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
out := bytes.NewBuffer(nil)
|
||||
if err := MarshalOnePayload(out, testModel); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
resp := new(OnePayload)
|
||||
if err := json.NewDecoder(out).Decode(resp); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
data := resp.Data
|
||||
|
||||
if data.Meta == nil {
|
||||
t.Fatalf("Expected 'details' meta")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidLinkable(t *testing.T) {
|
||||
testModel := &BadComment{
|
||||
ID: 5,
|
||||
|
@ -594,6 +638,9 @@ func TestRelations(t *testing.T) {
|
|||
if relations["posts"].(map[string]interface{})["links"] == nil {
|
||||
t.Fatalf("Posts relationship links were not materialized")
|
||||
}
|
||||
if relations["posts"].(map[string]interface{})["meta"] == nil {
|
||||
t.Fatalf("Posts relationship meta were not materialized")
|
||||
}
|
||||
}
|
||||
|
||||
if relations["current_post"] == nil {
|
||||
|
@ -602,6 +649,9 @@ func TestRelations(t *testing.T) {
|
|||
if relations["current_post"].(map[string]interface{})["links"] == nil {
|
||||
t.Fatalf("Current post relationship links were not materialized")
|
||||
}
|
||||
if relations["current_post"].(map[string]interface{})["meta"] == nil {
|
||||
t.Fatalf("Current post relationship meta were not materialized")
|
||||
}
|
||||
}
|
||||
|
||||
if len(relations["posts"].(map[string]interface{})["data"].([]interface{})) != 2 {
|
||||
|
|
Loading…
Reference in New Issue