From c229d413e8f3dcca8e9511335802210c842ec27c Mon Sep 17 00:00:00 2001
From: Adam Lehechka <42357034+alehechka@users.noreply.github.com>
Date: Sun, 15 May 2022 10:23:06 -0500
Subject: [PATCH] Create Documentation (#13)
* start official docs
* finish top-level resource
* remove wip docs
* extending node method section
* finish links and errors
* revisions
---
README.md | 359 +++++++++++++++++++++++++++++++++++++++++++
documentation-wip.md | 117 --------------
2 files changed, 359 insertions(+), 117 deletions(-)
delete mode 100644 documentation-wip.md
diff --git a/README.md b/README.md
index 96680bb..08db926 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,360 @@
# go-jsonapi
+
+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].
+
+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.
+
+## Installation
+
+```bash
+go get github.com/alehechka/go-jsonapi
+```
+
+Import as:
+
+```go
+import "github.com/alehechka/go-jsonapi/jsonapi"
+```
+
+## Usage
+
+### Defining a JSON:API struct
+
+The primary resource object in JSON:API is of the following type:
+
+```json
+{
+ "data": {
+ "id": "1234",
+ "type": "people",
+ "attributes": {
+ "firstName": "John",
+ "lastName": "Doe",
+ "age": 30
+ }
+ }
+}
+```
+
+- The `attributes` object will be generated from the struct itself.
+- The `id` field will be populated by the `ID()` interface method.
+- The `type` field will be populated by the `Type()` interface method.
+
+```go
+type Person struct {
+ // It is recommended to omit the primary ID from json marshalling, but not required
+ PersonID string `json:"-"`
+ FirstName string `json:"firstName"`
+ LastName string `json:"lastName"`
+ Age int `json:"age"`
+}
+
+func (person Person) ID() string {
+ return person.PersonID
+}
+
+func (person Person) Type() string {
+ return "people"
+}
+```
+
+### Prepare for JSON marshalling
+
+To prepare the struct for json marshalling it is required to use the provided `TransformResponse` or `TransformCollectionResponse` functions:
+
+```go
+response := jsonapi.TransformResponse(jsonapi.Response{
+ Node: Person{},
+ "http://example.com",
+})
+
+response := jsonapi.TransformCollectionResponse(jsonapi.CollectionResponse{
+ Nodes: []Person{},
+ "http://example.com",
+})
+```
+
+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).
+
+### Recommended Usage
+
+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:
+
+```go
+req := httptest.NewRequest("GET", "http://example.com/example", nil)
+
+response := jsonapi.CreateResponse(req)(jsonapi.Response{
+ Node: Person{},
+})
+
+response := jsonapi.CreateCollectionResponse(req)(jsonapi.CollectionResponse{
+ Nodes: []Person{},
+})
+```
+
+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.
+
+Additionally, using the `Create` functions will automatically generate a `self` link at the top-level object for every response.
+
+### Extending the top-level resource
+
+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.
+
+#### Links
+
+A top-level `links` object can be provided to both `Response` and `CollectionResponse`. See [Link](#link) below for further details.
+
+```go
+res := jsonapi.Response{
+ Links: jsonapi.Links{
+ jsonapi.NextKey: jsonapi.Link{
+ Href: "/path/to/next/resource",
+ },
+ },
+}
+```
+
+> When using either `CreateResponse` or `CreateCollectionResponse` the `self` link will be automatically generated and always override an existing `self` link.
+
+#### Meta
+
+A top-level `meta` object can be provided to both `Response` and `CollectionResponse` in the form of any interface or key-value map.
+
+```go
+res := jsonapi.Response{
+ Meta: jsonapi.Meta{
+ "page": jsonapi.Meta{
+ "size": 10,
+ "number": 2,
+ },
+ },
+}
+```
+
+> The `Meta` struct is simply an alias for `map[string]interface{}`
+
+#### Errors
+
+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.
+
+```go
+res := jsonapi.Response{
+ Errors: jsonapi.Errors{
+ {
+ Status: http.StatusBadRequest,
+ Title: "Error Occurred",
+ Detail: "Failed to retrieve resource",
+ },
+ },
+}
+```
+
+> 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].
+
+### Extending `Node` interface
+
+By default, to be considered a JSON:API resource, a struct must include the `ID()` and `Type()` methods.
+
+However, this functionality can be extended further with other methods as follows:
+
+#### `Links()`
+
+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.
+
+```go
+func (person Person) Links() jsonapi.Links {
+ return jsonapi.Links{
+ jsonapi.SelfKey: jsonapi.Link{
+ Href: "/people/:id",
+ Params: jsonapi.Params{
+ "id": person.ID(),
+ }
+ },
+ }
+}
+```
+
+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()`)
+
+#### `Relationships()`
+
+[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:
+
+```go
+type Company struct {
+ CompanyID string `json:"-"`
+ Name string `json:"name"`
+ Address string `json:"address"`
+ Employees []Person `json:"-"` // recommended to omit children resources
+ Owner Person `json:"-"`
+}
+
+func (company Company) Relationships() map[string]interface{
+ return map[string]interface{}{
+ "employees": company.Employees,
+ "owner": company.Owner,
+ }
+}
+```
+
+> 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.
+
+#### `RelationshipLinks(parentID string)`
+
+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.
+
+```go
+func (person Person) RelationshipLinks(companyID string) jsonapi.Links {
+ return jsonapi.Links{
+ jsonapi.SelfKey: jsonapi.Link{
+ Href: "/companies/:companyID/relationships/employees",
+ Params: jsonapi.Params{
+ "companyID": companyID,
+ },
+ },
+ jsonapi.RelatedKey: jsonapi.Link{
+ Href: "/companies/:companyID/employees",
+ Params: jsonapi.Params{
+ "companyID": companyID,
+ },
+ },
+ }
+}
+```
+
+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:
+
+```go
+type People []Person
+
+func (people People) RelationshipLinks(companyID string) jsonapi.Links {
+ return jsonapi.Links{
+ jsonapi.SelfKey: jsonapi.Link{
+ Href: "/companies/:companyID/relationships/employees",
+ Params: jsonapi.Params{
+ "companyID": companyID,
+ },
+ },
+ }
+}
+```
+
+#### `Meta()`
+
+The `Meta()` method is simply a means to generate a `meta` object for an individual resource by using the object as an input.
+
+```go
+func (person Person) Meta() interface{} {
+ return jsonapi.Meta{
+ "fullName": fmt.Sprintf("%s %s", person.FirstName, person.LastName),
+ }
+}
+```
+
+### Structs Explained
+
+#### `Link`
+
+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.
+
+```go
+links := jsonapi.Links{
+ "self": jsonapi.Link{
+ Href: "/path/to/resource",
+ },
+ "next": jsonapi.Link{
+ Href: "/path/to/next/resource",
+ Meta: jsonapi.Meta{
+ "page": 3,
+ },
+ },
+}
+```
+
+After transformation and JSON marshalling assuming the provided `baseURL` is `http://example.com`, the result will be as follows:
+
+```json
+{
+ "links": {
+ "self": "http://example.com/path/to/resource",
+ "next": {
+ "href": "http://example.com/path/to/next/resource",
+ "meta": {
+ "page": 3
+ }
+ }
+ }
+}
+```
+
+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.
+
+- `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.
+- `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.
+
+```go
+links := jsonapi.Links{
+ "self": jsonapi.Link{
+ Href: "/path/to/resource/:id?page[size]=20"
+ Params: jsonapi.Params{
+ "id": 1234,
+ },
+ Queries: jsonapi.Queries{
+ "page[number]": 4,
+ },
+ },
+}
+```
+
+After transformation and JSON marshalling assuming the provided `baseURL` is `http://example.com`, the result will be as follows:
+
+```json
+{
+ "links": {
+ "self": "http://example.com/path/to/resource/1234?page[size]=20&page[limit]=4"
+ }
+}
+```
+
+> For further details, view the implementation here: [/jsonapi/links.go](/jsonapi/links.go#L35-L44)
+
+#### `Error`
+
+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).
+
+```go
+errs := jsonapi.Errors{
+ {
+ Status: http.StatusBadRequest,
+ Title: "Standard Error Occurred",
+ Detail: "Further Detail is supplied here",
+ },
+}
+```
+
+After transformation and JSON marshalling, the result will be as follows:
+
+```json
+{
+ "errors": [
+ {
+ "status": 400,
+ "title": "Standard Error Occurred",
+ "detail": "Further Detail is supplied here"
+ }
+ ]
+}
+```
+
+> For further details, view the implementation here: [/jsonapi/errors.go](/jsonapi/errors.go#L10-L19)
+
+
+
+[jsonapi]: (https://jsonapi.org/)
+[jsonapi-resource-object]: (https://jsonapi.org/format/#document-resource-objects)
+[jsonapi-top-level]: (https://jsonapi.org/format/#document-top-level)
+[jsonapi-relationships]: (https://jsonapi.org/format/#document-resource-object-relationships)
+[jsonapi-related-links]: (https://jsonapi.org/format/#document-resource-object-related-resource-links)
+[jsonapi-document-links]: (https://jsonapi.org/format/#document-links)
+[jsonapi-errors]: (https://jsonapi.org/format/#errors)
+[gin]: (https://github.com/gin-gonic/gin)
diff --git a/documentation-wip.md b/documentation-wip.md
deleted file mode 100644
index 5450fa6..0000000
--- a/documentation-wip.md
+++ /dev/null
@@ -1,117 +0,0 @@
-# go-jsonapi
-
-This go module provides a useful SDK to create [JSON:API][jsonapi] HTTP interfaces. The primary usage of this library is to facilitate transformation from a flattened Go struct into the standardized JSON:API [resource object][jsonapi-resource-object].
-
-With this, there are also struct interface options to facilitate relationships, metadata, and links, further information [below].
-
-To help with standard error messaging, there also some available middleware functions available for use out-of-the-box if using [gin][gin].
-
-## Installation
-
-Currently, this module is built with Go 1.18, but has yet to implement anything with Generics so it should be fairly backwards compatible.
-
-Install with:
-
-```bash
-go get github.com/alehechka/go-jsonapi
-```
-
-> gin will be listed as an indirect dependency, but the core of the library only uses core Go libraries.
-
-## Usage
-
-This library does not create any unnecessary marshalling assumptions, it will simply take in `Response` or `CollectionResponse` objects and transform them into JSON:API structs that keep the same provided object as the `Attributes` value. This allows an overlaying marshalling implementation to take over after transformation.
-
-View examples here: [/examples](/examples)
-
-### Interface Methods
-
-This library is built with struct interfaces at its core, so any object that you'd like to transform into JSON:API must implement both the `ID() string` and `Type() string` methods. However, there are more optional methods that can be implemented to further extend the JSON:API transformed object.
-
-ID() string
-
-The `ID()` interface is always required and is used to select a data member from the struct to use as the `id` field in the JSON:API object.
-
-> It is recommended to omit the selected variable with the following tag:
->
-> ```go
-> RecordID string `json:"-"`
-> ```
->
-> Although this is not required.
-
-```go
-func (record Record) ID() string {
- return record.RecordID
-}
-```
-
-
-
-Links() jsonapi.Links
-
-```go
-func (record Record) Links() jsonapi.Links {
- return jsonapi.Links{
- jsonapi.SelfKey: {
- Href: "/records/:id",
- Params: jsonapi.Params{
- "id": record.ID(),
- },
- Queries: jsonapi.Queries{
- "page[size]": 10,
- },
- // Meta: jsonapi.Meta{
- // "page": 10,
- // },
- }
- }
-}
-```
-
-
-Resulting JSON
-A `Record` object with `RecordID=1234` would have a resulting `links` object as follows:
-
-```json
-{
- "links": {
- "self": "http://example.com/records/1234?page[size]=10"
- }
-}
-```
-
-If the commented `Meta` option were used the resulting `self` link would be an object as follows:
-
-```json
-{
- "links": {
- "self": {
- "href": "http://example.com/records/1234?page[size]=10",
- "meta": {
- "page": 10
- }
- }
- }
-}
-```
-
-
-
-The `Href` variable is recommended to be provided a relative path and will be populated with a hostname from the provided `*http.Request` object. When no `Meta` variable is provided, the resulting `Link` will be a string of the generated URL.
-
-The `Href` variable can also be created with path params in the prefixed with a colon `:` and will be substituted in with a provided matching key in the `Params` variable.
-
-The `Params` variable represents a `map[string]any` and all matching keys will be substituted into the `Href`. `Params` will always be omitted when marshalling json.
-
-The `Queries` variable represents a `map[string]any` and will generate query parameters to append to the `Href`. `Queries` will always be omitted when marshalling json.
-
-The `Meta` variable represents a `map[string]any` and when provided will generate that `Link` as an object with the `meta` object included.
-
-
-
-
-
-[jsonapi]: (https://jsonapi.org/)
-[jsonapi-resource-object]: (https://jsonapi.org/format/#document-resource-objects)
-[gin]: (https://github.com/gin-gonic/gin)