github.com/seeker-insurance/kit@v0.0.13/jsonapi/node.go (about) 1 package jsonapi 2 3 import "fmt" 4 5 // Payloader is used to encapsulate the One and Many payload types 6 type Payloader interface { 7 clearIncluded() 8 } 9 10 // OnePayload is used to represent a generic JSON API payload where a single 11 // resource (Node) was included as an {} in the "data" key 12 type OnePayload struct { 13 Data *Node `json:"data"` 14 Included []*Node `json:"included,omitempty"` 15 Links *Links `json:"links,omitempty"` 16 Meta *Meta `json:"meta,omitempty"` 17 } 18 19 func (p *OnePayload) clearIncluded() { 20 p.Included = []*Node{} 21 } 22 23 // ManyPayload is used to represent a generic JSON API payload where many 24 // resources (Nodes) were included in an [] in the "data" key 25 type ManyPayload struct { 26 Data []*Node `json:"data"` 27 Included []*Node `json:"included,omitempty"` 28 Links *Links `json:"links,omitempty"` 29 Meta *Meta `json:"meta,omitempty"` 30 } 31 32 func (p *ManyPayload) clearIncluded() { 33 p.Included = []*Node{} 34 } 35 36 // Node is used to represent a generic JSON API Resource 37 type Node struct { 38 Type string `json:"type"` 39 ID string `json:"id,omitempty"` 40 ClientID string `json:"client-id,omitempty"` 41 Attributes map[string]interface{} `json:"attributes,omitempty"` 42 Relationships map[string]interface{} `json:"relationships,omitempty"` 43 Links *Links `json:"links,omitempty"` 44 Meta *Meta `json:"meta,omitempty"` 45 } 46 47 func (n *Node) merge(node *Node) { 48 if node.Type != "" { 49 n.Type = node.Type 50 } 51 52 if node.ID != "" { 53 n.ID = node.ID 54 } 55 56 if node.ClientID != "" { 57 n.ClientID = node.ClientID 58 } 59 60 if n.Attributes == nil && node.Attributes != nil { 61 n.Attributes = make(map[string]interface{}) 62 } 63 for k, v := range node.Attributes { 64 n.Attributes[k] = v 65 } 66 67 if n.Relationships == nil && node.Relationships != nil { 68 n.Relationships = make(map[string]interface{}) 69 } 70 for k, v := range node.Relationships { 71 n.Relationships[k] = v 72 } 73 74 if node.Links != nil { 75 n.Links = node.Links 76 } 77 } 78 79 // RelationshipOneNode is used to represent a generic has one JSON API relation 80 type RelationshipOneNode struct { 81 Data *Node `json:"data"` 82 Links *Links `json:"links,omitempty"` 83 Meta *Meta `json:"meta,omitempty"` 84 } 85 86 // RelationshipManyNode is used to represent a generic has many JSON API 87 // relation 88 type RelationshipManyNode struct { 89 Data []*Node `json:"data"` 90 Links *Links `json:"links,omitempty"` 91 Meta *Meta `json:"meta,omitempty"` 92 } 93 94 // Links is used to represent a `links` object. 95 // http://jsonapi.org/format/#document-links 96 type Links map[string]interface{} 97 98 func (l *Links) validate() (err error) { 99 // Each member of a links object is a “link”. A link MUST be represented as 100 // either: 101 // - a string containing the link’s URL. 102 // - an object (“link object”) which can contain the following members: 103 // - href: a string containing the link’s URL. 104 // - meta: a meta object containing non-standard meta-information about the 105 // link. 106 for k, v := range *l { 107 _, isString := v.(string) 108 _, isLink := v.(Link) 109 110 if !(isString || isLink) { 111 return fmt.Errorf( 112 "The %s member of the links object was not a string or link object", 113 k, 114 ) 115 } 116 } 117 return 118 } 119 120 // Link is used to represent a member of the `links` object. 121 type Link struct { 122 Href string `json:"href"` 123 Meta Meta `json:"meta,omitempty"` 124 } 125 126 // Linkable is used to include document links in response data 127 // e.g. {"self": "http://example.com/posts/1"} 128 type Linkable interface { 129 JSONAPILinks() *Links 130 } 131 132 // RelationshipLinkable is used to include relationship links in response data 133 // e.g. {"related": "http://example.com/posts/1/comments"} 134 type RelationshipLinkable interface { 135 // JSONAPIRelationshipLinks will be invoked for each relationship with the corresponding relation name (e.g. `comments`) 136 JSONAPIRelationshipLinks(relation string) *Links 137 } 138 139 // Meta is used to represent a `meta` object. 140 // http://jsonapi.org/format/#document-meta 141 type Meta map[string]interface{} 142 143 // Metable is used to include document meta in response data 144 // e.g. {"foo": "bar"} 145 type Metable interface { 146 JSONAPIMeta() *Meta 147 } 148 149 // RelationshipMetable is used to include relationship meta in response data 150 type RelationshipMetable interface { 151 // JSONRelationshipMeta will be invoked for each relationship with the corresponding relation name (e.g. `comments`) 152 JSONAPIRelationshipMeta(relation string) *Meta 153 } 154 155 // derefs the arg, and clones the map-type attributes 156 // note: maps are reference types, so they need an explicit copy. 157 func deepCopyNode(n *Node) *Node { 158 if n == nil { 159 return n 160 } 161 162 copyMap := func(m map[string]interface{}) map[string]interface{} { 163 if m == nil { 164 return m 165 } 166 cp := make(map[string]interface{}) 167 for k, v := range m { 168 cp[k] = v 169 } 170 return cp 171 } 172 173 copy := *n 174 copy.Attributes = copyMap(copy.Attributes) 175 copy.Relationships = copyMap(copy.Relationships) 176 if copy.Links != nil { 177 tmp := Links(copyMap(map[string]interface{}(*copy.Links))) 178 copy.Links = &tmp 179 } 180 if copy.Meta != nil { 181 tmp := Meta(copyMap(map[string]interface{}(*copy.Meta))) 182 copy.Meta = &tmp 183 } 184 return © 185 }