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 &copy
   185  }