From cf34a6652714eed3bda582ddcc45f0efa61028fc Mon Sep 17 00:00:00 2001 From: Benjamin Fleischer Date: Mon, 26 Oct 2015 01:07:02 -0500 Subject: [PATCH 1/4] Mapping JSON API spec / schema to AMS [ci skip] --- docs/jsonapi/schema.md | 138 ++++++++++++ docs/jsonapi/schema/schema.json | 366 ++++++++++++++++++++++++++++++++ 2 files changed, 504 insertions(+) create mode 100644 docs/jsonapi/schema.md create mode 100644 docs/jsonapi/schema/schema.json diff --git a/docs/jsonapi/schema.md b/docs/jsonapi/schema.md new file mode 100644 index 000000000..ea7f134e0 --- /dev/null +++ b/docs/jsonapi/schema.md @@ -0,0 +1,138 @@ +[![JSON API 1.0](https://img.shields.io/badge/JSON%20API-1.0-lightgrey.svg)](http://jsonapi.org/) + +## JSON API Requests + +[Query Parameters Spec](http://jsonapi.org/format/#query-parameters) + +Headers: + +- Request: `Accept: application/vnd.api+json` +- Response: `Content-Type: application/vnd.api+json` + +### [Fetching Data](http://jsonapi.org/format/#fetching) + +A server MUST support fetching resource data for every URL provided as: + +- a `self` link as part of the top-level links object +- a `self` link as part of a resource-level links object +- a `related` link as part of a relationship-level links object + +Example supported requests + +- Individual resource or collection + - GET /articles + - GET /articles/1 + - GET /articles/1/author +- Relationships + - GET /articles/1/relationships/comments + - GET /articles/1/relationships/author +- Optional: [Inclusion of related resources](http://jsonapi.org/format/#fetching-includes) `ActiveModel::Serializer::Fieldset` + - GET /articles/1?`include`=comments + - GET /articles/1?`include`=comments.author + - GET /articles/1?`include`=author,comments.author + - GET /articles/1/relationships/comments?`include`=comments.author +- Optional: [Sparse Fieldsets](http://jsonapi.org/format/#fetching-sparse-fieldsets) `ActiveModel::Serializer::Fieldset` + - GET /articles?`include`=author&`fields`[articles]=title,body&`fields`[people]=name +- Optional: [Sorting](http://jsonapi.org/format/#fetching-sorting) + - GET /people?`sort`=age + - GET /people?`sort`=age,author.name + - GET /articles?`sort`=-created,title +- Optional: [Pagination](http://jsonapi.org/format/#fetching-pagination) + - GET /articles?`page`[number]=3&`page`[size]=1 +- Optional: [Filtering](http://jsonapi.org/format/#fetching-filtering) + - GET /comments?`filter`[post]=1 + - GET /comments?`filter`[post]=1,2 + - GET /comments?`filter`[post]=1,2 + +### [CRUD Actions](http://jsonapi.org/format/#crud) + +### [Asynchronous Processing](http://jsonapi.org/recommendations/#asynchronous-processing) + +### [Bulk Operations Extension](http://jsonapi.org/extensions/bulk/) + +## JSON API Document Schema + +| JSON API object | JSON API properties | Required | ActiveModelSerializers representation | +|-----------------------|----------------------------------------------------------------------------------------------------|----------|---------------------------------------| +| schema | oneOf (success, failure, info) | | +| success | data, included, meta, links, jsonapi | | ActiveModel::SerializableResource +| success.meta | meta | | ActiveModel::Serializer::Adapter::Base#meta +| success.included | UniqueArray(resource) | | ActiveModel::Serializer::Adapter::JsonApi#serializable_hash_for_collection +| success.data | data | | +| success.links | allOf (links, pagination) | | ActiveModel::Serializer::Adapter::JsonApi#links_for +| success.jsonapi | jsonapi | | +| failure | errors, meta, jsonapi | errors | +| failure.errors | UniqueArray(error) | | #1004 +| meta | Object | | +| data | oneOf (resource, UniqueArray(resource)) | | ActiveModel::Serializer::Adapter::JsonApi#serializable_hash_for_collection,#serializable_hash_for_single_resource +| resource | String(type), String(id), attributes, relationships, links, meta | type, id | ActiveModel::Serializer::Adapter::JsonApi#primary_data_for +| links | Uri(self), Link(related) | | #1028, #1246, #1282 +| link | oneOf (linkString, linkObject) | | +| link.linkString | Uri | | +| link.linkObject | Uri(href), meta | href | +| attributes | patternProperites(`"^(?!relationships$|links$)\\w[-\\w_]*$"`), any valid JSON | | ActiveModel::Serializer#attributes, ActiveModel::Serializer::Adapter::JsonApi#resource_object_for +| relationships | patternProperites(`"^\\w[-\\w_]*$"`); links, relationships.data, meta | | ActiveModel::Serializer::Adapter::JsonApi#relationships_for +| relationships.data | oneOf (relationshipToOne, relationshipToMany) | | ActiveModel::Serializer::Adapter::JsonApi#resource_identifier_for +| relationshipToOne | anyOf(empty, linkage) | | +| relationshipToMany | UniqueArray(linkage) | | +| empty | null | | +| linkage | String(type), String(id), meta | type, id | ActiveModel::Serializer::Adapter::JsonApi#primary_data_for +| pagination | pageObject(first), pageObject(last), pageObject(prev), pageObject(next) | | ActiveModel::Serializer::Adapter::JsonApi::PaginationLinks#serializable_hash +| pagination.pageObject | oneOf(Uri, null) | | +| jsonapi | String(version), meta | | ActiveModel::Serializer::Adapter::JsonApi::ApiObjects::JsonApi +| error | String(id), links, String(status), String(code), String(title), String(detail), error.source, meta | | +| error.source | String(pointer), String(parameter) | | +| pointer | [JSON Pointer RFC6901](https://tools.ietf.org/html/rfc6901) | | + + +The [http://jsonapi.org/schema](schema/schema.json) makes a nice roadmap. + +### Success Document +- [ ] success + - [ ] data: `"$ref": "#/definitions/data"` + - [ ] included: array of unique items of type `"$ref": "#/definitions/resource"` + - [ ] meta: `"$ref": "#/definitions/meta"` + - [ ] links: + - [ ] link: `"$ref": "#/definitions/links"` + - [ ] pagination: ` "$ref": "#/definitions/pagination"` + - [ ] jsonapi: ` "$ref": "#/definitions/jsonapi"` + +### Failure Document + +- [ ] failure + - [ ] errors: array of unique items of type ` "$ref": "#/definitions/error"` + - [ ] meta: `"$ref": "#/definitions/meta"` + - [ ] jsonapi: `"$ref": "#/definitions/jsonapi"` + +### Info Document + +- [ ] info + - [ ] meta: `"$ref": "#/definitions/meta"` + - [ ] links: `"$ref": "#/definitions/links"` + - [ ] jsonapi: ` "$ref": "#/definitions/jsonapi"` + +### Definitions + +- [ ] definitions: + - [ ] meta + - [ ] data: oneOf (resource, array of unique resources) + - [ ] resource + - [ ] attributes + - [ ] relationships + - [ ] relationshipToOne + - [ ] empty + - [ ] linkage + - [ ] meta + - [ ] relationshipToMany + - [ ] linkage + - [ ] meta + - [ ] links + - [ ] meta + - [ ] links + - [ ] link + - [ ] uri + - [ ] href, meta + - [ ] pagination + - [ ] jsonapi + - [ ] meta + - [ ] error: id, links, status, code, title: detail: source [{pointer, type}, {parameter: {description, type}], meta diff --git a/docs/jsonapi/schema/schema.json b/docs/jsonapi/schema/schema.json new file mode 100644 index 000000000..ef3ea3510 --- /dev/null +++ b/docs/jsonapi/schema/schema.json @@ -0,0 +1,366 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "JSON API Schema", + "description": "This is a schema for responses in the JSON API format. For more, see http://jsonapi.org", + "oneOf": [ + { + "$ref": "#/definitions/success" + }, + { + "$ref": "#/definitions/failure" + }, + { + "$ref": "#/definitions/info" + } + ], + + "definitions": { + "success": { + "type": "object", + "required": [ + "data" + ], + "properties": { + "data": { + "$ref": "#/definitions/data" + }, + "included": { + "description": "To reduce the number of HTTP requests, servers **MAY** allow responses that include related resources along with the requested primary resources. Such responses are called \"compound documents\".", + "type": "array", + "items": { + "$ref": "#/definitions/resource" + }, + "uniqueItems": true + }, + "meta": { + "$ref": "#/definitions/meta" + }, + "links": { + "description": "Link members related to the primary data.", + "allOf": [ + { + "$ref": "#/definitions/links" + }, + { + "$ref": "#/definitions/pagination" + } + ] + }, + "jsonapi": { + "$ref": "#/definitions/jsonapi" + } + }, + "additionalProperties": false + }, + "failure": { + "type": "object", + "required": [ + "errors" + ], + "properties": { + "errors": { + "type": "array", + "items": { + "$ref": "#/definitions/error" + }, + "uniqueItems": true + }, + "meta": { + "$ref": "#/definitions/meta" + }, + "jsonapi": { + "$ref": "#/definitions/jsonapi" + } + }, + "additionalProperties": false + }, + "info": { + "type": "object", + "required": [ + "meta" + ], + "properties": { + "meta": { + "$ref": "#/definitions/meta" + }, + "links": { + "$ref": "#/definitions/links" + }, + "jsonapi": { + "$ref": "#/definitions/jsonapi" + } + }, + "additionalProperties": false + }, + + "meta": { + "description": "Non-standard meta-information that can not be represented as an attribute or relationship.", + "type": "object", + "additionalProperties": true + }, + "data": { + "description": "The document's \"primary data\" is a representation of the resource or collection of resources targeted by a request.", + "oneOf": [ + { + "$ref": "#/definitions/resource" + }, + { + "description": "An array of resource objects, an array of resource identifier objects, or an empty array ([]), for requests that target resource collections.", + "type": "array", + "items": { + "$ref": "#/definitions/resource" + }, + "uniqueItems": true + } + ] + }, + "resource": { + "description": "\"Resource objects\" appear in a JSON API document to represent resources.", + "type": "object", + "required": [ + "type", + "id" + ], + "properties": { + "type": { + "type": "string" + }, + "id": { + "type": "string" + }, + "attributes": { + "$ref": "#/definitions/attributes" + }, + "relationships": { + "$ref": "#/definitions/relationships" + }, + "links": { + "$ref": "#/definitions/links" + }, + "meta": { + "$ref": "#/definitions/meta" + } + }, + "additionalProperties": false + }, + + "links": { + "description": "A resource object **MAY** contain references to other resource objects (\"relationships\"). Relationships may be to-one or to-many. Relationships can be specified by including a member in a resource's links object.", + "type": "object", + "properties": { + "self": { + "description": "A `self` member, whose value is a URL for the relationship itself (a \"relationship URL\"). This URL allows the client to directly manipulate the relationship. For example, it would allow a client to remove an `author` from an `article` without deleting the people resource itself.", + "type": "string", + "format": "uri" + }, + "related": { + "$ref": "#/definitions/link" + } + }, + "additionalProperties": true + }, + "link": { + "description": "A link **MUST** be represented as either: a string containing the link's URL or a link object.", + "oneOf": [ + { + "description": "A string containing the link's URL.", + "type": "string", + "format": "uri" + }, + { + "type": "object", + "required": [ + "href" + ], + "properties": { + "href": { + "description": "A string containing the link's URL.", + "type": "string", + "format": "uri" + }, + "meta": { + "$ref": "#/definitions/meta" + } + } + } + ] + }, + + "attributes": { + "description": "Members of the attributes object (\"attributes\") represent information about the resource object in which it's defined.", + "type": "object", + "patternProperties": { + "^(?!relationships$|links$)\\w[-\\w_]*$": { + "description": "Attributes may contain any valid JSON value." + } + }, + "additionalProperties": false + }, + + "relationships": { + "description": "Members of the relationships object (\"relationships\") represent references from the resource object in which it's defined to other resource objects.", + "type": "object", + "patternProperties": { + "^\\w[-\\w_]*$": { + "properties": { + "links": { + "$ref": "#/definitions/links" + }, + "data": { + "description": "Member, whose value represents \"resource linkage\".", + "oneOf": [ + { + "$ref": "#/definitions/relationshipToOne" + }, + { + "$ref": "#/definitions/relationshipToMany" + } + ] + }, + "meta": { + "$ref": "#/definitions/meta" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "relationshipToOne": { + "description": "References to other resource objects in a to-one (\"relationship\"). Relationships can be specified by including a member in a resource's links object.", + "anyOf": [ + { + "$ref": "#/definitions/empty" + }, + { + "$ref": "#/definitions/linkage" + } + ] + }, + "relationshipToMany": { + "description": "An array of objects each containing \"type\" and \"id\" members for to-many relationships.", + "type": "array", + "items": { + "$ref": "#/definitions/linkage" + }, + "uniqueItems": true + }, + "empty": { + "description": "Describes an empty to-one relationship.", + "type": "null" + }, + "linkage": { + "description": "The \"type\" and \"id\" to non-empty members.", + "type": "object", + "required": [ + "type", + "id" + ], + "properties": { + "type": { + "type": "string" + }, + "id": { + "type": "string" + }, + "meta": { + "$ref": "#/definitions/meta" + } + }, + "additionalProperties": false + }, + "pagination": { + "type": "object", + "properties": { + "first": { + "description": "The first page of data", + "oneOf": [ + { "type": "string", "format": "uri" }, + { "type": "null" } + ] + }, + "last": { + "description": "The last page of data", + "oneOf": [ + { "type": "string", "format": "uri" }, + { "type": "null" } + ] + }, + "prev": { + "description": "The previous page of data", + "oneOf": [ + { "type": "string", "format": "uri" }, + { "type": "null" } + ] + }, + "next": { + "description": "The next page of data", + "oneOf": [ + { "type": "string", "format": "uri" }, + { "type": "null" } + ] + } + } + }, + + "jsonapi": { + "description": "An object describing the server's implementation", + "type": "object", + "properties": { + "version": { + "type": "string" + }, + "meta": { + "$ref": "#/definitions/meta" + } + }, + "additionalProperties": false + }, + + "error": { + "type": "object", + "properties": { + "id": { + "description": "A unique identifier for this particular occurrence of the problem.", + "type": "string" + }, + "links": { + "$ref": "#/definitions/links" + }, + "status": { + "description": "The HTTP status code applicable to this problem, expressed as a string value.", + "type": "string" + }, + "code": { + "description": "An application-specific error code, expressed as a string value.", + "type": "string" + }, + "title": { + "description": "A short, human-readable summary of the problem. It **SHOULD NOT** change from occurrence to occurrence of the problem, except for purposes of localization.", + "type": "string" + }, + "detail": { + "description": "A human-readable explanation specific to this occurrence of the problem.", + "type": "string" + }, + "source": { + "type": "object", + "properties": { + "pointer": { + "description": "A JSON Pointer [RFC6901] to the associated entity in the request document [e.g. \"/data\" for a primary data object, or \"/data/attributes/title\" for a specific attribute].", + "type": "string" + }, + "parameter": { + "description": "A string indicating which query parameter caused the error.", + "type": "string" + } + } + }, + "meta": { + "$ref": "#/definitions/meta" + } + }, + "additionalProperties": false + } + } +} From 15c296d6437c64d6d39f7555f1e5880a5c8b3c46 Mon Sep 17 00:00:00 2001 From: Benjamin Fleischer Date: Mon, 26 Oct 2015 01:21:49 -0500 Subject: [PATCH 2/4] Table formatting? [ci skip] --- docs/jsonapi/schema.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/jsonapi/schema.md b/docs/jsonapi/schema.md index ea7f134e0..56eacf1a0 100644 --- a/docs/jsonapi/schema.md +++ b/docs/jsonapi/schema.md @@ -2,7 +2,7 @@ ## JSON API Requests -[Query Parameters Spec](http://jsonapi.org/format/#query-parameters) +- [Query Parameters Spec](http://jsonapi.org/format/#query-parameters) Headers: @@ -65,22 +65,22 @@ Example supported requests | failure.errors | UniqueArray(error) | | #1004 | meta | Object | | | data | oneOf (resource, UniqueArray(resource)) | | ActiveModel::Serializer::Adapter::JsonApi#serializable_hash_for_collection,#serializable_hash_for_single_resource -| resource | String(type), String(id), attributes, relationships, links, meta | type, id | ActiveModel::Serializer::Adapter::JsonApi#primary_data_for +| resource | String(type), String(id),
attributes, relationships,
links, meta | type, id | ActiveModel::Serializer::Adapter::JsonApi#primary_data_for | links | Uri(self), Link(related) | | #1028, #1246, #1282 | link | oneOf (linkString, linkObject) | | | link.linkString | Uri | | | link.linkObject | Uri(href), meta | href | -| attributes | patternProperites(`"^(?!relationships$|links$)\\w[-\\w_]*$"`), any valid JSON | | ActiveModel::Serializer#attributes, ActiveModel::Serializer::Adapter::JsonApi#resource_object_for -| relationships | patternProperites(`"^\\w[-\\w_]*$"`); links, relationships.data, meta | | ActiveModel::Serializer::Adapter::JsonApi#relationships_for +| attributes | patternProperties(
`"^(?!relationships$|links$)\\w[-\\w_]*$"`),
any valid JSON | | ActiveModel::Serializer#attributes, ActiveModel::Serializer::Adapter::JsonApi#resource_object_for +| relationships | patternProperties(
`"^\\w[-\\w_]*$"`);
links, relationships.data, meta | | ActiveModel::Serializer::Adapter::JsonApi#relationships_for | relationships.data | oneOf (relationshipToOne, relationshipToMany) | | ActiveModel::Serializer::Adapter::JsonApi#resource_identifier_for | relationshipToOne | anyOf(empty, linkage) | | | relationshipToMany | UniqueArray(linkage) | | | empty | null | | | linkage | String(type), String(id), meta | type, id | ActiveModel::Serializer::Adapter::JsonApi#primary_data_for -| pagination | pageObject(first), pageObject(last), pageObject(prev), pageObject(next) | | ActiveModel::Serializer::Adapter::JsonApi::PaginationLinks#serializable_hash +| pagination | pageObject(first), pageObject(last),
pageObject(prev), pageObject(next) | | ActiveModel::Serializer::Adapter::JsonApi::PaginationLinks#serializable_hash | pagination.pageObject | oneOf(Uri, null) | | | jsonapi | String(version), meta | | ActiveModel::Serializer::Adapter::JsonApi::ApiObjects::JsonApi -| error | String(id), links, String(status), String(code), String(title), String(detail), error.source, meta | | +| error | String(id), links, String(status),
String(code), String(title),
String(detail), error.source, meta | | | error.source | String(pointer), String(parameter) | | | pointer | [JSON Pointer RFC6901](https://tools.ietf.org/html/rfc6901) | | From 14e84640c177e019ef17d456826457b82ded2e5e Mon Sep 17 00:00:00 2001 From: Benjamin Fleischer Date: Mon, 26 Oct 2015 01:24:10 -0500 Subject: [PATCH 3/4] Try to improve readability on github [ci skip] --- docs/jsonapi/schema.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/jsonapi/schema.md b/docs/jsonapi/schema.md index 56eacf1a0..423e5fc87 100644 --- a/docs/jsonapi/schema.md +++ b/docs/jsonapi/schema.md @@ -55,31 +55,31 @@ Example supported requests | JSON API object | JSON API properties | Required | ActiveModelSerializers representation | |-----------------------|----------------------------------------------------------------------------------------------------|----------|---------------------------------------| | schema | oneOf (success, failure, info) | | -| success | data, included, meta, links, jsonapi | | ActiveModel::SerializableResource -| success.meta | meta | | ActiveModel::Serializer::Adapter::Base#meta -| success.included | UniqueArray(resource) | | ActiveModel::Serializer::Adapter::JsonApi#serializable_hash_for_collection +| success | data, included, meta, links, jsonapi | | AM::SerializableResource +| success.meta | meta | | AM::S::Adapter::Base#meta +| success.included | UniqueArray(resource) | | AM::S::Adapter::JsonApi#serializable_hash_for_collection | success.data | data | | -| success.links | allOf (links, pagination) | | ActiveModel::Serializer::Adapter::JsonApi#links_for +| success.links | allOf (links, pagination) | | AM::S::Adapter::JsonApi#links_for | success.jsonapi | jsonapi | | | failure | errors, meta, jsonapi | errors | | failure.errors | UniqueArray(error) | | #1004 | meta | Object | | -| data | oneOf (resource, UniqueArray(resource)) | | ActiveModel::Serializer::Adapter::JsonApi#serializable_hash_for_collection,#serializable_hash_for_single_resource -| resource | String(type), String(id),
attributes, relationships,
links, meta | type, id | ActiveModel::Serializer::Adapter::JsonApi#primary_data_for +| data | oneOf (resource, UniqueArray(resource)) | | AM::S::Adapter::JsonApi#serializable_hash_for_collection,#serializable_hash_for_single_resource +| resource | String(type), String(id),
attributes, relationships,
links, meta | type, id | AM::S::Adapter::JsonApi#primary_data_for | links | Uri(self), Link(related) | | #1028, #1246, #1282 | link | oneOf (linkString, linkObject) | | | link.linkString | Uri | | | link.linkObject | Uri(href), meta | href | -| attributes | patternProperties(
`"^(?!relationships$|links$)\\w[-\\w_]*$"`),
any valid JSON | | ActiveModel::Serializer#attributes, ActiveModel::Serializer::Adapter::JsonApi#resource_object_for -| relationships | patternProperties(
`"^\\w[-\\w_]*$"`);
links, relationships.data, meta | | ActiveModel::Serializer::Adapter::JsonApi#relationships_for -| relationships.data | oneOf (relationshipToOne, relationshipToMany) | | ActiveModel::Serializer::Adapter::JsonApi#resource_identifier_for +| attributes | patternProperties(
`"^(?!relationships$|links$)\\w[-\\w_]*$"`),
any valid JSON | | AM::Serializer#attributes, AM::S::Adapter::JsonApi#resource_object_for +| relationships | patternProperties(
`"^\\w[-\\w_]*$"`);
links, relationships.data, meta | | AM::S::Adapter::JsonApi#relationships_for +| relationships.data | oneOf (relationshipToOne, relationshipToMany) | | AM::S::Adapter::JsonApi#resource_identifier_for | relationshipToOne | anyOf(empty, linkage) | | | relationshipToMany | UniqueArray(linkage) | | | empty | null | | -| linkage | String(type), String(id), meta | type, id | ActiveModel::Serializer::Adapter::JsonApi#primary_data_for -| pagination | pageObject(first), pageObject(last),
pageObject(prev), pageObject(next) | | ActiveModel::Serializer::Adapter::JsonApi::PaginationLinks#serializable_hash +| linkage | String(type), String(id), meta | type, id | AM::S::Adapter::JsonApi#primary_data_for +| pagination | pageObject(first), pageObject(last),
pageObject(prev), pageObject(next) | | AM::S::Adapter::JsonApi::PaginationLinks#serializable_hash | pagination.pageObject | oneOf(Uri, null) | | -| jsonapi | String(version), meta | | ActiveModel::Serializer::Adapter::JsonApi::ApiObjects::JsonApi +| jsonapi | String(version), meta | | AM::S::Adapter::JsonApi::ApiObjects::JsonApi | error | String(id), links, String(status),
String(code), String(title),
String(detail), error.source, meta | | | error.source | String(pointer), String(parameter) | | | pointer | [JSON Pointer RFC6901](https://tools.ietf.org/html/rfc6901) | | From 68a751744eed9f18f557ee50bf23bf2ae4c2c702 Mon Sep 17 00:00:00 2001 From: Lucas Hosseini Date: Mon, 26 Oct 2015 09:07:37 +0100 Subject: [PATCH 4/4] Update schema.md --- docs/jsonapi/schema.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/jsonapi/schema.md b/docs/jsonapi/schema.md index 423e5fc87..1a42585b3 100644 --- a/docs/jsonapi/schema.md +++ b/docs/jsonapi/schema.md @@ -26,7 +26,7 @@ Example supported requests - Relationships - GET /articles/1/relationships/comments - GET /articles/1/relationships/author -- Optional: [Inclusion of related resources](http://jsonapi.org/format/#fetching-includes) `ActiveModel::Serializer::Fieldset` +- Optional: [Inclusion of related resources](http://jsonapi.org/format/#fetching-includes) `ActiveModel::Serializer::IncludeTree` - GET /articles/1?`include`=comments - GET /articles/1?`include`=comments.author - GET /articles/1?`include`=author,comments.author