Skip to content

Commit c229d41

Browse files
authored
Create Documentation (#13)
* start official docs * finish top-level resource * remove wip docs * extending node method section * finish links and errors * revisions
1 parent 4c7c318 commit c229d41

2 files changed

Lines changed: 359 additions & 117 deletions

File tree

README.md

Lines changed: 359 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,360 @@
11
# go-jsonapi
2+
3+
This Go module provides a useful API to create [JSON:API][jsonapi] HTTP servers. The primary usage of this library is to facilitate transformation from flattened Go structs into the standardized JSON:API [resource object][jsonapi-resource-object].
4+
5+
Additionally, there are optional methods that can be implemented with structs to add further standardized JSON:API structures such as links, relationships, included data, and metadata.
6+
7+
## Installation
8+
9+
```bash
10+
go get github.com/alehechka/go-jsonapi
11+
```
12+
13+
Import as:
14+
15+
```go
16+
import "github.com/alehechka/go-jsonapi/jsonapi"
17+
```
18+
19+
## Usage
20+
21+
### Defining a JSON:API struct
22+
23+
The primary resource object in JSON:API is of the following type:
24+
25+
```json
26+
{
27+
"data": {
28+
"id": "1234",
29+
"type": "people",
30+
"attributes": {
31+
"firstName": "John",
32+
"lastName": "Doe",
33+
"age": 30
34+
}
35+
}
36+
}
37+
```
38+
39+
- The `attributes` object will be generated from the struct itself.
40+
- The `id` field will be populated by the `ID()` interface method.
41+
- The `type` field will be populated by the `Type()` interface method.
42+
43+
```go
44+
type Person struct {
45+
// It is recommended to omit the primary ID from json marshalling, but not required
46+
PersonID string `json:"-"`
47+
FirstName string `json:"firstName"`
48+
LastName string `json:"lastName"`
49+
Age int `json:"age"`
50+
}
51+
52+
func (person Person) ID() string {
53+
return person.PersonID
54+
}
55+
56+
func (person Person) Type() string {
57+
return "people"
58+
}
59+
```
60+
61+
### Prepare for JSON marshalling
62+
63+
To prepare the struct for json marshalling it is required to use the provided `TransformResponse` or `TransformCollectionResponse` functions:
64+
65+
```go
66+
response := jsonapi.TransformResponse(jsonapi.Response{
67+
Node: Person{},
68+
"http://example.com",
69+
})
70+
71+
response := jsonapi.TransformCollectionResponse(jsonapi.CollectionResponse{
72+
Nodes: []Person{},
73+
"http://example.com",
74+
})
75+
```
76+
77+
The second parameter to these functions is for `baseURL`, this is used to dynamically populate relative URLs in `links` objects. More on this [here](#links).
78+
79+
### Recommended Usage
80+
81+
The above functions are effectively the top-level transformation tools, however, the dynamic link creation can be made easy by supplying an `*http.Request` object to the following functions instead:
82+
83+
```go
84+
req := httptest.NewRequest("GET", "http://example.com/example", nil)
85+
86+
response := jsonapi.CreateResponse(req)(jsonapi.Response{
87+
Node: Person{},
88+
})
89+
90+
response := jsonapi.CreateCollectionResponse(req)(jsonapi.CollectionResponse{
91+
Nodes: []Person{},
92+
})
93+
```
94+
95+
These versions will automatically extract the baseURL from the request and supply it to the respective `Transform` functions outlined above. This allows all generated links to display the same scheme and hostname as the server domain that the request was originally made to.
96+
97+
Additionally, using the `Create` functions will automatically generate a `self` link at the top-level object for every response.
98+
99+
### Extending the top-level resource
100+
101+
The JSON:API spec also allows for `links`, `errors`, and `meta` objects at the top-level of the document. Both `jsonapi.Response` and `jsonapi.CollectionResponse` have values available for these.
102+
103+
#### Links
104+
105+
A top-level `links` object can be provided to both `Response` and `CollectionResponse`. See [Link](#link) below for further details.
106+
107+
```go
108+
res := jsonapi.Response{
109+
Links: jsonapi.Links{
110+
jsonapi.NextKey: jsonapi.Link{
111+
Href: "/path/to/next/resource",
112+
},
113+
},
114+
}
115+
```
116+
117+
> When using either `CreateResponse` or `CreateCollectionResponse` the `self` link will be automatically generated and always override an existing `self` link.
118+
119+
#### Meta
120+
121+
A top-level `meta` object can be provided to both `Response` and `CollectionResponse` in the form of any interface or key-value map.
122+
123+
```go
124+
res := jsonapi.Response{
125+
Meta: jsonapi.Meta{
126+
"page": jsonapi.Meta{
127+
"size": 10,
128+
"number": 2,
129+
},
130+
},
131+
}
132+
```
133+
134+
> The `Meta` struct is simply an alias for `map[string]interface{}`
135+
136+
#### Errors
137+
138+
A top-level `errors` array can be provided to both `Response` and `CollectionResponse` in the form of an array of `Error` objects. See [Error](#error) below for further detail.
139+
140+
```go
141+
res := jsonapi.Response{
142+
Errors: jsonapi.Errors{
143+
{
144+
Status: http.StatusBadRequest,
145+
Title: "Error Occurred",
146+
Detail: "Failed to retrieve resource",
147+
},
148+
},
149+
}
150+
```
151+
152+
> It is important to note that if at least 1 error is present in this array than the top-level `data` object/array and `included` array will not be available as per the JSON:API spec for [Top Level][jsonapi-top-level].
153+
154+
### Extending `Node` interface
155+
156+
By default, to be considered a JSON:API resource, a struct must include the `ID()` and `Type()` methods.
157+
158+
However, this functionality can be extended further with other methods as follows:
159+
160+
#### `Links()`
161+
162+
The `Links()` method allows an individual resource to generate the `links` object for itself using data from the object. See [Link](#link) below for further details.
163+
164+
```go
165+
func (person Person) Links() jsonapi.Links {
166+
return jsonapi.Links{
167+
jsonapi.SelfKey: jsonapi.Link{
168+
Href: "/people/:id",
169+
Params: jsonapi.Params{
170+
"id": person.ID(),
171+
}
172+
},
173+
}
174+
}
175+
```
176+
177+
The above scenario makes use of the `Params` field which will not be included in the resulting json, but will use the key-value pairs to substitute the values into the `href` based on keys that it finds. (Ex. `:id` in the href will be substituted with the value of `person.ID()`)
178+
179+
#### `Relationships()`
180+
181+
[Relationships][jsonapi-relationships] are a key object within a resource to provide linkage and information about related resources. To facilitate the mapping, the `Relationships()` method gives access to the parent struct and allows definition of the `relationships` map as follows:
182+
183+
```go
184+
type Company struct {
185+
CompanyID string `json:"-"`
186+
Name string `json:"name"`
187+
Address string `json:"address"`
188+
Employees []Person `json:"-"` // recommended to omit children resources
189+
Owner Person `json:"-"`
190+
}
191+
192+
func (company Company) Relationships() map[string]interface{
193+
return map[string]interface{}{
194+
"employees": company.Employees,
195+
"owner": company.Owner,
196+
}
197+
}
198+
```
199+
200+
> In the above example it is crucial that the children relationship objects adhere to the JSON:API methods, i.e. initialize their own `ID()` and `Type()` methods.
201+
202+
#### `RelationshipLinks(parentID string)`
203+
204+
Typically in the `relationships` object, there will be included `links` object with links to the [related resources][jsonapi-related-links]. This can be facilitated by included the `RelationshipLinks(parentID string`) on children structs. The `parentID` parameter will automatically be supplied when generated as part of a relationship by the parent struct, it is recommended to use this in generating path params for the href variable.
205+
206+
```go
207+
func (person Person) RelationshipLinks(companyID string) jsonapi.Links {
208+
return jsonapi.Links{
209+
jsonapi.SelfKey: jsonapi.Link{
210+
Href: "/companies/:companyID/relationships/employees",
211+
Params: jsonapi.Params{
212+
"companyID": companyID,
213+
},
214+
},
215+
jsonapi.RelatedKey: jsonapi.Link{
216+
Href: "/companies/:companyID/employees",
217+
Params: jsonapi.Params{
218+
"companyID": companyID,
219+
},
220+
},
221+
}
222+
}
223+
```
224+
225+
If the relationship will point to an array of resources, it is recommended to instead create a unique type for that array of structs as follows:
226+
227+
```go
228+
type People []Person
229+
230+
func (people People) RelationshipLinks(companyID string) jsonapi.Links {
231+
return jsonapi.Links{
232+
jsonapi.SelfKey: jsonapi.Link{
233+
Href: "/companies/:companyID/relationships/employees",
234+
Params: jsonapi.Params{
235+
"companyID": companyID,
236+
},
237+
},
238+
}
239+
}
240+
```
241+
242+
#### `Meta()`
243+
244+
The `Meta()` method is simply a means to generate a `meta` object for an individual resource by using the object as an input.
245+
246+
```go
247+
func (person Person) Meta() interface{} {
248+
return jsonapi.Meta{
249+
"fullName": fmt.Sprintf("%s %s", person.FirstName, person.LastName),
250+
}
251+
}
252+
```
253+
254+
### Structs Explained
255+
256+
#### `Link`
257+
258+
The JSON:API [Links][jsonapi-document-links] states that each value of the `links` map must either be a string containing the link's URL or an object with an `href` and `meta` object. By, default, a `Link` object will be transformed into the string format in all cases expect when a non-nil, non-empty `Meta` object is provided.
259+
260+
```go
261+
links := jsonapi.Links{
262+
"self": jsonapi.Link{
263+
Href: "/path/to/resource",
264+
},
265+
"next": jsonapi.Link{
266+
Href: "/path/to/next/resource",
267+
Meta: jsonapi.Meta{
268+
"page": 3,
269+
},
270+
},
271+
}
272+
```
273+
274+
After transformation and JSON marshalling assuming the provided `baseURL` is `http://example.com`, the result will be as follows:
275+
276+
```json
277+
{
278+
"links": {
279+
"self": "http://example.com/path/to/resource",
280+
"next": {
281+
"href": "http://example.com/path/to/next/resource",
282+
"meta": {
283+
"page": 3
284+
}
285+
}
286+
}
287+
}
288+
```
289+
290+
Additionally, the `Link` object provides options for `Params` and `Queries`. These will always be ignored in the JSON marshalling and are used to help generate the `href` URL.
291+
292+
- `Params` is a map of key-value pairs that represent path parameters. During transformation, href path sections that are prefixed with a colon (`:`), will be substituted with the value of a matching key in the `Params` map.
293+
- `Queries` is a map of key-value pairs that represent query parameters. During transformation, all key-value pairs will be generated and appended to the href as query parameters. Pre-existing query parameters in the supplied href will not be removed, but will be replaced if they have the same key.
294+
295+
```go
296+
links := jsonapi.Links{
297+
"self": jsonapi.Link{
298+
Href: "/path/to/resource/:id?page[size]=20"
299+
Params: jsonapi.Params{
300+
"id": 1234,
301+
},
302+
Queries: jsonapi.Queries{
303+
"page[number]": 4,
304+
},
305+
},
306+
}
307+
```
308+
309+
After transformation and JSON marshalling assuming the provided `baseURL` is `http://example.com`, the result will be as follows:
310+
311+
```json
312+
{
313+
"links": {
314+
"self": "http://example.com/path/to/resource/1234?page[size]=20&page[limit]=4"
315+
}
316+
}
317+
```
318+
319+
> For further details, view the implementation here: [/jsonapi/links.go](/jsonapi/links.go#L35-L44)
320+
321+
#### `Error`
322+
323+
The JSON:API `Errors` specification includes a large number of fields, all of which can be supplied to the provided `Error` object. The internal `links` object of `Error` will also be supplied the `baseURL` and follow the same transformation rules outlined [above](#link).
324+
325+
```go
326+
errs := jsonapi.Errors{
327+
{
328+
Status: http.StatusBadRequest,
329+
Title: "Standard Error Occurred",
330+
Detail: "Further Detail is supplied here",
331+
},
332+
}
333+
```
334+
335+
After transformation and JSON marshalling, the result will be as follows:
336+
337+
```json
338+
{
339+
"errors": [
340+
{
341+
"status": 400,
342+
"title": "Standard Error Occurred",
343+
"detail": "Further Detail is supplied here"
344+
}
345+
]
346+
}
347+
```
348+
349+
> For further details, view the implementation here: [/jsonapi/errors.go](/jsonapi/errors.go#L10-L19)
350+
351+
<!--- Links -->
352+
353+
[jsonapi]: (https://jsonapi.org/)
354+
[jsonapi-resource-object]: (https://jsonapi.org/format/#document-resource-objects)
355+
[jsonapi-top-level]: (https://jsonapi.org/format/#document-top-level)
356+
[jsonapi-relationships]: (https://jsonapi.org/format/#document-resource-object-relationships)
357+
[jsonapi-related-links]: (https://jsonapi.org/format/#document-resource-object-related-resource-links)
358+
[jsonapi-document-links]: (https://jsonapi.org/format/#document-links)
359+
[jsonapi-errors]: (https://jsonapi.org/format/#errors)
360+
[gin]: (https://github.com/gin-gonic/gin)

0 commit comments

Comments
 (0)