From 05c2e91302d60a06b70d6bd10a44c012c7460981 Mon Sep 17 00:00:00 2001 From: Art4 Date: Fri, 27 Oct 2023 12:49:36 +0200 Subject: [PATCH 1/9] Add spec for JSON:API 1.0, run tests with normative statements --- tests/Functional/ParsingTest.php | 12 + tests/files/format_1.0/README.md | 3 + tests/files/format_1.0/index.md | 1822 +++++++++++ .../format_1.0/normative-statements.json | 2681 +++++++++++++++++ 4 files changed, 4518 insertions(+) create mode 100644 tests/files/format_1.0/README.md create mode 100644 tests/files/format_1.0/index.md create mode 100644 tests/files/format_1.0/normative-statements.json diff --git a/tests/Functional/ParsingTest.php b/tests/Functional/ParsingTest.php index 7e4957c..b058e84 100644 --- a/tests/Functional/ParsingTest.php +++ b/tests/Functional/ParsingTest.php @@ -647,4 +647,16 @@ public function testParseLinksInRelationshipsCorrectly(): void $this->assertInstanceOf('Art4\JsonApiClient\V1\Document', $document); $this->assertSame(['data'], $document->getKeys()); } + + public function testParseNormativeStatementsForVersion10Correctly(): void + { + $string = $this->getJsonString('format_1.0/normative-statements.json'); + $document = Parser::parseResponseString($string); + + $this->assertInstanceOf('Art4\JsonApiClient\V1\Document', $document); + $this->assertSame(['data', 'included', 'jsonapi'], $document->getKeys()); + $this->assertSame('1.0', $document->get('jsonapi.version')); + $this->assertCount(6, $document->get('data')->getKeys()); + $this->assertCount(184, $document->get('included')->getKeys()); + } } diff --git a/tests/files/format_1.0/README.md b/tests/files/format_1.0/README.md new file mode 100644 index 0000000..0d327e2 --- /dev/null +++ b/tests/files/format_1.0/README.md @@ -0,0 +1,3 @@ +# JSON:API spec version 1.0 + +Source: https://github.com/json-api/json-api/tree/v1.1/_format/1.0 diff --git a/tests/files/format_1.0/index.md b/tests/files/format_1.0/index.md new file mode 100644 index 0000000..ecb83f2 --- /dev/null +++ b/tests/files/format_1.0/index.md @@ -0,0 +1,1822 @@ +--- +version: 1.0 +--- + +## Introduction + +JSON:API is a specification for how a client should request that resources be +fetched or modified, and how a server should respond to those requests. + +JSON:API is designed to minimize both the number of requests and the amount of +data transmitted between clients and servers. This efficiency is achieved +without compromising readability, flexibility, or discoverability. + +JSON:API requires use of the JSON:API media type +([`application/vnd.api+json`](http://www.iana.org/assignments/media-types/application/vnd.api+json)) +for exchanging data. + +## Conventions + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", +"SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be +interpreted as described in RFC 2119 +[[RFC2119](https://tools.ietf.org/html/rfc2119)]. + +## Content Negotiation + +### Client Responsibilities + +Clients **MUST** send all JSON:API data in request documents with the header +`Content-Type: application/vnd.api+json` without any media type parameters. + +Clients that include the JSON:API media type in their `Accept` header **MUST** +specify the media type there at least once without any media type parameters. + +Clients **MUST** ignore any parameters for the `application/vnd.api+json` +media type received in the `Content-Type` header of response documents. + +### Server Responsibilities + +Servers **MUST** send all JSON:API data in response documents with the header +`Content-Type: application/vnd.api+json` without any media type parameters. + +Servers **MUST** respond with a `415 Unsupported Media Type` status code if +a request specifies the header `Content-Type: application/vnd.api+json` +with any media type parameters. + +Servers **MUST** respond with a `406 Not Acceptable` status code if a +request's `Accept` header contains the JSON:API media type and all instances +of that media type are modified with media type parameters. + +> Note: The content negotiation requirements exist to allow future versions +of this specification to use media type parameters for extension negotiation +and versioning. + +## Document Structure + +This section describes the structure of a JSON:API document, which is identified +by the media type [`application/vnd.api+json`](http://www.iana.org/assignments/media-types/application/vnd.api+json). +JSON:API documents are defined in JavaScript Object Notation (JSON) +[[RFC7159](http://tools.ietf.org/html/rfc7159)]. + +Although the same media type is used for both request and response documents, +certain aspects are only applicable to one or the other. These differences are +called out below. + +Unless otherwise noted, objects defined by this specification **MUST NOT** +contain any additional members. Client and server implementations **MUST** +ignore members not recognized by this specification. + +> Note: These conditions allow this specification to evolve through additive +changes. + +### Top Level + +A JSON object **MUST** be at the root of every JSON:API request and response +containing data. This object defines a document's "top level". + +A document **MUST** contain at least one of the following top-level members: + +* `data`: the document's "primary data" +* `errors`: an array of [error objects](#errors) +* `meta`: a [meta object][meta] that contains non-standard + meta-information. + +The members `data` and `errors` **MUST NOT** coexist in the same document. + +A document **MAY** contain any of these top-level members: + +* `jsonapi`: an object describing the server's implementation +* `links`: a [links object][links] related to the primary data. +* `included`: an array of [resource objects] that are related to the primary + data and/or each other ("included resources"). + +If a document does not contain a top-level `data` key, the `included` member +**MUST NOT** be present either. + +The top-level [links object][links] **MAY** contain the following members: + +* `self`: the [link][links] that generated the current response document. +* `related`: a [related resource link] when the primary data represents a + resource relationship. +* [pagination] links for the primary data. + +The document's "primary data" is a representation of the resource or collection +of resources targeted by a request. + +Primary data **MUST** be either: + +* a single [resource object][resource objects], a single [resource identifier object], or `null`, + for requests that target single resources +* an array of [resource objects], an array of + [resource identifier objects][resource identifier object], or + an empty array (`[]`), for requests that target resource collections + +For example, the following primary data is a single resource object: + +```json +{ + "data": { + "type": "articles", + "id": "1", + "attributes": { + // ... this article's attributes + }, + "relationships": { + // ... this article's relationships + } + } +} +``` + +The following primary data is a single [resource identifier object] that +references the same resource: + +```json +{ + "data": { + "type": "articles", + "id": "1" + } +} +``` + +A logical collection of resources **MUST** be represented as an array, even if +it only contains one item or is empty. + +### Resource Objects + +"Resource objects" appear in a JSON:API document to represent resources. + +A resource object **MUST** contain at least the following top-level members: + +* `id` +* `type` + +Exception: The `id` member is not required when the resource object originates at +the client and represents a new resource to be created on the server. + +In addition, a resource object **MAY** contain any of these top-level members: + +* `attributes`: an [attributes object][attributes] representing some of the resource's data. +* `relationships`: a [relationships object][relationships] describing relationships between + the resource and other JSON:API resources. +* `links`: a [links object][links] containing links related to the resource. +* `meta`: a [meta object][meta] containing non-standard meta-information about a + resource that can not be represented as an attribute or relationship. + +Here's how an article (i.e. a resource of type "articles") might appear in a document: + +```json +// ... +{ + "type": "articles", + "id": "1", + "attributes": { + "title": "Rails is Omakase" + }, + "relationships": { + "author": { + "links": { + "self": "/articles/1/relationships/author", + "related": "/articles/1/author" + }, + "data": { "type": "people", "id": "9" } + } + } +} +// ... +``` + +#### Identification + +Every [resource object][resource objects] **MUST** contain an `id` member and a `type` member. +The values of the `id` and `type` members **MUST** be strings. + +Within a given API, each resource object's `type` and `id` pair **MUST** +identify a single, unique resource. (The set of URIs controlled by a server, +or multiple servers acting as one, constitute an API.) + +The `type` member is used to describe [resource objects] that share common +attributes and relationships. + +The values of `type` members **MUST** adhere to the same constraints as +[member names]. + +> Note: This spec is agnostic about inflection rules, so the value of `type` +can be either plural or singular. However, the same value should be used +consistently throughout an implementation. + +#### Fields + +A resource object's [attributes] and its [relationships] are collectively called +its "[fields]". + +Fields for a [resource object][resource objects] **MUST** share a common namespace with each +other and with `type` and `id`. In other words, a resource can not have an +attribute and relationship with the same name, nor can it have an attribute +or relationship named `type` or `id`. + +#### Attributes + +The value of the `attributes` key **MUST** be an object (an "attributes +object"). Members of the attributes object ("attributes") represent information +about the [resource object][resource objects] in which it's defined. + +Attributes may contain any valid JSON value. + +Complex data structures involving JSON objects and arrays are allowed as +attribute values. However, any object that constitutes or is contained in an +attribute **MUST NOT** contain a `relationships` or `links` member, as those +members are reserved by this specification for future use. + +Although has-one foreign keys (e.g. `author_id`) are often stored internally +alongside other information to be represented in a resource object, these keys +**SHOULD NOT** appear as attributes. + +> Note: See [fields] and [member names] for more restrictions on this container. + +#### Relationships + +The value of the `relationships` key **MUST** be an object (a "relationships +object"). Members of the relationships object ("relationships") represent +references from the [resource object][resource objects] in which it's defined to other resource +objects. + +Relationships may be to-one or to-many. + + +A "relationship object" **MUST** contain at least one of the following: + +* `links`: a [links object][links] containing at least one of the following: + * `self`: a link for the relationship itself (a "relationship link"). This + link allows the client to directly manipulate the relationship. For example, + removing an `author` through an `article`'s relationship URL would disconnect + the person from the `article` without deleting the `people` resource itself. + When fetched successfully, this link returns the [linkage][resource linkage] + for the related resources as its primary data. + (See [Fetching Relationships](#fetching-relationships).) + * `related`: a [related resource link] +* `data`: [resource linkage] +* `meta`: a [meta object][meta] that contains non-standard meta-information about the + relationship. + +A relationship object that represents a to-many relationship **MAY** also contain +[pagination] links under the `links` member, as described below. Any +[pagination] links in a relationship object **MUST** paginate the relationship +data, not the related resources. + +> Note: See [fields] and [member names] for more restrictions on this container. + +#### Related Resource Links + +A "related resource link" provides access to [resource objects][resource objects] [linked][links] +in a [relationship][relationships]. When fetched, the related resource object(s) +are returned as the response's primary data. + +For example, an `article`'s `comments` [relationship][relationships] could +specify a [link][links] that returns a collection of comment [resource objects] +when retrieved through a `GET` request. + +If present, a related resource link **MUST** reference a valid URL, even if the +relationship isn't currently associated with any target resources. Additionally, +a related resource link **MUST NOT** change because its relationship's content +changes. + +#### Resource Linkage + +Resource linkage in a [compound document] allows a client to link together all +of the included [resource objects] without having to `GET` any URLs via [links]. + +Resource linkage **MUST** be represented as one of the following: + +* `null` for empty to-one relationships. +* an empty array (`[]`) for empty to-many relationships. +* a single [resource identifier object] for non-empty to-one relationships. +* an array of [resource identifier objects][resource identifier object] for non-empty to-many relationships. + +> Note: The spec does not impart meaning to order of resource identifier +objects in linkage arrays of to-many relationships, although implementations +may do that. Arrays of resource identifier objects may represent ordered +or unordered relationships, and both types can be mixed in one response +object. + +For example, the following article is associated with an `author`: + +```json +// ... +{ + "type": "articles", + "id": "1", + "attributes": { + "title": "Rails is Omakase" + }, + "relationships": { + "author": { + "links": { + "self": "http://example.com/articles/1/relationships/author", + "related": "http://example.com/articles/1/author" + }, + "data": { "type": "people", "id": "9" } + } + }, + "links": { + "self": "http://example.com/articles/1" + } +} +// ... +``` + +The `author` relationship includes a link for the relationship itself (which +allows the client to change the related author directly), a related resource +link to fetch the resource objects, and linkage information. + +#### Resource Links + +The optional `links` member within each [resource object][resource objects] contains [links] +related to the resource. + +If present, this links object **MAY** contain a `self` [link][links] that +identifies the resource represented by the resource object. + +```json +// ... +{ + "type": "articles", + "id": "1", + "attributes": { + "title": "Rails is Omakase" + }, + "links": { + "self": "http://example.com/articles/1" + } +} +// ... +``` + +A server **MUST** respond to a `GET` request to the specified URL with a +response that includes the resource as the primary data. + +### Resource Identifier Objects + +A "resource identifier object" is an object that identifies an individual +resource. + +A "resource identifier object" **MUST** contain `type` and `id` members. + +A "resource identifier object" **MAY** also include a `meta` member, whose value is a [meta] object that +contains non-standard meta-information. + +### Compound Documents + +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". + +In a compound document, all included resources **MUST** be represented as an +array of [resource objects] in a top-level `included` member. + +Compound documents require "full linkage", meaning that every included +resource **MUST** be identified by at least one [resource identifier object] +in the same document. These resource identifier objects could either be +primary data or represent resource linkage contained within primary or +included resources. + +The only exception to the full linkage requirement is when relationship fields +that would otherwise contain linkage data are excluded via [sparse fieldsets](#fetching-sparse-fieldsets). + +> Note: Full linkage ensures that included resources are related to either +the primary data (which could be [resource objects] or [resource identifier +objects][resource identifier object]) or to each other. + +A complete example document with multiple included relationships: + +```json +{ + "data": [{ + "type": "articles", + "id": "1", + "attributes": { + "title": "JSON:API paints my bikeshed!" + }, + "links": { + "self": "http://example.com/articles/1" + }, + "relationships": { + "author": { + "links": { + "self": "http://example.com/articles/1/relationships/author", + "related": "http://example.com/articles/1/author" + }, + "data": { "type": "people", "id": "9" } + }, + "comments": { + "links": { + "self": "http://example.com/articles/1/relationships/comments", + "related": "http://example.com/articles/1/comments" + }, + "data": [ + { "type": "comments", "id": "5" }, + { "type": "comments", "id": "12" } + ] + } + } + }], + "included": [{ + "type": "people", + "id": "9", + "attributes": { + "first-name": "Dan", + "last-name": "Gebhardt", + "twitter": "dgeb" + }, + "links": { + "self": "http://example.com/people/9" + } + }, { + "type": "comments", + "id": "5", + "attributes": { + "body": "First!" + }, + "relationships": { + "author": { + "data": { "type": "people", "id": "2" } + } + }, + "links": { + "self": "http://example.com/comments/5" + } + }, { + "type": "comments", + "id": "12", + "attributes": { + "body": "I like XML better" + }, + "relationships": { + "author": { + "data": { "type": "people", "id": "9" } + } + }, + "links": { + "self": "http://example.com/comments/12" + } + }] +} +``` + +A [compound document] **MUST NOT** include more than one [resource object][resource objects] for +each `type` and `id` pair. + +> Note: In a single document, you can think of the `type` and `id` as a +composite key that uniquely references [resource objects] in another part of +the document. + +> Note: This approach ensures that a single canonical [resource object][resource objects] is +returned with each response, even when the same resource is referenced +multiple times. + +### Meta Information + +Where specified, a `meta` member can be used to include non-standard +meta-information. The value of each `meta` member **MUST** be an object (a +"meta object"). + +Any members **MAY** be specified within `meta` objects. + +For example: + +```json +{ + "meta": { + "copyright": "Copyright 2015 Example Corp.", + "authors": [ + "Yehuda Katz", + "Steve Klabnik", + "Dan Gebhardt", + "Tyler Kellen" + ] + }, + "data": { + // ... + } +} +``` + +### Links + +Where specified, a `links` member can be used to represent links. The value +of each `links` member **MUST** be an object (a "links object"). + +Each member of a links object is a "link". A link **MUST** be represented as +either: + +* a string containing the link's URL. +* an object ("link object") which can + contain the following members: + * `href`: a string containing the link's URL. + * `meta`: a meta object containing non-standard meta-information about the + link. + +The following `self` link is simply a URL: + +```json +"links": { + "self": "http://example.com/posts" +} +``` + +The following `related` link includes a URL as well as meta-information +about a related resource collection: + +```json +"links": { + "related": { + "href": "http://example.com/articles/1/comments", + "meta": { + "count": 10 + } + } +} +``` + +> Note: Additional members may be specified for links objects and link +objects in the future. It is also possible that the allowed values of +additional members will be expanded (e.g. a `collection` link may support an +array of values, whereas a `self` link does not). + +### JSON:API Object + +A JSON:API document **MAY** include information about its implementation +under a top level `jsonapi` member. If present, the value of the `jsonapi` +member **MUST** be an object (a "jsonapi object"). The jsonapi object **MAY** +contain a `version` member whose value is a string indicating the highest JSON +API version supported. This object **MAY** also contain a `meta` member, whose +value is a [meta] object that contains non-standard meta-information. + +```json +{ + "jsonapi": { + "version": "1.0" + } +} +``` + +If the `version` member is not present, clients should assume the server +implements at least version 1.0 of the specification. + +> Note: Because JSON:API is committed to making additive changes only, the +version string primarily indicates which new features a server may support. + +### Member Names + +All member names used in a JSON:API document **MUST** be treated as case sensitive +by clients and servers, and they **MUST** meet all of the following conditions: + +- Member names **MUST** contain at least one character. +- Member names **MUST** contain only the allowed characters listed below. +- Member names **MUST** start and end with a "globally allowed character", + as defined below. + +To enable an easy mapping of member names to URLs, it is **RECOMMENDED** that +member names use only non-reserved, URL safe characters specified in [RFC 3986](https://datatracker.ietf.org/doc/html/rfc3986#section-2.3). + +#### Allowed Characters + +The following "globally allowed characters" **MAY** be used anywhere in a member name: + +- U+0061 to U+007A, "a-z" +- U+0041 to U+005A, "A-Z" +- U+0030 to U+0039, "0-9" +- U+0080 and above (non-ASCII Unicode characters; _not recommended, not URL safe_) + +Additionally, the following characters are allowed in member names, except as the +first or last character: + +- U+002D HYPHEN-MINUS, "-" +- U+005F LOW LINE, "_" +- U+0020 SPACE, " " _(not recommended, not URL safe)_ + +#### Reserved Characters + +The following characters **MUST NOT** be used in member names: + +- U+002B PLUS SIGN, "+" _(used for ordering)_ +- U+002C COMMA, "," _(used as a separator between relationship paths)_ +- U+002E PERIOD, "." _(used as a separator within relationship paths)_ +- U+005B LEFT SQUARE BRACKET, "[" _(used in sparse fieldsets)_ +- U+005D RIGHT SQUARE BRACKET, "]" _(used in sparse fieldsets)_ +- U+0021 EXCLAMATION MARK, "!" +- U+0022 QUOTATION MARK, '"' +- U+0023 NUMBER SIGN, "#" +- U+0024 DOLLAR SIGN, "$" +- U+0025 PERCENT SIGN, "%" +- U+0026 AMPERSAND, "&" +- U+0027 APOSTROPHE, "'" +- U+0028 LEFT PARENTHESIS, "(" +- U+0029 RIGHT PARENTHESIS, ")" +- U+002A ASTERISK, "*" +- U+002F SOLIDUS, "/" +- U+003A COLON, ":" +- U+003B SEMICOLON, ";" +- U+003C LESS-THAN SIGN, "<" +- U+003D EQUALS SIGN, "=" +- U+003E GREATER-THAN SIGN, ">" +- U+003F QUESTION MARK, "?" +- U+0040 COMMERCIAL AT, "@" +- U+005C REVERSE SOLIDUS, "\" +- U+005E CIRCUMFLEX ACCENT, "^" +- U+0060 GRAVE ACCENT, "`" +- U+007B LEFT CURLY BRACKET, "{" +- U+007C VERTICAL LINE, "|" +- U+007D RIGHT CURLY BRACKET, "}" +- U+007E TILDE, "~" +- U+007F DELETE +- U+0000 to U+001F (C0 Controls) + +## Fetching Data + +Data, including resources and relationships, can be fetched by sending a +`GET` request to an endpoint. + +Responses can be further refined with the optional features described below. + +### Fetching Resources + +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 + +For example, the following request fetches a collection of articles: + +```http +GET /articles HTTP/1.1 +Accept: application/vnd.api+json +``` + +The following request fetches an article: + +```http +GET /articles/1 HTTP/1.1 +Accept: application/vnd.api+json +``` + +And the following request fetches an article's author: + +```http +GET /articles/1/author HTTP/1.1 +Accept: application/vnd.api+json +``` + +#### Responses + +##### 200 OK + +A server **MUST** respond to a successful request to fetch an individual +resource or resource collection with a `200 OK` response. + +A server **MUST** respond to a successful request to fetch a resource +collection with an array of [resource objects] or an empty array (`[]`) as +the response document's primary data. + +For example, a `GET` request to a collection of articles could return: + +```http +HTTP/1.1 200 OK +Content-Type: application/vnd.api+json + +{ + "links": { + "self": "http://example.com/articles" + }, + "data": [{ + "type": "articles", + "id": "1", + "attributes": { + "title": "JSON:API paints my bikeshed!" + } + }, { + "type": "articles", + "id": "2", + "attributes": { + "title": "Rails is Omakase" + } + }] +} +``` + +A similar response representing an empty collection would be: + +```http +HTTP/1.1 200 OK +Content-Type: application/vnd.api+json + +{ + "links": { + "self": "http://example.com/articles" + }, + "data": [] +} +``` + +A server **MUST** respond to a successful request to fetch an individual +resource with a [resource object][resource objects] or `null` provided as +the response document's primary data. + +`null` is only an appropriate response when the requested URL is one that +might correspond to a single resource, but doesn't currently. + +> Note: Consider, for example, a request to fetch a to-one related resource link. +This request would respond with `null` when the relationship is empty (such that +the link is corresponding to no resources) but with the single related resource's +[resource object][resource objects] otherwise. + +For example, a `GET` request to an individual article could return: + +```http +HTTP/1.1 200 OK +Content-Type: application/vnd.api+json + +{ + "links": { + "self": "http://example.com/articles/1" + }, + "data": { + "type": "articles", + "id": "1", + "attributes": { + "title": "JSON:API paints my bikeshed!" + }, + "relationships": { + "author": { + "links": { + "related": "http://example.com/articles/1/author" + } + } + } + } +} +``` + +If the above article's author is missing, then a `GET` request to that related +resource would return: + +```http +HTTP/1.1 200 OK +Content-Type: application/vnd.api+json + +{ + "links": { + "self": "http://example.com/articles/1/author" + }, + "data": null +} +``` + +##### 404 Not Found + +A server **MUST** respond with `404 Not Found` when processing a request to +fetch a single resource that does not exist, except when the request warrants a +`200 OK` response with `null` as the primary data (as described above). + +##### Other Responses + +A server **MAY** respond with other HTTP status codes. + +A server **MAY** include [error details] with error responses. + +A server **MUST** prepare responses, and a client **MUST** interpret +responses, in accordance with +[`HTTP semantics`](http://tools.ietf.org/html/rfc7231). + +### Fetching Relationships + +A server **MUST** support fetching relationship data for every relationship URL +provided as a `self` link as part of a relationship's `links` object. + +For example, the following request fetches data about an article's comments: + +```http +GET /articles/1/relationships/comments HTTP/1.1 +Accept: application/vnd.api+json +``` + +And the following request fetches data about an article's author: + +```http +GET /articles/1/relationships/author HTTP/1.1 +Accept: application/vnd.api+json +``` + +#### Responses + +##### 200 OK + +A server **MUST** respond to a successful request to fetch a relationship +with a `200 OK` response. + +The primary data in the response document **MUST** match the appropriate +value for [resource linkage], as described above for +[relationship objects][relationships]. + +The top-level [links object][links] **MAY** contain `self` and `related` links, +as described above for [relationship objects][relationships]. + +For example, a `GET` request to a URL from a to-one relationship link could +return: + +```http +HTTP/1.1 200 OK +Content-Type: application/vnd.api+json + +{ + "links": { + "self": "/articles/1/relationships/author", + "related": "/articles/1/author" + }, + "data": { + "type": "people", + "id": "12" + } +} +``` + +If the above relationship is empty, then a `GET` request to the same URL would +return: + +```http +HTTP/1.1 200 OK +Content-Type: application/vnd.api+json + +{ + "links": { + "self": "/articles/1/relationships/author", + "related": "/articles/1/author" + }, + "data": null +} +``` + +A `GET` request to a URL from a to-many relationship link could return: + +```http +HTTP/1.1 200 OK +Content-Type: application/vnd.api+json + +{ + "links": { + "self": "/articles/1/relationships/tags", + "related": "/articles/1/tags" + }, + "data": [ + { "type": "tags", "id": "2" }, + { "type": "tags", "id": "3" } + ] +} +``` + +If the above relationship is empty, then a `GET` request to the same URL would +return: + +```http +HTTP/1.1 200 OK +Content-Type: application/vnd.api+json + +{ + "links": { + "self": "/articles/1/relationships/tags", + "related": "/articles/1/tags" + }, + "data": [] +} +``` + +##### 404 Not Found + +A server **MUST** return `404 Not Found` when processing a request to fetch +a relationship link URL that does not exist. + +> Note: This can happen when the parent resource of the relationship +does not exist. For example, when `/articles/1` does not exist, request to +`/articles/1/relationships/tags` returns `404 Not Found`. + +If a relationship link URL exists but the relationship is empty, then +`200 OK` **MUST** be returned, as described above. + +##### Other Responses + +A server **MAY** respond with other HTTP status codes. + +A server **MAY** include [error details] with error responses. + +A server **MUST** prepare responses, and a client **MUST** interpret +responses, in accordance with +[`HTTP semantics`](http://tools.ietf.org/html/rfc7231). + +### Inclusion of Related Resources + +An endpoint **MAY** return resources related to the primary data by default. + +An endpoint **MAY** also support an `include` request parameter to allow the +client to customize which related resources should be returned. + +If an endpoint does not support the `include` parameter, it **MUST** respond +with `400 Bad Request` to any requests that include it. + +If an endpoint supports the `include` parameter and a client supplies it, +the server **MUST NOT** include unrequested [resource objects] in the `included` +section of the [compound document]. + +The value of the `include` parameter **MUST** be a comma-separated (U+002C +COMMA, ",") list of relationship paths. A relationship path is a dot-separated +(U+002E FULL-STOP, ".") list of [relationship][relationships] names. + +If a server is unable to identify a relationship path or does not support +inclusion of resources from a path, it **MUST** respond with 400 Bad Request. + +> Note: For example, a relationship path could be `comments.author`, where +`comments` is a relationship listed under a `articles` [resource object][resource objects], and +`author` is a relationship listed under a `comments` [resource object][resource objects]. + +For instance, comments could be requested with an article: + +```http +GET /articles/1?include=comments HTTP/1.1 +Accept: application/vnd.api+json +``` + +In order to request resources related to other resources, a dot-separated path +for each relationship name can be specified: + +```http +GET /articles/1?include=comments.author HTTP/1.1 +Accept: application/vnd.api+json +``` + +> Note: Because [compound documents][compound document] require full linkage +(except when relationship linkage is excluded by sparse fieldsets), intermediate +resources in a multi-part path must be returned along with the leaf nodes. For +example, a response to a request for `comments.author` should include `comments` +as well as the `author` of each of those `comments`. + +> Note: A server may choose to expose a deeply nested relationship such as +`comments.author` as a direct relationship with an alias such as +`comment-authors`. This would allow a client to request +`/articles/1?include=comment-authors` instead of +`/articles/1?include=comments.author`. By abstracting the nested +relationship with an alias, the server can still provide full linkage in +compound documents without including potentially unwanted intermediate +resources. + +Multiple related resources can be requested in a comma-separated list: + +```http +GET /articles/1?include=comments.author,ratings HTTP/1.1 +Accept: application/vnd.api+json +``` + +Furthermore, related resources can be requested from a relationship endpoint: + +```http +GET /articles/1/relationships/comments?include=comments.author HTTP/1.1 +Accept: application/vnd.api+json +``` + +In this case, the primary data would be a collection of +[resource identifier objects][resource identifier object] that represent linkage to comments for an article, +while the full comments and comment authors would be returned as included data. + +> Note: This section applies to any endpoint that responds with primary +data, regardless of the request type. For instance, a server could support +the inclusion of related resources along with a `POST` request to create a +resource or relationship. + +### Sparse Fieldsets + +A client **MAY** request that an endpoint return only specific [fields] in the +response on a per-type basis by including a `fields[TYPE]` parameter. + +The value of the `fields` parameter **MUST** be a comma-separated (U+002C +COMMA, ",") list that refers to the name(s) of the fields to be returned. +An empty value indicates that no fields should be returned. + +If a client requests a restricted set of [fields] for a given resource type, +an endpoint **MUST NOT** include additional [fields] in resource objects of +that type in its response. + +If a client does not specify the set of [fields] for a given resource type, +the server **MAY** send all fields, a subset of fields, or no fields for that +resource type. + +```http +GET /articles?include=author&fields[articles]=title,body&fields[people]=name HTTP/1.1 +Accept: application/vnd.api+json +``` + +> Note: The above example URI shows unencoded `[` and `]` characters simply for +readability. In practice, these characters must be percent-encoded, per the +requirements [in RFC 3986](http://tools.ietf.org/html/rfc3986#section-3.4). + +> Note: This section applies to any endpoint that responds with resources as +primary or included data, regardless of the request type. For instance, a +server could support sparse fieldsets along with a `POST` request to create +a resource. + +### Sorting + +A server **MAY** choose to support requests to sort resource collections +according to one or more criteria ("sort fields"). + +> Note: Although recommended, sort fields do not necessarily need to +correspond to resource attribute and association names. + +> Note: It is recommended that dot-separated (U+002E FULL-STOP, ".") sort +fields be used to request sorting based upon relationship attributes. For +example, a sort field of `author.name` could be used to request that the +primary data be sorted based upon the `name` attribute of the `author` +relationship. + +An endpoint **MAY** support requests to sort the primary data with a `sort` +query parameter. The value for `sort` **MUST** represent sort fields. + +```http +GET /people?sort=age HTTP/1.1 +Accept: application/vnd.api+json +``` + +An endpoint **MAY** support multiple sort fields by allowing comma-separated +(U+002C COMMA, ",") sort fields. Sort fields **SHOULD** be applied in the +order specified. + +```http +GET /people?sort=age,name HTTP/1.1 +Accept: application/vnd.api+json +``` + +The sort order for each sort field **MUST** be ascending unless it is prefixed +with a minus (U+002D HYPHEN-MINUS, "-"), in which case it **MUST** be descending. + +```http +GET /articles?sort=-created,title HTTP/1.1 +Accept: application/vnd.api+json +``` + +The above example should return the newest articles first. Any articles +created on the same date will then be sorted by their title in ascending +alphabetical order. + +If the server does not support sorting as specified in the query parameter +`sort`, it **MUST** return `400 Bad Request`. + +If sorting is supported by the server and requested by the client via query +parameter `sort`, the server **MUST** return elements of the top-level +`data` array of the response ordered according to the criteria specified. +The server **MAY** apply default sorting rules to top-level `data` if +request parameter `sort` is not specified. + +> Note: This section applies to any endpoint that responds with a resource +collection as primary data, regardless of the request type. + +### Pagination + +A server **MAY** choose to limit the number of resources returned in a response +to a subset ("page") of the whole set available. + +A server **MAY** provide links to traverse a paginated data set ("pagination +links"). + +Pagination links **MUST** appear in the links object that corresponds to a +collection. To paginate the primary data, supply pagination links in the +top-level `links` object. To paginate an included collection returned in +a [compound document], supply pagination links in the corresponding links +object. + +The following keys **MUST** be used for pagination links: + +* `first`: the first page of data +* `last`: the last page of data +* `prev`: the previous page of data +* `next`: the next page of data + +Keys **MUST** either be omitted or have a `null` value to indicate that a +particular link is unavailable. + +Concepts of order, as expressed in the naming of pagination links, **MUST** +remain consistent with JSON:API's [sorting rules](#fetching-sorting). + +The `page` query parameter is reserved for pagination. Servers and clients +**SHOULD** use this key for pagination operations. + +> Note: JSON:API is agnostic about the pagination strategy used by a server. +Effective pagination strategies include (but are not limited to): +page-based, offset-based, and cursor-based. The `page` query parameter can +be used as a basis for any of these strategies. For example, a page-based +strategy might use query parameters such as `page[number]` and `page[size]`, +an offset-based strategy might use `page[offset]` and `page[limit]`, while a +cursor-based strategy might use `page[cursor]`. + +> Note: The example query parameters above use unencoded `[` and `]` characters +simply for readability. In practice, these characters must be percent-encoded, +per the requirements in [RFC 3986](http://tools.ietf.org/html/rfc3986#section-3.4). + +> Note: This section applies to any endpoint that responds with a resource +collection as primary data, regardless of the request type. + +### Filtering + +The `filter` query parameter is reserved for filtering data. Servers and clients +**SHOULD** use this key for filtering operations. + +> Note: JSON:API is agnostic about the strategies supported by a server. The +`filter` query parameter can be used as the basis for any number of filtering +strategies. + +## Creating, Updating and Deleting Resources + +A server **MAY** allow resources of a given type to be created. It **MAY** +also allow existing resources to be modified or deleted. + +A request **MUST** completely succeed or fail (in a single "transaction"). No +partial updates are allowed. + +> Note: The `type` member is required in every [resource object][resource objects] throughout requests and +responses in JSON:API. There are some cases, such as when `POST`ing to an +endpoint representing heterogeneous data, when the `type` could not be inferred +from the endpoint. However, picking and choosing when it is required would be +confusing; it would be hard to remember when it was required and when it was +not. Therefore, to improve consistency and minimize confusion, `type` is +always required. + +### Creating Resources + +A resource can be created by sending a `POST` request to a URL that represents +a collection of resources. The request **MUST** include a single [resource object][resource objects] +as primary data. The [resource object][resource objects] **MUST** contain at least a `type` member. + +For instance, a new photo might be created with the following request: + +```http +POST /photos HTTP/1.1 +Content-Type: application/vnd.api+json +Accept: application/vnd.api+json + +{ + "data": { + "type": "photos", + "attributes": { + "title": "Ember Hamster", + "src": "http://example.com/images/productivity.png" + }, + "relationships": { + "photographer": { + "data": { "type": "people", "id": "9" } + } + } + } +} +``` + +If a relationship is provided in the `relationships` member of the +[resource object][resource objects], its value **MUST** be a relationship object with a `data` +member. The value of this key represents the [linkage][resource linkage] the new resource is to +have. + +#### Client-Generated IDs + +A server **MAY** accept a client-generated ID along with a request to create +a resource. An ID **MUST** be specified with an `id` key, the value of +which **MUST** be a universally unique identifier. The client **SHOULD** use +a properly generated and formatted *UUID* as described in RFC 4122 +[[RFC4122](http://tools.ietf.org/html/rfc4122.html)]. + +> NOTE: In some use-cases, such as importing data from another source, it +may be possible to use something other than a UUID that is still guaranteed +to be globally unique. Do not use anything other than a UUID unless you are +100% confident that the strategy you are using indeed generates globally +unique identifiers. + +For example: + +```http +POST /photos HTTP/1.1 +Content-Type: application/vnd.api+json +Accept: application/vnd.api+json + +{ + "data": { + "type": "photos", + "id": "550e8400-e29b-41d4-a716-446655440000", + "attributes": { + "title": "Ember Hamster", + "src": "http://example.com/images/productivity.png" + } + } +} +``` + +A server **MUST** return `403 Forbidden` in response to an unsupported request +to create a resource with a client-generated ID. + +#### Responses + +##### 201 Created + +If a `POST` request did not include a [Client-Generated +ID](#crud-creating-client-ids) and the requested resource has been created +successfully, the server **MUST** return a `201 Created` status code. + +The response **SHOULD** include a `Location` header identifying the location +of the newly created resource. + +The response **MUST** also include a document that contains the primary +resource created. + +If the [resource object][resource objects] returned by the response contains a `self` key in its +`links` member and a `Location` header is provided, the value of the `self` +member **MUST** match the value of the `Location` header. + +```http +HTTP/1.1 201 Created +Location: http://example.com/photos/550e8400-e29b-41d4-a716-446655440000 +Content-Type: application/vnd.api+json + +{ + "data": { + "type": "photos", + "id": "550e8400-e29b-41d4-a716-446655440000", + "attributes": { + "title": "Ember Hamster", + "src": "http://example.com/images/productivity.png" + }, + "links": { + "self": "http://example.com/photos/550e8400-e29b-41d4-a716-446655440000" + } + } +} +``` + +##### 202 Accepted + +If a request to create a resource has been accepted for processing, but the +processing has not been completed by the time the server responds, the +server **MUST** return a `202 Accepted` status code. + +##### 204 No Content + +If a `POST` request *did* include a [Client-Generated +ID](#crud-creating-client-ids) and the requested resource has been created +successfully, the server **MUST** return either a `201 Created` status code +and response document (as described above) or a `204 No Content` status code +with no response document. + +> Note: If a `204` response is received the client should consider the resource +object sent in the request to be accepted by the server, as if the server +had returned it back in a `201` response. + +##### 403 Forbidden + +A server **MAY** return `403 Forbidden` in response to an unsupported request +to create a resource. + +##### 404 Not Found + +A server **MUST** return `404 Not Found` when processing a request that +references a related resource that does not exist. + +##### 409 Conflict + +A server **MUST** return `409 Conflict` when processing a `POST` request to +create a resource with a client-generated ID that already exists. + +A server **MUST** return `409 Conflict` when processing a `POST` request in +which the [resource object][resource objects]'s `type` is not among the type(s) that constitute the +collection represented by the endpoint. + +A server **SHOULD** include error details and provide enough information to +recognize the source of the conflict. + +##### Other Responses + +A server **MAY** respond with other HTTP status codes. + +A server **MAY** include [error details] with error responses. + +A server **MUST** prepare responses, and a client **MUST** interpret +responses, in accordance with +[`HTTP semantics`](http://tools.ietf.org/html/rfc7231). + +### Updating Resources + +A resource can be updated by sending a `PATCH` request to the URL that +represents the resource. + +The URL for a resource can be obtained in the `self` link of the resource +object. Alternatively, when a `GET` request returns a single [resource object][resource objects] as +primary data, the same request URL can be used for updates. + +The `PATCH` request **MUST** include a single [resource object][resource objects] as primary data. +The [resource object][resource objects] **MUST** contain `type` and `id` members. + +For example: + +```http +PATCH /articles/1 HTTP/1.1 +Content-Type: application/vnd.api+json +Accept: application/vnd.api+json + +{ + "data": { + "type": "articles", + "id": "1", + "attributes": { + "title": "To TDD or Not" + } + } +} +``` + +#### Updating a Resource's Attributes + +Any or all of a resource's [attributes] **MAY** be included in the resource +object included in a `PATCH` request. + +If a request does not include all of the [attributes] for a resource, the server +**MUST** interpret the missing [attributes] as if they were included with their +current values. The server **MUST NOT** interpret missing attributes as `null` +values. + +For example, the following `PATCH` request is interpreted as a request to +update only the `title` and `text` attributes of an article: + +```http +PATCH /articles/1 HTTP/1.1 +Content-Type: application/vnd.api+json +Accept: application/vnd.api+json + +{ + "data": { + "type": "articles", + "id": "1", + "attributes": { + "title": "To TDD or Not", + "text": "TLDR; It's complicated... but check your test coverage regardless." + } + } +} +``` + +#### Updating a Resource's Relationships + +Any or all of a resource's [relationships] **MAY** be included in the resource +object included in a `PATCH` request. + +If a request does not include all of the [relationships] for a resource, the server +**MUST** interpret the missing [relationships] as if they were included with their +current values. It **MUST NOT** interpret them as `null` or empty values. + +If a relationship is provided in the `relationships` member of a resource +object in a `PATCH` request, its value **MUST** be a relationship object +with a `data` member. The relationship's value will be replaced with the +value specified in this member. + +For instance, the following `PATCH` request will update the `author` relationship of an article: + +```http +PATCH /articles/1 HTTP/1.1 +Content-Type: application/vnd.api+json +Accept: application/vnd.api+json + +{ + "data": { + "type": "articles", + "id": "1", + "relationships": { + "author": { + "data": { "type": "people", "id": "1" } + } + } + } +} +``` + +Likewise, the following `PATCH` request performs a complete replacement of +the `tags` for an article: + +```http +PATCH /articles/1 HTTP/1.1 +Content-Type: application/vnd.api+json +Accept: application/vnd.api+json + +{ + "data": { + "type": "articles", + "id": "1", + "relationships": { + "tags": { + "data": [ + { "type": "tags", "id": "2" }, + { "type": "tags", "id": "3" } + ] + } + } + } +} +``` + +A server **MAY** reject an attempt to do a full replacement of a to-many +relationship. In such a case, the server **MUST** reject the entire update, +and return a `403 Forbidden` response. + +> Note: Since full replacement may be a very dangerous operation, a server +may choose to disallow it. For example, a server may reject full replacement if +it has not provided the client with the full list of associated objects, and +does not want to allow deletion of records the client has not seen. + +#### Responses + +##### 202 Accepted + +If an update request has been accepted for processing, but the processing +has not been completed by the time the server responds, the server **MUST** +return a `202 Accepted` status code. + +##### 200 OK + +If a server accepts an update but also changes the resource(s) in ways other +than those specified by the request (for example, updating the `updated-at` +attribute or a computed `sha`), it **MUST** return a `200 OK` response. The +response document **MUST** include a representation of the updated +resource(s) as if a `GET` request was made to the request URL. + +A server **MUST** return a `200 OK` status code if an update is successful, +the client's current fields remain up to date, and the server responds only +with top-level [meta] data. In this case the server **MUST NOT** include a +representation of the updated resource(s). + +##### 204 No Content + +If an update is successful and the server doesn't update any fields besides +those provided, the server **MUST** return either a `200 OK` status code and +response document (as described above) or a `204 No Content` status code with no +response document. + +##### 403 Forbidden + +A server **MUST** return `403 Forbidden` in response to an unsupported request +to update a resource or relationship. + +##### 404 Not Found + +A server **MUST** return `404 Not Found` when processing a request to modify +a resource that does not exist. + +A server **MUST** return `404 Not Found` when processing a request that +references a related resource that does not exist. + +##### 409 Conflict + +A server **MAY** return `409 Conflict` when processing a `PATCH` request to +update a resource if that update would violate other server-enforced +constraints (such as a uniqueness constraint on a property other than `id`). + +A server **MUST** return `409 Conflict` when processing a `PATCH` request in +which the resource object's `type` and `id` do not match the server's endpoint. + +A server **SHOULD** include error details and provide enough information to +recognize the source of the conflict. + +##### Other Responses + +A server **MAY** respond with other HTTP status codes. + +A server **MAY** include [error details] with error responses. + +A server **MUST** prepare responses, and a client **MUST** interpret +responses, in accordance with +[`HTTP semantics`](http://tools.ietf.org/html/rfc7231). + +### Updating Relationships + +Although relationships can be modified along with resources (as described +above), JSON:API also supports updating of relationships independently at +URLs from [relationship links][relationships]. + +> Note: Relationships are updated without exposing the underlying server +semantics, such as foreign keys. Furthermore, relationships can be updated +without necessarily affecting the related resources. For example, if an article +has many authors, it is possible to remove one of the authors from the article +without deleting the person itself. Similarly, if an article has many tags, it +is possible to add or remove tags. Under the hood on the server, the first +of these examples might be implemented with a foreign key, while the second +could be implemented with a join table, but the JSON:API protocol would be +the same in both cases. + +> Note: A server may choose to delete the underlying resource if a +relationship is deleted (as a garbage collection measure). + +#### Updating To-One Relationships + +A server **MUST** respond to `PATCH` requests to a URL from a to-one +[relationship link][relationships] as described below. + +The `PATCH` request **MUST** include a top-level member named `data` containing +one of: + +* a [resource identifier object] corresponding to the new related resource. +* `null`, to remove the relationship. + +For example, the following request updates the author of an article: + +```http +PATCH /articles/1/relationships/author HTTP/1.1 +Content-Type: application/vnd.api+json +Accept: application/vnd.api+json + +{ + "data": { "type": "people", "id": "12" } +} +``` + +And the following request clears the author of the same article: + +```http +PATCH /articles/1/relationships/author HTTP/1.1 +Content-Type: application/vnd.api+json +Accept: application/vnd.api+json + +{ + "data": null +} +``` + +If the relationship is updated successfully then the server **MUST** return +a successful response. + +#### Updating To-Many Relationships + +A server **MUST** respond to `PATCH`, `POST`, and `DELETE` requests to a +URL from a to-many [relationship link][relationships] as described below. + +For all request types, the body **MUST** contain a `data` member whose value +is an empty array or an array of [resource identifier objects][resource identifier object]. + +If a client makes a `PATCH` request to a URL from a to-many +[relationship link][relationships], the server **MUST** either completely +replace every member of the relationship, return an appropriate error response +if some resources can not be found or accessed, or return a `403 Forbidden` +response if complete replacement is not allowed by the server. + +For example, the following request replaces every tag for an article: + +```http +PATCH /articles/1/relationships/tags HTTP/1.1 +Content-Type: application/vnd.api+json +Accept: application/vnd.api+json + +{ + "data": [ + { "type": "tags", "id": "2" }, + { "type": "tags", "id": "3" } + ] +} +``` + +And the following request clears every tag for an article: + +```http +PATCH /articles/1/relationships/tags HTTP/1.1 +Content-Type: application/vnd.api+json +Accept: application/vnd.api+json + +{ + "data": [] +} +``` + +If a client makes a `POST` request to a URL from a +[relationship link][relationships], the server **MUST** add the specified +members to the relationship unless they are already present. If a given `type` +and `id` is already in the relationship, the server **MUST NOT** add it again. + +> Note: This matches the semantics of databases that use foreign keys for +has-many relationships. Document-based storage should check the has-many +relationship before appending to avoid duplicates. + +If all of the specified resources can be added to, or are already present +in, the relationship then the server **MUST** return a successful response. + +> Note: This approach ensures that a request is successful if the server's +state matches the requested state, and helps avoid pointless race conditions +caused by multiple clients making the same changes to a relationship. + +In the following example, the comment with ID `123` is added to the list of +comments for the article with ID `1`: + +```http +POST /articles/1/relationships/comments HTTP/1.1 +Content-Type: application/vnd.api+json +Accept: application/vnd.api+json + +{ + "data": [ + { "type": "comments", "id": "123" } + ] +} +``` + +If the client makes a `DELETE` request to a URL from a +[relationship link][relationships] the server **MUST** delete the specified +members from the relationship or return a `403 Forbidden` response. If all of +the specified resources are able to be removed from, or are already missing +from, the relationship then the server **MUST** return a successful response. + +> Note: As described above for `POST` requests, this approach helps avoid +pointless race conditions between multiple clients making the same changes. + +Relationship members are specified in the same way as in the `POST` request. + +In the following example, comments with IDs of `12` and `13` are removed +from the list of comments for the article with ID `1`: + +```http +DELETE /articles/1/relationships/comments HTTP/1.1 +Content-Type: application/vnd.api+json +Accept: application/vnd.api+json + +{ + "data": [ + { "type": "comments", "id": "12" }, + { "type": "comments", "id": "13" } + ] +} +``` + +> Note: RFC 7231 specifies that a DELETE request may include a body, but +that a server may reject the request. This spec defines the semantics of a +server, and we are defining its semantics for JSON:API. + +#### Responses + +##### 202 Accepted + +If a relationship update request has been accepted for processing, but the +processing has not been completed by the time the server responds, the +server **MUST** return a `202 Accepted` status code. + +##### 204 No Content + +A server **MUST** return a `204 No Content` status code if an update is +successful and the representation of the resource in the request matches the +result. + +> Note: This is the appropriate response to a `POST` request sent to a URL +from a to-many [relationship link][relationships] when that relationship already +exists. It is also the appropriate response to a `DELETE` request sent to a URL +from a to-many [relationship link][relationships] when that relationship does +not exist. + +##### 200 OK + +If a server accepts an update but also changes the targeted relationship(s) +in other ways than those specified by the request, it **MUST** return a `200 +OK` response. The response document **MUST** include a representation of the +updated relationship(s). + +A server **MUST** return a `200 OK` status code if an update is successful, +the client's current data remain up to date, and the server responds +only with top-level [meta] data. In this case the server **MUST NOT** +include a representation of the updated relationship(s). + +##### 403 Forbidden + +A server **MUST** return `403 Forbidden` in response to an unsupported request +to update a relationship. + +##### Other Responses + +A server **MAY** respond with other HTTP status codes. + +A server **MAY** include [error details] with error responses. + +A server **MUST** prepare responses, and a client **MUST** interpret +responses, in accordance with +[`HTTP semantics`](http://tools.ietf.org/html/rfc7231). + +### Deleting Resources + +An individual resource can be *deleted* by making a `DELETE` request to the +resource's URL: + +```http +DELETE /photos/1 HTTP/1.1 +Accept: application/vnd.api+json +``` + +#### Responses + +##### 202 Accepted + +If a deletion request has been accepted for processing, but the processing has +not been completed by the time the server responds, the server **MUST** +return a `202 Accepted` status code. + +##### 204 No Content + +A server **MUST** return a `204 No Content` status code if a deletion +request is successful and no content is returned. + +##### 200 OK + +A server **MUST** return a `200 OK` status code if a deletion request is +successful and the server responds with only top-level [meta] data. + +##### 404 NOT FOUND + +A server **SHOULD** return a `404 Not Found` status code if a deletion request fails +due to the resource not existing. + +##### Other Responses + +A server **MAY** respond with other HTTP status codes. + +A server **MAY** include [error details] with error responses. + +A server **MUST** prepare responses, and a client **MUST** interpret +responses, in accordance with +[`HTTP semantics`](http://tools.ietf.org/html/rfc7231). + +## Query Parameters + +Implementation specific query parameters **MUST** adhere to the same constraints +as [member names] with the additional requirement that they **MUST** contain at +least one non a-z character (U+0061 to U+007A). It is **RECOMMENDED** that a +U+002D HYPHEN-MINUS, "-", U+005F LOW LINE, "_", or capital letter is used +(e.g. camelCasing). + +If a server encounters a query parameter that does not follow the naming +conventions above, and the server does not know how to process it as a query +parameter from this specification, it **MUST** return `400 Bad Request`. + +> Note: This is to preserve the ability of JSON:API to make additive additions +to standard query parameters without conflicting with existing implementations. + +## Errors + +### Processing Errors + +A server **MAY** choose to stop processing as soon as a problem is encountered, +or it **MAY** continue processing and encounter multiple problems. For instance, +a server might process multiple attributes and then return multiple validation +problems in a single response. + +When a server encounters multiple problems for a single request, the most +generally applicable HTTP error code **SHOULD** be used in the response. For +instance, `400 Bad Request` might be appropriate for multiple 4xx errors +or `500 Internal Server Error` might be appropriate for multiple 5xx errors. + +### Error Objects + +Error objects provide additional information about problems encountered while +performing an operation. Error objects **MUST** be returned as an array +keyed by `errors` in the top level of a JSON:API document. + +An error object **MAY** have the following members: + +* `id`: a unique identifier for this particular occurrence of the problem. +* `links`: a [links object][links] containing the following members: + * `about`: a [link][links] that leads to further details about this + particular occurrence of the problem. +* `status`: the HTTP status code applicable to this problem, expressed as a + string value. +* `code`: an application-specific error code, expressed as a string value. +* `title`: a short, human-readable summary of the problem that **SHOULD NOT** + change from occurrence to occurrence of the problem, except for purposes of + localization. +* `detail`: a human-readable explanation specific to this occurrence of the + problem. Like `title`, this field's value can be localized. +* `source`: an object containing references to the source of the error, + optionally including any of the following members: + * `pointer`: a JSON Pointer [[RFC6901](https://tools.ietf.org/html/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]. + * `parameter`: a string indicating which URI query parameter caused + the error. +* `meta`: a [meta object][meta] containing non-standard meta-information about the + error. + +[resource objects]: #document-resource-objects +[attributes]: #document-resource-object-attributes +[relationships]: #document-resource-object-relationships +[fields]: #document-resource-object-fields +[related resource link]: #document-resource-object-related-resource-links +[resource linkage]: #document-resource-object-linkage +[resource links]: #document-resource-object-links +[resource identifier object]: #document-resource-identifier-objects +[compound document]: #document-compound-documents +[meta]: #document-meta +[links]: #document-links +[error details]: #errors +[member names]: #document-member-names +[pagination]: #fetching-pagination diff --git a/tests/files/format_1.0/normative-statements.json b/tests/files/format_1.0/normative-statements.json new file mode 100644 index 0000000..d6adef8 --- /dev/null +++ b/tests/files/format_1.0/normative-statements.json @@ -0,0 +1,2681 @@ +{ + "jsonapi": { + "version": "1.0" + }, + "data": [ + { + "id": "content-negotiation", + "type": "sections", + "attributes": { + "title": "Content Negotiation" + }, + "links": { + "self": "http://jsonapi.org/format/#content-negotiation" + }, + "relationships": { + "statements": { + "data": [ + { "id": "request-content-type", "type": "normative-statements" }, + { "id": "request-accept", "type": "normative-statements" }, + { "id": "response-ignore-parameters", "type": "normative-statements" }, + { "id": "response-content-type", "type": "normative-statements"}, + { "id": "response-unsupported-media-type", "type": "normative-statements"}, + { "id": "response-not-acceptable", "type": "normative-statements"} + ] + } + } + }, + { + "id": "document-structure", + "type": "sections", + "attributes": { + "title": "Document Structure" + }, + "links": { + "self": "http://jsonapi.org/format/#document-structure" + }, + "relationships": { + "statements": { + "data": [ + { "id": "additional-members", "type": "normative-statements" }, + { "id": "ignore-additional-members", "type": "normative-statements" }, + { "id": "json-object", "type": "normative-statements" }, + { "id": "required-top-level", "type": "normative-statements" }, + { "id": "data-errors", "type": "normative-statements" }, + { "id": "optional-top-level", "type": "normative-statements" }, + { "id": "data-included", "type": "normative-statements" }, + { "id": "top-level-links", "type": "normative-statements" }, + { "id": "primary-data", "type": "normative-statements" }, + { "id": "logical-collection", "type": "normative-statements" }, + { "id": "resource-required-top-level", "type": "normative-statements" }, + { "id": "resource-optional-top-level", "type": "normative-statements" }, + { "id": "resource-id-type", "type": "normative-statements" }, + { "id": "resource-id-type-types", "type": "normative-statements" }, + { "id": "resource-unique", "type": "normative-statements" }, + { "id": "resource-type-constraints", "type": "normative-statements" }, + { "id": "resource-fields", "type": "normative-statements" }, + { "id": "resource-attributes-key", "type": "normative-statements" }, + { "id": "resource-attributes-reserve-members", "type": "normative-statements" }, + { "id": "resource-attributes-reserve-members", "type": "normative-statements" }, + { "id": "resource-relationships-key", "type": "normative-statements" }, + { "id": "resource-relationships-object", "type": "normative-statements" }, + { "id": "resource-relationships-pagination", "type": "normative-statements" }, + { "id": "resource-related-resource-link", "type": "normative-statements" }, + { "id": "resource-related-resource-link-change", "type": "normative-statements" }, + { "id": "resource-linkage", "type": "normative-statements" }, + { "id": "resource-links", "type": "normative-statements" }, + { "id": "resource-link-response", "type": "normative-statements" }, + { "id": "resource-identifier-required-members", "type": "normative-statements" }, + { "id": "resource-identifier-optional-member", "type": "normative-statements" }, + { "id": "compound-documents-allow", "type": "normative-statements" }, + { "id": "compound-documents-top-level-included", "type": "normative-statements" }, + { "id": "compound-documents-full-linkage", "type": "normative-statements" }, + { "id": "compound-documents-duplicates", "type": "normative-statements" }, + { "id": "meta-objects", "type": "normative-statements" }, + { "id": "meta-object-members", "type": "normative-statements" }, + { "id": "top-level-links", "type": "normative-statements" }, + { "id": "top-level-links-members", "type": "normative-statements" }, + { "id": "top-level-json-api-member", "type": "normative-statements" }, + { "id": "json-api-type", "type": "normative-statements" }, + { "id": "json-api-version", "type": "normative-statements" }, + { "id": "json-api-meta", "type": "normative-statements" }, + { "id": "member-name-case", "type": "normative-statements" }, + { "id": "member-name-character", "type": "normative-statements" }, + { "id": "member-name-allowed-characters-only", "type": "normative-statements" }, + { "id": "member-name-globally-allowed", "type": "normative-statements" }, + { "id": "member-name-url-safe", "type": "normative-statements" }, + { "id": "member-name-allowed-characters", "type": "normative-statements" }, + { "id": "member-name-reserved-characters", "type": "normative-statements" } + ] + } + } + }, + { + "id": "reading", + "type": "sections", + "attributes": { + "title": "Fetching Data" + }, + "links": { + "self": "http://jsonapi.org/format/#fetching" + }, + "relationships": { + "statements": { + "data": [ + { "id": "fetch-url-support", "type": "normative-statements" }, + { "id": "fetch-response-code", "type": "normative-statements" }, + { "id": "fetch-primary-data-collection", "type": "normative-statements" }, + { "id": "fetch-primary-data-single", "type": "normative-statements" }, + { "id": "fetch-responses-404", "type": "normative-statements" }, + { "id": "fetch-responses-other-status-codes", "type": "normative-statements" }, + { "id": "fetch-responses-error-details", "type": "normative-statements" }, + { "id": "fetch-responses-http-semantics", "type": "normative-statements" }, + { "id": "fetch-relationships", "type": "normative-statements" }, + { "id": "fetch-relationships-response-200", "type": "normative-statements" }, + { "id": "fetch-relationships-response-200-primary-data", "type": "normative-statements" }, + { "id": "fetch-relationships-response-200-self-related", "type": "normative-statements" }, + { "id": "fetch-relationships-response-404", "type": "normative-statements" }, + { "id": "fetch-relationships-response-exists-empty", "type": "normative-statements" }, + { "id": "fetch-relationships-other-status-codes", "type": "normative-statements" }, + { "id": "fetch-relationships-other-error-details", "type": "normative-statements" }, + { "id": "fetch-relationships-http-semantics", "type": "normative-statements" }, + { "id": "inclusion-default", "type": "normative-statements" }, + { "id": "inclusion-include-parameter", "type": "normative-statements" }, + { "id": "inclusion-unrequested", "type": "normative-statements" }, + { "id": "inclusion-include-parameter-value", "type": "normative-statements" }, + { "id": "inclusion-bad-request", "type": "normative-statements" }, + { "id": "sparse-fieldsets-parameter", "type": "normative-statements" }, + { "id": "sparse-fieldsets-parameter-value", "type": "normative-statements" }, + { "id": "sparse-fieldsets-additional-fields", "type": "normative-statements" }, + { "id": "sorting-option", "type": "normative-statements" }, + { "id": "sorting-parameter", "type": "normative-statements" }, + { "id": "sorting-parameter-value", "type": "normative-statements" }, + { "id": "sorting-multiple-fields", "type": "normative-statements" }, + { "id": "sorting-multiple-fields-order", "type": "normative-statements" }, + { "id": "sorting-order", "type": "normative-statements" }, + { "id": "sorting-not-supported", "type": "normative-statements" }, + { "id": "sorting-specified-order", "type": "normative-statements" }, + { "id": "sorting-default", "type": "normative-statements" }, + { "id": "pagination", "type": "normative-statements" }, + { "id": "pagination-links", "type": "normative-statements" }, + { "id": "pagination-links-object", "type": "normative-statements" }, + { "id": "pagination-keys", "type": "normative-statements" }, + { "id": "pagination-unavailable-link", "type": "normative-statements" }, + { "id": "pagination-order", "type": "normative-statements" }, + { "id": "pagination-page-parameter", "type": "normative-statements" }, + { "id": "filtering", "type": "normative-statements" } + ] + } + } + }, + { + "id": "creating-updating-deleting", + "type": "sections", + "attributes": { + "title": "Creating, Updating and Deleting Resources" + }, + "links": { + "self": "http://jsonapi.org/format/#crud" + }, + "relationships": { + "statements": { + "data": [ + { "id": "create-support", "type": "normative-statements" }, + { "id": "modify-delete-support", "type": "normative-statements" }, + { "id": "crud-atomic", "type": "normative-statements" }, + { "id": "create-single-resource", "type": "normative-statements" }, + { "id": "create-type-member", "type": "normative-statements" }, + { "id": "create-relationships-member", "type": "normative-statements" }, + { "id": "create-accept-client-generated-ids", "type": "normative-statements" }, + { "id": "create-client-generated-ids-key", "type": "normative-statements" }, + { "id": "create-client-generated-ids-uuid", "type": "normative-statements" }, + { "id": "create-client-generated-ids-forbidden", "type": "normative-statements" }, + { "id": "create-responses-201-status", "type": "normative-statements" }, + { "id": "create-responses-201-location", "type": "normative-statements" }, + { "id": "create-responses-201-document", "type": "normative-statements" }, + { "id": "create-responses-201-self", "type": "normative-statements" }, + { "id": "create-responses-202", "type": "normative-statements" }, + { "id": "create-responses-204", "type": "normative-statements" }, + { "id": "create-responses-403", "type": "normative-statements" }, + { "id": "create-responses-404-related", "type": "normative-statements" }, + { "id": "create-responses-409-exists", "type": "normative-statements" }, + { "id": "create-responses-409-bad-type", "type": "normative-statements" }, + { "id": "create-responses-409-error-details", "type": "normative-statements" }, + { "id": "create-responses-other-status", "type": "normative-statements" }, + { "id": "create-responses-other-error-details", "type": "normative-statements" }, + { "id": "create-http-semantics", "type": "normative-statements" }, + { "id": "update-patch-resource", "type": "normative-statements" }, + { "id": "update-patch-resource-members", "type": "normative-statements" }, + { "id": "update-resource-attributes", "type": "normative-statements" }, + { "id": "update-interpret-resource-attributes", "type": "normative-statements" }, + { "id": "update-resource-relationships", "type": "normative-statements" }, + { "id": "update-interpret-resource-relationships", "type": "normative-statements" }, + { "id": "update-resource-relationship-value", "type": "normative-statements" }, + { "id": "update-resource-relationship-reject-full-replacement", "type": "normative-statements" }, + { "id": "update-resource-relationship-reject-full-replacement-response", "type": "normative-statements" }, + { "id": "update-resource-202-status", "type": "normative-statements" }, + { "id": "update-resource-200-status", "type": "normative-statements" }, + { "id": "update-resource-relationship-200-response", "type": "normative-statements" }, + { "id": "update-resource-200-meta", "type": "normative-statements" }, + { "id": "update-resource-200-meta-representation", "type": "normative-statements" }, + { "id": "update-resource-204-status", "type": "normative-statements" }, + { "id": "update-resource-403-status", "type": "normative-statements" }, + { "id": "update-resource-404-status", "type": "normative-statements" }, + { "id": "update-resource-404-related", "type": "normative-statements" }, + { "id": "update-resource-409-status", "type": "normative-statements" }, + { "id": "update-resource-409-no-match", "type": "normative-statements" }, + { "id": "update-resource-409-details", "type": "normative-statements" }, + { "id": "update-resource-409-details", "type": "normative-statements" }, + { "id": "update-resource-other-status", "type": "normative-statements" }, + { "id": "update-resource-other-status", "type": "normative-statements" }, + { "id": "update-resource-other-semantics", "type": "normative-statements" }, + { "id": "update-resource-http-semantics", "type": "normative-statements" }, + { "id": "respond-patch-to-one-relationship-link", "type": "normative-statements" }, + { "id": "patch-to-one-data-member", "type": "normative-statements" }, + { "id": "patch-to-one-response", "type": "normative-statements" }, + { "id": "respond-patch-post-delete-to-many-relationship-link", "type": "normative-statements" }, + { "id": "patch-post-delete-to-many-data-member", "type": "normative-statements" }, + { "id": "patch-to-many-complete-replace", "type": "normative-statements" }, + { "id": "post-to-many-add", "type": "normative-statements" }, + { "id": "post-to-many-add-again", "type": "normative-statements" }, + { "id": "post-to-many-add-again", "type": "normative-statements" }, + { "id": "post-to-many-response", "type": "normative-statements" }, + { "id": "delete-to-many", "type": "normative-statements" }, + { "id": "delete-to-many", "type": "normative-statements" }, + { "id": "delete-to-many-success", "type": "normative-statements" }, + { "id": "updating-relationship-202-status", "type": "normative-statements" }, + { "id": "updating-relationship-204-status", "type": "normative-statements" }, + { "id": "updating-relationship-200-status", "type": "normative-statements" }, + { "id": "updating-relationship-200-response", "type": "normative-statements" }, + { "id": "updating-relationship-200-meta", "type": "normative-statements" }, + { "id": "updating-relationship-200-meta-content", "type": "normative-statements" }, + { "id": "updating-relationship-403-status", "type": "normative-statements" }, + { "id": "updating-relationship-other-status", "type": "normative-statements" }, + { "id": "updating-relationship-other-details", "type": "normative-statements" }, + { "id": "update-relationship-http-semantics", "type": "normative-statements" }, + { "id": "delete-202-status", "type": "normative-statements" }, + { "id": "delete-204-status", "type": "normative-statements" }, + { "id": "delete-200-status", "type": "normative-statements" }, + { "id": "delete-404-status", "type": "normative-statements" }, + { "id": "deleting-other-status", "type": "normative-statements" }, + { "id": "deleting-other-details", "type": "normative-statements" }, + { "id": "deleting-http-semantics", "type": "normative-statements" } + ] + } + } + }, + { + "id": "query-parameters", + "type": "sections", + "attributes": { + "title": "Query Parameters" + }, + "links": { + "self": "http://jsonapi.org/format/#query-parameters" + }, + "relationships": { + "statements": { + "data": [ + { "id": "query-parameters-non-alpha", "type": "normative-statements"}, + { "id": "query-parameters-under-camel", "type": "normative-statements"}, + { "id": "query-parameters-bad-request", "type": "normative-statements"} + ] + } + } + }, + { + "id": "errors", + "type": "sections", + "attributes": { + "title": "Errors" + }, + "links": { + "self": "http://jsonapi.org/format/#errors" + }, + "relationships": { + "statements": { + "data": [ + { "id": "error-stop-processing", "type": "normative-statements" }, + { "id": "error-general", "type": "normative-statements" }, + { "id": "error-object-key", "type": "normative-statements" }, + { "id": "error-object-members", "type": "normative-statements" } + ] + } + } + } + ], + "included": [ + { + "id": "request-content-type", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "Clients **MUST** send all JSON:API data in request documents with the header `Content-Type: application/vnd.api+json` without any media type parameters." + }, + "relationships": { + "section": { + "data": { "id": "content-negotiation", "type": "sections" } + } + } + }, + { + "id": "request-accept", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "Clients that include the JSON:API media type in their `Accept` header **MUST** specify the media type there at least once without any media type parameters." + }, + "relationships": { + "section": { + "data": { "id": "content-negotiation", "type": "sections" } + } + } + }, + { + "id": "response-ignore-parameters", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "Clients **MUST** ignore any parameters for the `application/vnd.api+json` media type received in the `Content-Type` header of response documents." + }, + "relationships": { + "section": { + "data": { "id": "content-negotiation", "type": "sections" } + } + } + }, + { + "id": "response-content-type", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "Servers **MUST** send all JSON:API data in response documents with the header `Content-Type: application/vnd.api+json` without any media type parameters." + }, + "relationships": { + "section": { + "data": { "id": "content-negotiation", "type": "sections" } + } + } + }, + { + "id": "response-unsupported-media-type", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "Servers **MUST** respond with a `415 Unsupported Media Type` status code if a request specifies the header `Content-Type: application/vnd.api+json` with any media type parameters" + }, + "relationships": { + "section": { + "data": { "id": "content-negotiation", "type": "sections" } + } + } + }, + { + "id": "response-not-acceptable", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "Servers **MUST** respond with a `406 Not Acceptable` status code if a request's `Accept` header contains the JSON:API media type and all instances of that media type are modified with media type parameters." + }, + "relationships": { + "section": { + "data": { "id": "content-negotiation", "type": "sections" } + } + } + }, + { + "id": "additional-members", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "Unless otherwise noted, objects defined by this specification **MUST NOT** contain any additional members. " + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "ignore-additional-members", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "Client and server implementations **MUST** ignore members not recognized by this specification." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "json-object", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A JSON object MUST be at the root of every JSON:API request and response containing data. This object defines a document's \"top level\"." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "required-top-level", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A document **MUST** contain at least one of the following top-level members:\\n\\n- `data`: the document's \"primary data\"\\n- `errors`: an array of error objects\\n- meta`: a meta object that contains non-standard meta-information." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "data-errors", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "The members `data` and `errors` **MUST NOT** coexist in the same document." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "optional-top-level", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "A document **MAY** contain any of these top-level members:\\n\\n- `jsonapi`: an object describing the server's implementation\\n- `links`: a links object related to the primary data.\\n- `included`: an array of resource objects that are related to the primary data and/or each other (\"included resources\")." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "data-included", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "If a document does not contain a top-level `data` key, the `included` member **MUST NOT** be present either." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "top-level-links", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "The top-level links object **MAY** contain the following members:\\n\\n- `self`: the link that generated the current response document.\\n- `related`: a related resource link when the primary data represents a resource relationship.\\n- pagination links for the primary data." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "primary-data", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "Primary data **MUST** be either:\\n\\n- a single resource object, a single resource identifier object, or `null`, for requests that target single resources\\n- an array of resource objects, an array of resource identifier objects, or an empty array (`[]`), for requests that target resource collections" + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "logical-collection", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A logical collection of resources **MUST** be represented as an array, even if it only contains one item or is empty." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "resource-required-top-level", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A resource object MUST contain at least the following top-level members:\\n\\n- `id`\\n- `type`\\n\\nException: The `id` member is not required when the resource object originates at the client and represents a new resource to be created on the server." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "resource-optional-top-level", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "In addition, a resource object MAY contain any of these top-level members:\\n\\n- `attributes`: an attributes object representing some of the resource's data.\\n- `relationships`: a relationships object describing relationships between the resource and other JSON:API resources.\\n -`links`: a links object containing links related to the resource.\\n- `meta`: a meta object containing non-standard meta-information about a resource that can not be represented as an attribute or relationship." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "resource-id-type", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "Every resource object **MUST** contain an `id` member and a `type` member." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "resource-id-type-types", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "The values of the `id` and `type` members **MUST** be strings." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "resource-unique", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "Within a given API, each resource object's `type` and `id` pair **MUST** identify a single, unique resource. (The set of URIs controlled by a server, or multiple servers acting as one, constitute an API.)" + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "resource-type-constraints", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "The values of `type` members **MUST** adhere to the same constraints as member names." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "resource-fields", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "Fields for a resource object **MUST** share a common namespace with each other and with `type` and `id`. In other words, a resource can not have an attribute and relationship with the same name, nor can it have an attribute or relationship named `type` or id." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "resource-attributes-key", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "The value of the `attributes` key **MUST** be an object (an \"attributes object\")." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "resource-attributes-reserve-members", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "any object that constitutes or is contained in an attribute **MUST** reserve the `relationships` and `links` members for future use." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "resource-attributes-reserve-members", + "type": "normative-statements", + "attributes": { + "level": "SHOULD", + "description": "Although has-one foreign keys (e.g. author_id) are often stored internally alongside other information to be represented in a resource object, these keys **SHOULD NOT** appear as attributes." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "resource-relationships-key", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "The value of the `relationships` key **MUST** be an object (a \"relationships object\")." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "resource-relationships-object", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A \"relationship object\" **MUST** contain at least one of the following:]\\n\\n- `links`: a links object containing at least one of the following:\\n - `self`: a link for the relationship itself (a \"relationship link\"). This link 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.\\n - `related`: a related resource link\\n- `data`: resource linkage\\n- `meta`: a meta object that contains non-standard meta-information about the relationship." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "resource-relationships-pagination", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "A relationship object that represents a to-many relationship **MAY** also contain pagination links under the links member" + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "resource-related-resource-link", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "If present, a related resource link **MUST** reference a valid URL, even if the relationship isn't currently associated with any target resources." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "resource-related-resource-link-change", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "a related resource link **MUST NOT** change because its relationship's content changes." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "resource-linkage", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "Resource linkage MUST be represented as one of the following:\\n\\n- `null` for empty to-one relationships.\\n- an empty array (`[]`) for empty to-many relationships.\\n- a single resource identifier object for non-empty to-one relationships.\\n- an array of resource identifier objects for non-empty to-many relationships." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "resource-links", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "If present, this links object **MAY** contain a `self` link that identifies the resource represented by the resource object." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "resource-link-response", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A server **MUST** respond to a `GET` request to the specified URL with a response that includes the resource as the primary data." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "resource-identifier-required-members", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A \"resource identifier object\" **MUST** contain type and id members." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "resource-identifier-optional-member", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "A \"resource identifier object\" **MAY** also include a meta member, whose value is a meta object that contains non-standard meta-information." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "compound-documents-allow", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "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\"." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "compound-documents-top-level-included", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "In a compound document, all included resources **MUST** be represented as an array of resource objects in a top-level `included` member." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "compound-documents-full-linkage", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "Compound documents require \"full linkage\", meaning that every included resource **MUST** be identified by at least one resource identifier object in the same document. These resource identifier objects could either be primary data or represent resource linkage contained within primary or included resources. The only exception to the full linkage requirement is when relationship fields that would otherwise contain linkage data are excluded via sparse fieldsets." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "compound-documents-duplicates", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A compound document **MUST NOT** include more than one resource object for each type and id pair." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "meta-objects", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "The value of each meta member **MUST** be an object (a \"meta object\")." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "meta-object-members", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "Any members **MAY** be specified within meta objects." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "top-level-links", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "The value of each links member **MUST** be an object (a \"links object\")." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "top-level-links-members", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "Each member of a links object is a \"link\". A link **MUST** be represented as either:\\n\\n- a string containing the link's URL.\\n- an object (\"link object\") which can contain the following members:\\n - `href`: a string containing the link's URL.\\n - `meta`: a meta object containing non-standard meta-information about the link." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "top-level-json-api-member", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "A JSON:API document **MAY** include information about its implementation under a top level `jsonapi` member." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "json-api-type", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "If present, the value of the `jsonapi` member **MUST** be an object (a \"jsonapi object\")" + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "json-api-version", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "The jsonapi object **MAY** contain a `version` member whose value is a string indicating the highest JSON:API version supported." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "json-api-meta", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "The jsonapi object **MAY** also contain a `meta` member, whose value is a meta object that contains non-standard meta-information." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "member-name-case", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "All member names used in a JSON:API document **MUST** be treated as case sensitive by clients and servers" + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "member-name-character", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "Member names **MUST** contain at least one character." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "member-name-allowed-characters-only", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "Member names **MUST** contain only the allowed characters listed below." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "member-name-globally-allowed", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "Member names MUST start and end with a \"globally allowed character\", as defined below." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "member-name-url-safe", + "type": "normative-statements", + "attributes": { + "level": "RECOMMENDED", + "description": "it is **RECOMMENDED** that member names use only non-reserved, URL safe characters specified in RFC 3986." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "member-name-allowed-characters", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "The following \"globally allowed characters\" **MAY** be used anywhere in a member name:\\n\\n- U+0061 to U+007A, \"a-z\"\\n- U+0041 to U+005A, \"A-Z\"\\n- U+0030 to U+0039, \"0-9\"\\n- any UNICODE character except U+0000 to U+007F (not recommended, not URL safe)" + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "member-name-reserved-characters", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "The following characters **MUST NOT** be used in member names:\\n\\n- U+002B PLUS SIGN, \"+\" (used for ordering)\\n- U+002C COMMA, \",\" (used separator for multiple relationship paths)\\n- U+002E PERIOD, \".\" (used as relationship path separators)\\n- U+005B LEFT SQUARE BRACKET, \"[\" (use in sparse fieldsets)\\n- U+005D RIGHT SQUARE BRACKET, \"]\" (used in sparse fieldsets)\\n- U+0021 EXCLAMATION MARK, \"!\"\\n- U+0022 QUOTATION MARK, '\"'\\n- U+0023 NUMBER SIGN, \"#\"\\n- U+0024 DOLLAR SIGN, \"$\"\\n- U+0025 PERCENT SIGN, \"%\"\\n- U+0026 AMPERSAND, \"&\"\\n- U+0027 APOSTROPHE, \"'\"\\n- U+0028 LEFT PARENTHESIS, \"(\"\\n- U+0029 RIGHT PARENTHESIS, \")\"\\n- U+002A ASTERISK, \"*\"\\n- U+002F SOLIDUS, \"/\"\\n- U+003A COLON, \":\"\\n- U+003B SEMICOLON, \";\"\\n- U+003C LESS-THAN SIGN, \"<\"\\n- U+003D EQUALS SIGN, \"=\"\\n- U+003E GREATER-THAN SIGN, \">\"\\n- U+003F QUESTION MARK, \"?\"\\n- U+0040 COMMERCIAL AT, \"@\"\\n- U+005C REVERSE SOLIDUS, \"\\\"\\n- U+005E CIRCUMFLEX ACCENT, \"^\"\\n- U+0060 GRAVE ACCENT, \"`\"\\n- U+007B LEFT CURLY BRACKET, \"{\"\\n- U+007C VERTICAL LINE, \"|\"\\n- U+007D RIGHT CURLY BRACKET, \"}\"\\n- U+007E TILDE, \"~\"" + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "fetch-url-support", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A server **MUST** support fetching resource data for every URL provided as:\\n\\n- a self link as part of the top-level links object\\n- a self link as part of a resource-level links object\\n- a related link as part of a relationship-level links object" + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "fetch-response-code", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A server **MUST** respond to a successful request to fetch an individual resource or resource collection with a `200 OK` response." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "fetch-primary-data-collection", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A server **MUST** respond to a successful request to fetch a resource collection with an array of resource objects or an empty array (`[]`) as the response document's primary data." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "fetch-primary-data-single", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A server **MUST** respond to a successful request to fetch an individual resource with a resource object or `null` provided as the response document's primary data.\\n\\n`null` is only an appropriate response when the requested URL is one that might correspond to a single resource, but doesn't currently." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "fetch-responses-404", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A server **MUST** respond with `404 Not Found` when processing a request to fetch a single resource that does not exist, except when the request warrants a `200 OK` response with `null` as the primary data (as described above)." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "fetch-responses-other-status-codes", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "A server **MAY** respond with other HTTP status codes." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "fetch-responses-error-details", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "A server **MAY** include error details with error responses." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "fetch-responses-http-semantics", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A server **MUST** prepare responses, and a client **MUST** interpret responses, in accordance with HTTP semantics." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "fetch-relationships", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A server **MUST** support fetching relationship data for every relationship URL provided as a self link as part of a relationship's links object." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "fetch-relationships-response-200", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A server **MUST** respond to a successful request to fetch a relationship with a `200 OK` response." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "fetch-relationships-response-200-primary-data", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "The primary data in the response document **MUST** match the appropriate value for resource linkage, as described above for relationship objects." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "fetch-relationships-response-200-self-related", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "The top-level links object **MAY** contain self and related links, as described above for relationship objects." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "fetch-relationships-response-404", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A server **MUST** return 404 Not Found when processing a request to fetch a relationship link URL that does not exist." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "fetch-relationships-response-exists-empty", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "If a relationship link URL exists but the relationship is empty, then `200 OK` **MUST** be returned, as described above." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "fetch-relationships-other-status-codes", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "A server **MAY** respond with other HTTP status codes." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "fetch-relationships-other-error-details", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "A server **MAY** include error details with error responses." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "fetch-relationships-http-semantics", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A server **MUST** prepare responses, and a client **MUST** interpret responses, in accordance with HTTP semantics." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "inclusion-default", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "An endpoint **MAY** return resources related to the primary data by default." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "inclusion-include-parameter", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "An endpoint **MAY** also support an `include` request parameter to allow the client to customize which related resources should be returned." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "inclusion-unrequested", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "If an endpoint supports the include parameter and a client supplies it, the server **MUST NOT** include unrequested resource objects in the included section of the compound document." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "inclusion-include-parameter-value", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "The value of the include parameter **MUST** be a comma-separated (U+002C COMMA, \",\") list of relationship paths. A relationship path is a dot-separated (U+002E FULL-STOP, \".\") list of relationship names." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "inclusion-bad-request", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "If a server is unable to identify a relationship path or does not support inclusion of resources from a path, it **MUST** respond with 400 Bad Request." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "sparse-fieldsets-parameter", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "An endpoint **MAY** also support a `fields[TYPE]` request parameter to allow the client to customize which related resources should be returned." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "sparse-fieldsets-parameter-value", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "The value of the fields parameter **MUST** be a comma-separated (U+002C COMMA, \",\") list that refers to the name(s) of the fields to be returned." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "sparse-fieldsets-additional-fields", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "If a client requests a restricted set of fields, an endpoint **MUST NOT** include additional fields in the response." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "sorting-option", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "A server **MAY** choose to support requests to sort resource collections according to one or more criteria (\"sort fields\")." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "sorting-parameter", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "An endpoint **MAY** support requests to sort the primary data with a sort query parameter." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "sorting-parameter-value", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "The value for sort **MUST** represent sort fields." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "sorting-multiple-fields", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "An endpoint **MAY** support multiple sort fields by allowing comma-separated (U+002C COMMA, \",\") sort fields." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "sorting-multiple-fields-order", + "type": "normative-statements", + "attributes": { + "level": "SHOULD", + "description": "Sort fields **SHOULD** be applied in the order specified." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "sorting-order", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "The sort order for each sort field **MUST** be ascending unless it is prefixed with a minus (U+002D HYPHEN-MINUS, \"-\"), in which case it **MUST** be descending." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "sorting-not-supported", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "If the server does not support sorting as specified in the query parameter sort, it **MUST** return `400 Bad Request`." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "sorting-specified-order", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "If sorting is supported by the server and requested by the client via query parameter sort, the server **MUST** return elements of the top-level data array of the response ordered according to the criteria specified." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "sorting-default", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "The server **MAY** apply default sorting rules to top-level data if request parameter sort is not specified." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "pagination", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "A server **MAY** choose to limit the number of resources returned in a response to a subset (\"page\") of the whole set available." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "pagination-links", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "A server **MAY** provide links to traverse a paginated data set (\"pagination links\")." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "pagination-links-object", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "Pagination links **MUST** appear in the links object that corresponds to a collection." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "pagination-keys", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "The following keys MUST be used for pagination links:\\n\\n- `first`: the first page of data\\n- `last`: the last page of data\\n- `prev`: the previous page of data\\n- `next`: the next page of data" + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "pagination-unavailable-link", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "Keys **MUST** either be omitted or have a `null` value to indicate that a particular link is unavailable." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "pagination-order", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "Concepts of order, as expressed in the naming of pagination links, **MUST** remain consistent with JSON:API's sorting rules." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "pagination-page-parameter", + "type": "normative-statements", + "attributes": { + "level": "SHOULD", + "description": "The `page` query parameter is reserved for pagination. Servers and clients *SHOULD* use this key for pagination operations." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "filtering", + "type": "normative-statements", + "attributes": { + "level": "SHOULD", + "description": "The `filter` query parameter is reserved for filtering data. Servers and clients **SHOULD** use this key for filtering operations." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "create-support", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "A server **MAY** allow resources of a given type to be created." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "modify-delete-support", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "It **MAY** also allow existing resources to be modified or deleted." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "crud-atomic", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A request **MUST** completely succeed or fail (in a single \"transaction\"). No partial updates are allowed." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "create-single-resource", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "The request **MUST** include a single resource object as primary data." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "create-type-member", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "The resource object **MUST** contain at least a type member." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "create-relationships-member", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "If a relationship is provided in the `relationships` member of the resource object, its value **MUST** be a relationship object with a `data` member." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "create-accept-client-generated-ids", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "A server **MAY** accept a client-generated ID along with a request to create a resource." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "create-client-generated-ids-key", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "An ID **MUST** be specified with an `id` key, the value of which **MUST** be a universally unique identifier." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "create-client-generated-ids-uuid", + "type": "normative-statements", + "attributes": { + "level": "SHOULD", + "description": "The client **SHOULD** use a properly generated and formatted *UUID* as described in RFC 4122 [RFC4122]." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "create-client-generated-ids-forbidden", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A server **MUST** return `403 Forbidden` in response to an unsupported request to create a resource with a client-generated ID." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "create-responses-201-status", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "If a `POST` request did not include a Client-Generated ID and the requested resource has been created successfully, the server **MUST** return a `201 Created` status code." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "create-responses-201-location", + "type": "normative-statements", + "attributes": { + "level": "SHOULD", + "description": "The response **SHOULD** include a `Location` header identifying the location of the newly created resource." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "create-responses-201-document", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "The response **MUST** also include a document that contains the primary resource created." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "create-responses-201-self", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "If the resource object returned by the response contains a `self` key in its `links` member and a `Location` header is provided, the value of the `self` member **MUST** match the value of the `Location` header." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "create-responses-202", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "If a request to create a resource has been accepted for processing, but the processing has not been completed by the time the server responds, the server **MUST** return a `202 Accepted` status code." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "create-responses-204", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "If a `POST` request did include a Client-Generated ID and the requested resource has been created successfully, the server **MUST** return either a `201 Created` status code and response document (as described above) or a `204 No Content` status code with no response document." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "create-responses-403", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "A server **MAY** return `403 Forbidden` in response to an unsupported request to create a resource." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "create-responses-404-related", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "A server **MUST** return `404 Not Found` when processing a request that references a related resource that does not exist." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "create-responses-409-exists", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A server **MUST** return `409 Conflict` when processing a `POST` request to create a resource with a client-generated ID that already exists." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "create-responses-409-bad-type", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A server **MUST** return `409 Conflict` when processing a `POST` request in which the resource object's type is not among the type(s) that constitute the collection represented by the endpoint." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "create-responses-409-error-details", + "type": "normative-statements", + "attributes": { + "level": "SHOULD", + "description": "A server **SHOULD** include error details and provide enough information to recognize the source of the conflict." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "create-responses-other-status", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "A server **MAY** respond with other HTTP status codes." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "create-responses-other-error-details", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "A server **MAY** include error details with error responses." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "create-http-semantics", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A server **MUST** prepare responses, and a client **MUST** interpret responses, in accordance with HTTP semantics." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "update-patch-resource", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "The `PATCH` request **MUST** include a single resource object as primary data." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "update-patch-resource-members", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "The resource object **MUST** contain `type` and `id` members." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "update-resource-attributes", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "Any or all of a resource's attributes **MAY** be included in the resource object included in a `PATCH` request." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "update-interpret-resource-attributes", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "If a request does not include all of the attributes for a resource, the server **MUST** interpret the missing attributes as if they were included with their current values. It **MUST NOT** interpret them as `null` values." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "update-resource-relationships", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "Any or all of a resource's relationships **MAY** be included in the resource object included in a `PATCH` request." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "update-interpret-resource-relationships", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "If a request does not include all of the relationships for a resource, the server **MUST** interpret the missing relationships as if they were included with their current values. It **MUST NOT** interpret them as `null` or empty values." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "update-resource-relationship-value", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "If a relationship is provided in the relationships member of a resource object in a `PATCH` request, its value **MUST** be a relationship object with a data member." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "update-resource-relationship-reject-full-replacement", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "A server **MAY** reject an attempt to do a full replacement of a to-many relationship." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "update-resource-relationship-reject-full-replacement-response", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "In such a case, the server **MUST** reject the entire update, and return a `403 Forbidden` response." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "update-resource-202-status", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "If an update request has been accepted for processing, but the processing has not been completed by the time the server responds, the server **MUST** return a `202 Accepted` status code." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "update-resource-200-status", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "If a server accepts an update but also changes the resource(s) in ways other than those specified by the request (for example, updating the `updated-at` attribute or a computed `sha`), it **MUST** return a `200 OK` response." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "update-resource-relationship-200-response", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "The response document **MUST** include a representation of the updated resource(s) as if a `GET` request was made to the request URL." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "update-resource-200-meta", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A server **MUST** return a `200 OK` status code if an update is successful, the client's current attributes remain up to date, and the server responds only with top-level meta data." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "update-resource-200-meta-representation", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "In this case the server **MUST NOT** include a representation of the updated resource(s)." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "update-resource-204-status", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "If an update is successful and the server doesn't update any attributes besides those provided, the server **MUST** return either a `200 OK` status code and response document (as described above) or a `204 No Content` status code with no response document." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "update-resource-403-status", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A server **MUST** return `403 Forbidden` in response to an unsupported request to update a resource or relationship." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "update-resource-404-status", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A server **MUST** return `404 Not Found` when processing a request to modify a resource that does not exist." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "update-resource-404-related", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A server **MUST** return `404 Not Found` when processing a request that references a related resource that does not exist." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "update-resource-409-status", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "A server **MAY** return `409 Conflict` when processing a `PATCH` request to update a resource if that update would violate other server-enforced constraints (such as a uniqueness constraint on a property other than `id`)." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "update-resource-409-no-match", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A server **MUST** return `409 Conflict` when processing a `PATCH` request in which the resource object's `type` and `id` do not match the server's endpoint." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "update-resource-409-details", + "type": "normative-statements", + "attributes": { + "level": "SHOULD", + "description": "A server **SHOULD** include error details and provide enough information to recognize the source of the conflict." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "update-resource-409-details", + "type": "normative-statements", + "attributes": { + "level": "SHOULD", + "description": "A server **SHOULD** include error details and provide enough information to recognize the source of the conflict." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "update-resource-other-status", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "A server **MAY** respond with other `HTTP` status codes." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "update-resource-other-status", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "A server **MAY** include error details with error responses." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "update-resource-other-semantics", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "A server **MAY** include error details with error responses." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "update-resource-http-semantics", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A server **MUST** prepare responses, and a client **MUST** interpret responses, in accordance with HTTP semantics." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "respond-patch-to-one-relationship-link", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A server **MUST** respond to `PATCH` requests to a URL from a to-one relationship link as described below." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "patch-to-one-data-member", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "The PATCH request MUST include a top-level member named data containing one of:\\n\\n- a resource identifier object corresponding to the new related resource.\\n- `null`, to remove the relationship." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "patch-to-one-response", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "If the relationship is updated successfully then the server **MUST** return a successful response." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "respond-patch-post-delete-to-many-relationship-link", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A server **MUST** respond to `PATCH`, `POST`, and `DELETE` requests to a URL from a to-many relationship link as described below." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "patch-post-delete-to-many-data-member", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "For all request types, the body **MUST** contain a `data` member whose value is an empty array or an array of resource identifier objects." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "patch-to-many-complete-replace", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "If a client makes a `PATCH` request to a URL from a to-many relationship link, the server **MUST** either completely replace every member of the relationship, return an appropriate error response if some resources can not be found or accessed, or return a `403 Forbidden` response if complete replacement is not allowed by the server." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "post-to-many-add", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "If a client makes a `POST` request to a URL from a relationship link, the server **MUST** add the specified members to the relationship unless they are already present." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "post-to-many-add-again", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "If a given `type` and `id` is already in the relationship, the server **MUST NOT** add it again." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "post-to-many-add-again", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "If a given `type` and `id` is already in the relationship, the server **MUST NOT** add it again." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "post-to-many-response", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "If all of the specified resources can be added to, or are already present in, the relationship then the server **MUST** return a successful response." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "delete-to-many", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "If the client makes a `DELETE` request to a URL from a relationship link the server **MUST** delete the specified members from the relationship or return a `403 Forbidden` response." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "delete-to-many", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "If the client makes a `DELETE` request to a URL from a relationship link the server **MUST** delete the specified members from the relationship or return a `403 Forbidden` response." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "delete-to-many-success", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "If all of the specified resources are able to be removed from, or are already missing from, the relationship then the server **MUST** return a successful response." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "updating-relationship-202-status", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "If a relationship update request has been accepted for processing, but the processing has not been completed by the time the server responds, the server **MUST** return a `202 Accepted` status code." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "updating-relationship-204-status", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A server **MUST** return a `204 No Content` status code if an update is successful and the representation of the resource in the request matches the result." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "updating-relationship-200-status", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "If a server accepts an update but also changes the targeted relationship(s) in other ways than those specified by the request, it **MUST** return a `200 OK` response." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "updating-relationship-200-response", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "The response document **MUST** include a representation of the updated relationship(s)." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "updating-relationship-200-meta", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A server **MUST** return a `200 OK` status code if an update is successful, the client's current data remain up to date, and the server responds only with top-level meta data." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "updating-relationship-200-meta-content", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "In this case the server **MUST NOT** include a representation of the updated relationship(s)." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "updating-relationship-403-status", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A server **MUST** return `403 Forbidden` in response to an unsupported request to update a relationship." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "updating-relationship-other-status", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "A server **MAY** respond with other HTTP status codes." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "updating-relationship-other-details", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "A server **MAY** include error details with error responses." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "update-relationship-http-semantics", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A server **MUST** prepare responses, and a client **MUST** interpret responses, in accordance with HTTP semantics." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "delete-202-status", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "If a deletion request has been accepted for processing, but the processing has not been completed by the time the server responds, the server **MUST** return a `202 Accepted` status code." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "delete-204-status", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A server **MUST** return a `204 No Content` status code if a deletion request is successful and no content is returned." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "delete-200-status", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A server **MUST** return a `200 OK` status code if a deletion request is successful and the server responds with only top-level meta data." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "delete-404-status", + "type": "normative-statements", + "attributes": { + "level": "SHOULD", + "description": "A server **SHOULD** return a 404 Not Found status code if a deletion request fails due to the resource not existing." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "deleting-other-status", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "A server **MAY** respond with other HTTP status codes." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "deleting-other-details", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "A server **MAY** include error details with error responses." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "deleting-http-semantics", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A server **MUST** prepare responses, and a client **MUST** interpret responses, in accordance with HTTP semantics." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "query-parameters-non-alpha", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "Implementation specific query parameters **MUST** adhere to the same constraints as member names with the additional requirement that they **MUST** contain at least one non a-z character (U+0061 to U+007A)." + }, + "relationships": { + "section": { + "data": { "id": "query-parameters", "type": "sections" } + } + } + }, + { + "id": "query-parameters-under-camel", + "type": "normative-statements", + "attributes": { + "level": "RECOMMENDED", + "description": "It is RECOMMENDED that a U+002D HYPHEN-MINUS, \"-\", U+005F LOW LINE, \"_\", or capital letter is used (e.g. camelCasing)." + }, + "relationships": { + "section": { + "data": { "id": "query-parameters", "type": "sections" } + } + } + }, + { + "id": "query-parameters-bad-request", + "type": "normative-statements", + "attributes": { + "level": "RECOMMENDED", + "description": "If a server encounters a query parameter that does not follow the naming conventions above, and the server does not know how to process it as a query parameter from this specification, it **MUST** return `400 Bad Request`." + }, + "relationships": { + "section": { + "data": { "id": "query-parameters", "type": "sections" } + } + } + }, + { + "id": "error-stop-processing", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "A server **MAY** choose to stop processing as soon as a problem is encountered, or it **MAY** continue processing and encounter multiple problems." + }, + "relationships": { + "section": { + "data": { "id": "errors", "type": "sections" } + } + } + }, + { + "id": "error-general", + "type": "normative-statements", + "attributes": { + "level": "SHOULD", + "description": "When a server encounters multiple problems for a single request, the most generally applicable HTTP error code **SHOULD** be used in the response." + }, + "relationships": { + "section": { + "data": { "id": "errors", "type": "sections" } + } + } + }, + { + "id": "error-object-key", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "Error objects **MUST** be returned as an array keyed by `errors` in the top level of a JSON:API document." + }, + "relationships": { + "section": { + "data": { "id": "errors", "type": "sections" } + } + } + }, + { + "id": "error-object-members", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "An error object **MAY** have the following members:\\n\\n- `id`: a unique identifier for this particular occurrence of the problem.\\n- `links`: a links object containing the following members:\\n - `about`: a link that leads to further details about this particular occurrence of the problem.\\n- `status`: the HTTP status code applicable to this problem, expressed as a string value.\\n- `code`: an application-specific error code, expressed as a string value.\\n- `title`: a short, human-readable summary of the problem that **SHOULD NOT** change from occurrence to occurrence of the problem, except for purposes of localization.\\n- `detail`: a human-readable explanation specific to this occurrence of the problem.\\n- `source`: an object containing references to the source of the error, optionally including any of the following members:\\n - `pointer`: 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].\\n - `parameter`: a string indicating which URI query parameter caused the error.\\n- `meta`: a meta object containing non-standard meta-information about the error." + }, + "relationships": { + "section": { + "data": { "id": "errors", "type": "sections" } + } + } + } + ] +} From ea12636ec57149109af24900e3c6acdc726c71f3 Mon Sep 17 00:00:00 2001 From: Art4 Date: Fri, 27 Oct 2023 12:54:26 +0200 Subject: [PATCH 2/9] Add spec for JSON:API 1.1, run tests with normative statements --- tests/Functional/ParsingTest.php | 12 + tests/files/format_1.0/README.md | 2 + tests/files/format_1.1/README.md | 5 + tests/files/format_1.1/index.md | 2247 ++++++++++++++ .../format_1.1/normative-statements.json | 2737 +++++++++++++++++ 5 files changed, 5003 insertions(+) create mode 100644 tests/files/format_1.1/README.md create mode 100644 tests/files/format_1.1/index.md create mode 100644 tests/files/format_1.1/normative-statements.json diff --git a/tests/Functional/ParsingTest.php b/tests/Functional/ParsingTest.php index b058e84..7d4bdc8 100644 --- a/tests/Functional/ParsingTest.php +++ b/tests/Functional/ParsingTest.php @@ -659,4 +659,16 @@ public function testParseNormativeStatementsForVersion10Correctly(): void $this->assertCount(6, $document->get('data')->getKeys()); $this->assertCount(184, $document->get('included')->getKeys()); } + + public function testParseNormativeStatementsForVersion11Correctly(): void + { + $string = $this->getJsonString('format_1.1/normative-statements.json'); + $document = Parser::parseResponseString($string); + + $this->assertInstanceOf('Art4\JsonApiClient\V1\Document', $document); + $this->assertSame(['data', 'included', 'jsonapi'], $document->getKeys()); + $this->assertSame('1.1', $document->get('jsonapi.version')); + $this->assertCount(6, $document->get('data')->getKeys()); + $this->assertCount(188, $document->get('included')->getKeys()); + } } diff --git a/tests/files/format_1.0/README.md b/tests/files/format_1.0/README.md index 0d327e2..f0894b7 100644 --- a/tests/files/format_1.0/README.md +++ b/tests/files/format_1.0/README.md @@ -1,3 +1,5 @@ # JSON:API spec version 1.0 Source: https://github.com/json-api/json-api/tree/v1.1/_format/1.0 + +The file `normative-statements.json` has been modified to contain only valid JSON. \ No newline at end of file diff --git a/tests/files/format_1.1/README.md b/tests/files/format_1.1/README.md new file mode 100644 index 0000000..e469590 --- /dev/null +++ b/tests/files/format_1.1/README.md @@ -0,0 +1,5 @@ +# JSON:API spec version 1.1 + +Source: https://github.com/json-api/json-api/tree/v1.1/_format/1.1 + +The file `normative-statements.json` has been modified to contain only valid JSON. \ No newline at end of file diff --git a/tests/files/format_1.1/index.md b/tests/files/format_1.1/index.md new file mode 100644 index 0000000..9ba32ce --- /dev/null +++ b/tests/files/format_1.1/index.md @@ -0,0 +1,2247 @@ +--- +version: 1.1 +--- + +## Introduction + +JSON:API is a specification for how a client should request that resources be +fetched or modified, and how a server should respond to those requests. JSON:API +can be easily extended with [extensions] and [profiles]. + +JSON:API is designed to minimize both the number of requests and the amount of +data transmitted between clients and servers. This efficiency is achieved +without compromising readability, flexibility, or discoverability. + +JSON:API requires use of the JSON:API media type +([`application/vnd.api+json`](http://www.iana.org/assignments/media-types/application/vnd.api+json)) +for exchanging data. + +## Semantics + +All document members, query parameters, and processing rules defined by +this specification are collectively called "specification semantics". + +Certain document members, query parameters, and processing rules are reserved +for implementors to define at their discretion. These are called "implementation +semantics". + +All other semantics are reserved for potential future use by this specification. + +## Conventions + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", +"SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this +document are to be interpreted as described in +[BCP 14](https://tools.ietf.org/html/bcp14) +[[RFC2119](https://tools.ietf.org/html/rfc2119)] +[[RFC8174](https://tools.ietf.org/html/rfc8174)] +when, and only when, they appear in all capitals, as shown here. + +## The JSON:API Media Type + +The JSON:API media type is +[`application/vnd.api+json`](http://www.iana.org/assignments/media-types/application/vnd.api+json). + +### Media Type Parameters + +The JSON:API specification supports two media type parameters: `ext` and +`profile`, which are used to specify [extensions] and [profiles], respectively. + +> Note: A media type parameter is an extra piece of information that can +accompany a media type. For example, in the header +`Content-Type: text/html; charset="utf-8"`, the media type is `text/html` and +`charset` is a parameter. + +### Extensions + +Extensions provide a means to "extend" the base specification by defining +additional [specification semantics](#semantics). + +Extensions cannot alter or remove specification semantics, nor can they specify +implementation semantics. + +### Profiles + +Profiles provide a means to share a particular usage of the specification among +implementations. + +Profiles can specify [implementation semantics](#semantics), but cannot alter, +add to, or remove specification semantics. + +### Rules for Media Type Parameters + +The JSON:API media type **MUST NOT** be specified with any media type parameters +other than `ext` and `profile`. The `ext` parameter is used to support +[extensions] and the `profile` parameter is used to support [profiles]. + +Extensions and profiles are each uniquely identified by a +[URI](https://tools.ietf.org/html/rfc3986). Visiting an extension's or a +profile's URI **SHOULD** return documentation that describes its usage. The +values of the `ext` and `profile` parameters **MUST** equal a space-separated +(U+0020 SPACE, " ") list of extension or profile URIs, respectively. + +> Note: When serializing the `ext` or `profile` media type parameters, the HTTP +> specification requires that parameter values be surrounded by quotation marks +> (U+0022 QUOTATION MARK, "\""). + +#### Rules for Extensions + +An extension **MAY** impose additional processing rules or further restrictions +and it **MAY** define new object members as described below. + +An extension **MUST NOT** lessen or remove any processing rules, restrictions or +object member requirements defined in this specification or other extensions. + +An extension **MAY** define new members within the document structure defined by +this specification. The rules for extension member names are covered +[below](#extension-members). + +An extension **MAY** define new query parameters. The rules for extension-defined +query parameters are covered [below](#extension-query-parameters). + +When an extension defines new query parameters or document members, the +extension **MUST** define a namespace to guarantee that extensions will never +conflict with current or future versions of this specification. A namespace +**MUST** meet all of the following conditions: + +- A namespace **MUST** contain at least one character. +- A namespace **MUST** contain only these characters: + - U+0061 to U+007A, "a-z" + - U+0041 to U+005A, "A-Z" + - U+0030 to U+0039, "0-9" + +An extension **MUST NOT** define more than one namespace. The namespace used for +all query parameters and document members **MUST** be the same for any given +extension. + +In the following example, an extension with the namespace `version` has +specified a resource object member `version:id` to support per-resource +versioning. This member might appear as follows: + +```json +HTTP/1.1 200 OK +Content-Type: application/vnd.api+json; ext="https://jsonapi.org/ext/version" + +// ... +{ + "type": "articles", + "id": "1", + "version:id": "42", + "attributes": { + "title": "Rails is Omakase" + } +} +// ... +``` + +#### Rules for Profiles + +The rules for profile usage are dictated by [RFC +6906](https://tools.ietf.org/html/rfc6906). + +A profile **MAY** define document members and processing rules that are reserved +for implementors. + +A profile **MUST NOT** define any query parameters. + +A profile **MUST NOT** alter or remove processing rules that have been defined +by this specification or by an [extension][extensions]. However, a profile +**MAY** define processing rules for query parameters whose processing rules +have been reserved for implementors to define at their discretion. + +For example, a profile could define rules for interpreting [the `filter` query +parameter](#fetching-filtering), but it could not specify that relationship +names in [the `include` query parameter](#fetching-includes) are +space-separated instead of dot-separated. + +Unlike extensions, profiles do not need to define a namespace for document +members because profiles cannot define specification semantics and thus cannot +conflict with current or future versions of this specification. However, it is +possible for profiles to conflict with other profiles. Therefore, it is the +responsibility of implementors to ensure that they do not support conflicting +profiles. + +In the following example, a profile has defined a `timestamps` +[attribute][attributes]. According to the profile, the attribute must be an +object containing a `created` member and a `modified` member and these members' +values must use the [RFC 3339](https://tools.ietf.org/html/rfc3339) format. +With such a profile applied, a response might appear as follows: + +```json +HTTP/1.1 200 OK +Content-Type: application/vnd.api+json; profile="https://example.com/resource-timestamps" + +// ... +{ + "type": "articles", + "id": "1", + "attributes": { + "title": "Rails is Omakase", + "timestamps": { + "created": "2020-07-21T12:09:00Z", + "modified": "2020-07-30T10:19:01Z" + } + } +} +// ... +``` + +## Content Negotiation + +### Universal Responsibilities + +Clients and servers **MUST** send all JSON:API payloads using the JSON:API media +type in the `Content-Type` header. + +Clients and servers **MUST** specify the `ext` media type parameter in the +`Content-Type` header when they have applied one or more extensions to a +JSON:API document. + +Clients and servers **MUST** specify the `profile` media type parameters in the +`Content-Type` header when they have applied one or more profiles to a JSON:API +document. + +### Client Responsibilities + +When processing a JSON:API response document, clients **MUST** ignore any +parameters other than `ext` and `profile` parameters in the server's +`Content-Type` header. + +A client **MAY** use the `ext` media type parameter in an `Accept` header to +require that a server apply all the specified extensions to the response +document. + +A client **MAY** use the `profile` media type parameter in an `Accept` header +to request that the server apply one or more profiles to the response document. + +> Note: A client is allowed to send more than one acceptable media type in the +`Accept` header, including multiple instances of the JSON:API media type. This +allows clients to request different combinations of the `ext` and `profile` +media type parameters. A client can use [quality values](https://tools.ietf.org/html/rfc7231#section-5.3.2) +to indicate that some combinations are less preferable than others. Media types +specified without a qvalue are equally preferable to each other, regardless of +their order, and are always considered more preferable than a media type with a +qvalue less than 1. + +### Server Responsibilities + +If a request specifies the `Content-Type` header with the JSON:API media type, +servers **MUST** respond with a `415 Unsupported Media Type` status code if +that media type contains any media type parameters other than `ext` or +`profile`. + +If a request specifies the `Content-Type` header with an instance of +the JSON:API media type modified by the `ext` media type parameter and that +parameter contains an unsupported extension URI, the server **MUST** respond +with a `415 Unsupported Media Type` status code. + +> Note: JSON:API servers that do not support version 1.1 of this specification + will respond with a `415 Unsupported Media Type` client error if the `ext` or + `profile` media type parameter is present. + +If a request's `Accept` header contains an instance of the JSON:API media type, +servers **MUST** ignore instances of that media type which are modified by a +media type parameter other than `ext` or `profile`. If all instances of that +media type are modified with a media type parameter other than `ext` or `profile`, +servers **MUST** respond with a `406 Not Acceptable` status code. If every +instance of that media type is modified by the `ext` parameter and each contains +at least one unsupported extension URI, the server **MUST** also respond with a +`406 Not Acceptable`. + +If the `profile` parameter is received, a server **SHOULD** attempt to apply any +requested profile(s) to its response. A server **MUST** ignore any profiles +that it does not recognize. + +> Note: The above rules guarantee strict agreement on extensions between the + client and server, while the application of profiles is left to the discretion + of the server. + +Servers that support the `ext` or `profile` media type parameters **SHOULD** +specify the `Vary` header with `Accept` as one of its values. This applies to +responses with and without any [profiles] or [extensions] applied. + +> Note: Some HTTP intermediaries (e.g. CDNs) may ignore the `Vary` header +unless specifically configured to respect it. + +## Document Structure + +This section describes the structure of a JSON:API document, which is identified +by the [JSON:API media type](#content-negotiation-all). JSON:API documents are +defined in JavaScript Object Notation (JSON) [[RFC8259](http://tools.ietf.org/html/rfc8259)]. + +Although the same media type is used for both request and response documents, +certain aspects are only applicable to one or the other. These differences are +called out below. + +Extensions **MAY** define new members within the document structure. These +members **MUST** comply with the naming requirements specified +[below](#extension-members). + +Unless otherwise noted, objects defined by this specification or any applied +extensions **MUST NOT** contain any additional members. Client and server +implementations **MUST** ignore non-compliant members. + +> Note: These conditions allow this specification to evolve through additive +changes. + +### Top Level + +A JSON object **MUST** be at the root of every JSON:API request and response +document containing data. This object defines a document's "top level". + +A document **MUST** contain at least one of the following top-level members: + +* `data`: the document's "primary data". +* `errors`: an array of [error objects](#errors). +* `meta`: a [meta object][meta] that contains non-standard + meta-information. +* a member defined by an applied [extension](#extensions). + +The members `data` and `errors` **MUST NOT** coexist in the same document. + +A document **MAY** contain any of these top-level members: + +* `jsonapi`: an object describing the server's implementation. +* `links`: a [links object][links] related to the primary data. +* `included`: an array of [resource objects] that are related to the primary + data and/or each other ("included resources"). + +If a document does not contain a top-level `data` key, the `included` member +**MUST NOT** be present either. + +The top-level [links object][links] **MAY** contain the following members: + +* `self`: the [link][links] that generated the current response document. If a + document has extensions or profiles applied to it, this link **SHOULD** be + represented by a [link object] with the `type` target attribute specifying the + JSON:API media type with all applicable parameters. +* `related`: a [related resource link] when the primary data represents a + resource relationship. +* `describedby`: a [link] to a description document (e.g. OpenAPI or JSON + Schema) for the current document. +* [pagination] links for the primary data. + +> Note: The `self` link in the top-level `links` object allows a client to +> refresh the data represented by the current response document. The client +> should be able to use the provided link without applying any additional +> information. Therefore the link must contain the query parameters provided +> by the client to generate the response document. This includes but is not +> limited to query parameters used for [inclusion of related resources][fetching resources], +> [sparse fieldsets][fetching sparse fieldsets], [sorting][fetching sorting], +> [pagination][fetching pagination] and [filtering][fetching filtering]. + +The document's "primary data" is a representation of the resource or collection +of resources targeted by a request. + +Primary data **MUST** be either: + +* a single [resource object][resource objects], a single [resource identifier object], or `null`, + for requests that target single resources +* an array of [resource objects], an array of + [resource identifier objects][resource identifier object], or + an empty array (`[]`), for requests that target resource collections + +For example, the following primary data is a single resource object: + +```json +{ + "data": { + "type": "articles", + "id": "1", + "attributes": { + // ... this article's attributes + }, + "relationships": { + // ... this article's relationships + } + } +} +``` + +The following primary data is a single [resource identifier object] that +references the same resource: + +```json +{ + "data": { + "type": "articles", + "id": "1" + } +} +``` + +A logical collection of resources **MUST** be represented as an array, even if +it only contains one item or is empty. + +### Resource Objects + +"Resource objects" appear in a JSON:API document to represent resources. + +A resource object **MUST** contain at least the following top-level members: + +* `id` +* `type` + +Exception: The `id` member is not required when the resource object originates +at the client and represents a new resource to be created on the server. In that +case, a client **MAY** include a `lid` member to uniquely identify the resource +by `type` _locally_ within the document. + +In addition, a resource object **MAY** contain any of these top-level members: + +* `attributes`: an [attributes object][attributes] representing some of the resource's data. +* `relationships`: a [relationships object][relationships] describing relationships between + the resource and other JSON:API resources. +* `links`: a [links object][links] containing links related to the resource. +* `meta`: a [meta object][meta] containing non-standard meta-information about a + resource that can not be represented as an attribute or relationship. + +Here's how an article (i.e. a resource of type "articles") might appear in a document: + +```json +// ... +{ + "type": "articles", + "id": "1", + "attributes": { + "title": "Rails is Omakase" + }, + "relationships": { + "author": { + "links": { + "self": "/articles/1/relationships/author", + "related": "/articles/1/author" + }, + "data": { "type": "people", "id": "9" } + } + } +} +// ... +``` + +#### Identification + +As noted above, every [resource object][resource objects] **MUST** contain a +`type` member. Every resource object **MUST** also contain an `id` member, +except when the resource object originates at the client and represents a new +resource to be created on the server. If `id` is omitted due to this exception, +a `lid` member **MAY** be included to uniquely identify the resource by `type` +_locally_ within the document. The value of the `lid` member **MUST** be +identical for every representation of the resource in the document, including +[resource identifier objects][resource identifier object]. + +The values of the `id`, `type`, and `lid` members **MUST** be strings. + +Within a given API, each resource object's `type` and `id` pair **MUST** +identify a single, unique resource. (The set of URIs controlled by a server, +or multiple servers acting as one, constitute an API.) + +The `type` member is used to describe [resource objects] that share common +attributes and relationships. + +The values of `type` members **MUST** adhere to the same constraints as +[member names]. + +> Note: This spec is agnostic about inflection rules, so the value of `type` +can be either plural or singular. However, the same value should be used +consistently throughout an implementation. + +#### Fields + +A resource object's [attributes] and its [relationships] are collectively called +its "[fields]". + +Fields for a [resource object][resource objects] **MUST** share a common namespace with each +other and with `type` and `id`. In other words, a resource can not have an +attribute and relationship with the same name, nor can it have an attribute +or relationship named `type` or `id`. + +##### Attributes + +The value of the `attributes` key **MUST** be an object (an "attributes +object"). Members of the attributes object ("attributes") represent information +about the [resource object][resource objects] in which it's defined. + +Attributes may contain any valid JSON value, including complex data structures +involving JSON objects and arrays. + +Keys that reference related resources (e.g. `author_id`) **SHOULD NOT** appear +as attributes. Instead, [relationships] **SHOULD** be used. + +> Note: See [fields] and [member names] for more restrictions on this container. + +##### Relationships + +The value of the `relationships` key **MUST** be an object (a "relationships +object"). Each member of a relationships object represents +a "relationship" from the [resource object][resource objects] +in which it has been defined to other resource objects. + +Relationships may be to-one or to-many. + +A relationship's name is given by its key. The value at that key **MUST** be an +object ("relationship object"). + + +A "relationship object" **MUST** contain at least one of the following: + +* `links`: a [links object][links] containing at least one of the following: + * `self`: a link for the relationship itself (a "relationship link"). This + link allows the client to directly manipulate the relationship. For example, + removing an `author` through an `article`'s relationship URL would disconnect + the person from the `article` without deleting the `people` resource itself. + When fetched successfully, this link returns the [linkage][resource linkage] + for the related resources as its primary data. + (See [Fetching Relationships](#fetching-relationships).) + * `related`: a [related resource link] + * a member defined by an applied [extension](#extensions). +* `data`: [resource linkage] +* `meta`: a [meta object][meta] that contains non-standard meta-information about the + relationship. +* a member defined by an applied [extension](#extensions). + +A relationship object that represents a to-many relationship **MAY** also contain +[pagination] links under the `links` member, as described below. Any +[pagination] links in a relationship object **MUST** paginate the relationship +data, not the related resources. + +> Note: See [fields] and [member names] for more restrictions on this container. + +##### Related Resource Links + +A "related resource link" provides access to [resource objects][resource objects] [linked][links] +in a [relationship][relationships]. When fetched, the related resource object(s) +are returned as the response's primary data. + +For example, an `article`'s `comments` [relationship][relationships] could +specify a [link][links] that returns a collection of comment [resource objects] +when retrieved through a `GET` request. + +If present, a related resource link **MUST** reference a valid URL, even if the +relationship isn't currently associated with any target resources. Additionally, +a related resource link **MUST NOT** change because its relationship's content +changes. + +##### Resource Linkage + +Resource linkage in a [compound document] allows a client to link together all +of the included [resource objects] without having to `GET` any URLs via [links]. + +Resource linkage **MUST** be represented as one of the following: + +* `null` for empty to-one relationships. +* an empty array (`[]`) for empty to-many relationships. +* a single [resource identifier object] for non-empty to-one relationships. +* an array of [resource identifier objects][resource identifier object] for non-empty to-many relationships. + +> Note: The spec does not impart meaning to order of resource identifier +objects in linkage arrays of to-many relationships, although implementations +may do that. Arrays of resource identifier objects may represent ordered +or unordered relationships, and both types can be mixed in one response +object. + +For example, the following article is associated with an `author`: + +```json +// ... +{ + "type": "articles", + "id": "1", + "attributes": { + "title": "Rails is Omakase" + }, + "relationships": { + "author": { + "links": { + "self": "http://example.com/articles/1/relationships/author", + "related": "http://example.com/articles/1/author" + }, + "data": { "type": "people", "id": "9" } + } + }, + "links": { + "self": "http://example.com/articles/1" + } +} +// ... +``` + +The `author` relationship includes a link for the relationship itself (which +allows the client to change the related author directly), a related resource +link to fetch the resource objects, and linkage information. + +#### Resource Links + +The optional `links` member within each [resource object][resource objects] contains [links] +related to the resource. + +If present, this links object **MAY** contain a `self` [link][links] that +identifies the resource represented by the resource object. + +```json +// ... +{ + "type": "articles", + "id": "1", + "attributes": { + "title": "Rails is Omakase" + }, + "links": { + "self": "http://example.com/articles/1" + } +} +// ... +``` + +A server **MUST** respond to a `GET` request to the specified URL with a +response that includes the resource as the primary data. + +### Resource Identifier Objects + +A "resource identifier object" is an object that identifies an individual +resource. + +A "resource identifier object" **MUST** contain a `type` member. It **MUST** +also contain an `id` member, except when it represents a new resource to be +created on the server. In this case, a `lid` member **MUST** be included that +identifies the new resource. + +The values of the `id`, `type`, and `lid` members **MUST** be strings. + +A "resource identifier object" **MAY** also include a `meta` member, whose value is a [meta] object that +contains non-standard meta-information. + +### Compound Documents + +Servers **MAY** allow responses that include related resources along with the +requested primary resources. Such responses are called "compound documents". + +In a compound document, all included resources **MUST** be represented as an +array of [resource objects] in a top-level `included` member. + +Every included resource object **MUST** be identified via a chain of +relationships originating in a document's primary data. This means that +compound documents require "full linkage" and that no resource object can be +included without a direct or indirect relationship to the document's primary +data. + +The only exception to the full linkage requirement is when relationship fields +that would otherwise contain linkage data are excluded due to +[sparse fieldsets](#fetching-sparse-fieldsets) requested by the client. + +A complete example document with multiple included relationships: + +```json +{ + "data": [{ + "type": "articles", + "id": "1", + "attributes": { + "title": "JSON:API paints my bikeshed!" + }, + "links": { + "self": "http://example.com/articles/1" + }, + "relationships": { + "author": { + "links": { + "self": "http://example.com/articles/1/relationships/author", + "related": "http://example.com/articles/1/author" + }, + "data": { "type": "people", "id": "9" } + }, + "comments": { + "links": { + "self": "http://example.com/articles/1/relationships/comments", + "related": "http://example.com/articles/1/comments" + }, + "data": [ + { "type": "comments", "id": "5" }, + { "type": "comments", "id": "12" } + ] + } + } + }], + "included": [{ + "type": "people", + "id": "9", + "attributes": { + "firstName": "Dan", + "lastName": "Gebhardt", + "twitter": "dgeb" + }, + "links": { + "self": "http://example.com/people/9" + } + }, { + "type": "comments", + "id": "5", + "attributes": { + "body": "First!" + }, + "relationships": { + "author": { + "data": { "type": "people", "id": "2" } + } + }, + "links": { + "self": "http://example.com/comments/5" + } + }, { + "type": "comments", + "id": "12", + "attributes": { + "body": "I like XML better" + }, + "relationships": { + "author": { + "data": { "type": "people", "id": "9" } + } + }, + "links": { + "self": "http://example.com/comments/12" + } + }] +} +``` + +A [compound document] **MUST NOT** include more than one [resource object][resource objects] for +each `type` and `id` pair. + +> Note: In a single document, you can think of the `type` and `id` as a +composite key that uniquely references [resource objects] in another part of +the document. + +> Note: For resources that do not contain an `id` member but do contain a `lid`, +the `lid` is sufficient to establish resource identity and thus linkage between +resource objects and resource identifier objects throughout the document. + +> Note: This approach ensures that a single canonical [resource object][resource objects] is +returned with each response, even when the same resource is referenced +multiple times. + +### Meta Information + +Where specified, a `meta` member can be used to include non-standard +meta-information. The value of each `meta` member **MUST** be an object (a +"meta object"). + +Any members **MAY** be specified within `meta` objects. + +For example: + +```json +{ + "meta": { + "copyright": "Copyright 2015 Example Corp.", + "authors": [ + "Yehuda Katz", + "Steve Klabnik", + "Dan Gebhardt", + "Tyler Kellen" + ] + }, + "data": { + // ... + } +} +``` + +### Links + +Where specified, a `links` member can be used to represent links. The value +of this member **MUST** be an object (a "links object"). + + +Within this object, a link **MUST** be represented as either: + +* a string whose value is a URI-reference [[RFC3986 Section 4.1](https://tools.ietf.org/html/rfc3986#section-4.1)] + pointing to the link's target, +* a [link object] or +* `null` if the link does not exist. + +A link's relation type **SHOULD** be inferred from the name of the link unless the +link is a [link object] and the link object has a `rel` member. + +A link's context is the [top-level object][top level], [resource +object][resource objects], or [relationship object][relationships] in which it +appears. + +In the example below, the `self` link is a string whereas the `related` link is +a [link object]. The `related` link object provides additional information +about the targeted related resource collection as well as a schema that serves +as a description document for that collection: + +```json +"links": { + "self": "http://example.com/articles/1/relationships/comments", + "related": { + "href": "http://example.com/articles/1/comments", + "title": "Comments", + "describedby": "http://example.com/schemas/article-comments", + "meta": { + "count": 10 + } + } +} +``` + +#### Link objects + +A "link object" is an object that represents a [web link](https://tools.ietf.org/html/rfc8288). + +A link object **MUST** contain the following member: + +* `href`: a string whose value is a URI-reference [[RFC3986 Section 4.1](https://tools.ietf.org/html/rfc3986#section-4.1)] + pointing to the link's target. + +A link object **MAY** also contain any of the following members: + +* `rel`: a string indicating the link's relation type. The string **MUST** be a + [valid link relation type](https://tools.ietf.org/html/rfc8288#section-2.1). +* `describedby`: a [link] to a description document (e.g. OpenAPI or JSON + Schema) for the link target. +* `title`: a string which serves as a label for the destination of a link + such that it can be used as a human-readable identifier (e.g., a menu + entry). +* `type`: a string indicating the media type of the link's target. +* `hreflang`: a string or an array of strings indicating the language(s) of the + link's target. An array of strings indicates that the link's target is + available in multiple languages. Each string **MUST** be a valid language tag + [[RFC5646](https://tools.ietf.org/html/rfc5646)]. +* `meta`: a meta object containing non-standard meta-information about the + link. + +> Note: the `type` and `hreflang` members are only hints; the target resource +> is not guaranteed to be available in the indicated media type or language +> when the link is actually followed. + +### JSON:API Object + +A JSON:API document **MAY** include information about its implementation +under a top level `jsonapi` member. If present, the value of the `jsonapi` +member **MUST** be an object (a "jsonapi object"). + +The jsonapi object **MAY** contain any of the following members: + +* `version` - whose value is a string indicating the highest JSON:API version + supported. +* `ext` - an array of URIs for all applied [extensions]. +* `profile` - an array of URIs for all applied [profiles]. +* `meta` - a [meta] object that contains non-standard meta-information. + +Clients and servers **MUST NOT** use an `ext` or `profile` member for content +negotiation. Content negotiation **MUST** only happen based on media type +parameters in `Content-Type` header. + +A simple example appears below: + +```json +{ + "jsonapi": { + "version": "1.1", + "ext": [ + "https://jsonapi.org/ext/atomic" + ], + "profile": [ + "http://example.com/profiles/flexible-pagination", + "http://example.com/profiles/resource-versioning" + ] + } +} +``` + +If the `version` member is not present, clients should assume the server +implements at least version 1.0 of the specification. + +> Note: Because JSON:API is committed to making additive changes only, the +version string primarily indicates which new features a server may support. + +### Member Names + +Implementation and profile defined member names used in a JSON:API document +**MUST** be treated as case sensitive by clients and servers, and they **MUST** +meet all of the following conditions: + +- Member names **MUST** contain at least one character. +- Member names **MUST** contain only the allowed characters listed below. +- Member names **MUST** start and end with a "globally allowed character", + as defined below. + +To enable an easy mapping of member names to URLs, it is **RECOMMENDED** that +member names use only non-reserved, URL safe characters specified in [RFC 3986](https://datatracker.ietf.org/doc/html/rfc3986#section-2.3). + +#### Allowed Characters + +The following "globally allowed characters" **MAY** be used anywhere in a member name: + +- U+0061 to U+007A, "a-z" +- U+0041 to U+005A, "A-Z" +- U+0030 to U+0039, "0-9" +- U+0080 and above (non-ASCII Unicode characters; _not recommended, not URL safe_) + +Additionally, the following characters are allowed in member names, except as the +first or last character: + +- U+002D HYPHEN-MINUS, "-" +- U+005F LOW LINE, "_" +- U+0020 SPACE, " " _(not recommended, not URL safe)_ + +#### Reserved Characters + +The following characters **MUST NOT** be used in implementation and +[profile][profiles] defined member names: + +- U+002B PLUS SIGN, "+" _(has overloaded meaning in URL query strings)_ +- U+002C COMMA, "," _(used as a separator between relationship paths)_ +- U+002E PERIOD, "." _(used as a separator within relationship paths)_ +- U+005B LEFT SQUARE BRACKET, "[" _(used in query parameter families)_ +- U+005D RIGHT SQUARE BRACKET, "]" _(used in query parameter families)_ +- U+0021 EXCLAMATION MARK, "!" +- U+0022 QUOTATION MARK, '"' +- U+0023 NUMBER SIGN, "#" +- U+0024 DOLLAR SIGN, "$" +- U+0025 PERCENT SIGN, "%" +- U+0026 AMPERSAND, "&" +- U+0027 APOSTROPHE, "'" +- U+0028 LEFT PARENTHESIS, "(" +- U+0029 RIGHT PARENTHESIS, ")" +- U+002A ASTERISK, "*" +- U+002F SOLIDUS, "/" +- U+003A COLON, ":" +- U+003B SEMICOLON, ";" +- U+003C LESS-THAN SIGN, "<" +- U+003D EQUALS SIGN, "=" +- U+003E GREATER-THAN SIGN, ">" +- U+003F QUESTION MARK, "?" +- U+0040 COMMERCIAL AT, "@" (except as first character in [@-Members](#document-member-names-at-members)) +- U+005C REVERSE SOLIDUS, "\" +- U+005E CIRCUMFLEX ACCENT, "^" +- U+0060 GRAVE ACCENT, "`" +- U+007B LEFT CURLY BRACKET, "{" +- U+007C VERTICAL LINE, "|" +- U+007D RIGHT CURLY BRACKET, "}" +- U+007E TILDE, "~" +- U+007F DELETE +- U+0000 to U+001F (C0 Controls) + +#### @-Members + +Member names **MAY** also begin with an at sign (U+0040 COMMERCIAL AT, "@"). +Members named this way are called "@-Members". @-Members **MAY** appear +anywhere in a document. + +This specification provides no guidance on the meaning or usage of @-Members, +which are considered to be [implementation semantics](#semantics). @-Members +**MUST** be ignored when interpreting this specification's definitions and +processing instructions given outside of this subsection. For example, an +[attribute][attributes] is defined above as any member of the attributes object. +However, because @-Members must be ignored when interpreting that definition, an +@-Member that occurs in an attributes object is not an attribute. + +> Note: Among other things, "@" members can be used to add JSON-LD data to a +JSON:API document. Such documents should be served with [an extra header](http://www.w3.org/TR/json-ld/#interpreting-json-as-json-ld) +to convey to JSON-LD clients that they contain JSON-LD data. + +#### Extension Members + +The name of every new member introduced by an extension **MUST** be prefixed +with the [extension's namespace](#extension-rules) followed by a colon (`:`). +The remainder of the name **MUST** adhere to the rules for implementation +defined [member names]. + +## Fetching Data + +Data, including resources and relationships, can be fetched by sending a +`GET` request to an endpoint. + +Responses can be further refined with the optional features described below. + +### Fetching Resources + +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 + +For example, the following request fetches a collection of articles: + +```http +GET /articles HTTP/1.1 +Accept: application/vnd.api+json +``` + +The following request fetches an article: + +```http +GET /articles/1 HTTP/1.1 +Accept: application/vnd.api+json +``` + +And the following request fetches an article's author: + +```http +GET /articles/1/author HTTP/1.1 +Accept: application/vnd.api+json +``` + +#### Responses + +##### 200 OK + +A server **MUST** respond to a successful request to fetch an individual +resource or resource collection with a `200 OK` response. + +A server **MUST** respond to a successful request to fetch a resource +collection with an array of [resource objects] or an empty array (`[]`) as +the response document's primary data. + +For example, a `GET` request to a collection of articles could return: + +```http +HTTP/1.1 200 OK +Content-Type: application/vnd.api+json + +{ + "links": { + "self": "http://example.com/articles" + }, + "data": [{ + "type": "articles", + "id": "1", + "attributes": { + "title": "JSON:API paints my bikeshed!" + } + }, { + "type": "articles", + "id": "2", + "attributes": { + "title": "Rails is Omakase" + } + }] +} +``` + +A similar response representing an empty collection would be: + +```http +HTTP/1.1 200 OK +Content-Type: application/vnd.api+json + +{ + "links": { + "self": "http://example.com/articles" + }, + "data": [] +} +``` + +A server **MUST** respond to a successful request to fetch an individual +resource with a [resource object][resource objects] or `null` provided as +the response document's primary data. + +`null` is only an appropriate response when the requested URL is one that +might correspond to a single resource, but doesn't currently. + +> Note: Consider, for example, a request to fetch a to-one related resource link. +This request would respond with `null` when the relationship is empty (such that +the link is corresponding to no resources) but with the single related resource's +[resource object][resource objects] otherwise. + +For example, a `GET` request to an individual article could return: + +```http +HTTP/1.1 200 OK +Content-Type: application/vnd.api+json + +{ + "links": { + "self": "http://example.com/articles/1" + }, + "data": { + "type": "articles", + "id": "1", + "attributes": { + "title": "JSON:API paints my bikeshed!" + }, + "relationships": { + "author": { + "links": { + "related": "http://example.com/articles/1/author" + } + } + } + } +} +``` + +If the above article's author is missing, then a `GET` request to that related +resource would return: + +```http +HTTP/1.1 200 OK +Content-Type: application/vnd.api+json + +{ + "links": { + "self": "http://example.com/articles/1/author" + }, + "data": null +} +``` + +##### 404 Not Found + +A server **MUST** respond with `404 Not Found` when processing a request to +fetch a single resource that does not exist, except when the request warrants a +`200 OK` response with `null` as the primary data (as described above). + +##### Other Responses + +A server **MAY** respond with other HTTP status codes. + +A server **MAY** include [error details] with error responses. + +A server **MUST** prepare responses, and a client **MUST** interpret +responses, in accordance with +[`HTTP semantics`](http://tools.ietf.org/html/rfc7231). + +### Fetching Relationships + +A server **MUST** support fetching relationship data for every relationship URL +provided as a `self` link as part of a relationship's `links` object. + +For example, the following request fetches data about an article's comments: + +```http +GET /articles/1/relationships/comments HTTP/1.1 +Accept: application/vnd.api+json +``` + +And the following request fetches data about an article's author: + +```http +GET /articles/1/relationships/author HTTP/1.1 +Accept: application/vnd.api+json +``` + +#### Responses + +##### 200 OK + +A server **MUST** respond to a successful request to fetch a relationship +with a `200 OK` response. + +The primary data in the response document **MUST** match the appropriate +value for [resource linkage], as described above for +[relationship objects][relationships]. + +The top-level [links object][links] **MAY** contain `self` and `related` links, +as described above for [relationship objects][relationships]. + +For example, a `GET` request to a URL from a to-one relationship link could +return: + +```http +HTTP/1.1 200 OK +Content-Type: application/vnd.api+json + +{ + "links": { + "self": "/articles/1/relationships/author", + "related": "/articles/1/author" + }, + "data": { + "type": "people", + "id": "12" + } +} +``` + +If the above relationship is empty, then a `GET` request to the same URL would +return: + +```http +HTTP/1.1 200 OK +Content-Type: application/vnd.api+json + +{ + "links": { + "self": "/articles/1/relationships/author", + "related": "/articles/1/author" + }, + "data": null +} +``` + +A `GET` request to a URL from a to-many relationship link could return: + +```http +HTTP/1.1 200 OK +Content-Type: application/vnd.api+json + +{ + "links": { + "self": "/articles/1/relationships/tags", + "related": "/articles/1/tags" + }, + "data": [ + { "type": "tags", "id": "2" }, + { "type": "tags", "id": "3" } + ] +} +``` + +If the above relationship is empty, then a `GET` request to the same URL would +return: + +```http +HTTP/1.1 200 OK +Content-Type: application/vnd.api+json + +{ + "links": { + "self": "/articles/1/relationships/tags", + "related": "/articles/1/tags" + }, + "data": [] +} +``` + +##### 404 Not Found + +A server **MUST** return `404 Not Found` when processing a request to fetch +a relationship link URL that does not exist. + +> Note: This can happen when the parent resource of the relationship +does not exist. For example, when `/articles/1` does not exist, request to +`/articles/1/relationships/tags` returns `404 Not Found`. + +If a relationship link URL exists but the relationship is empty, then +`200 OK` **MUST** be returned, as described above. + +##### Other Responses + +A server **MAY** respond with other HTTP status codes. + +A server **MAY** include [error details] with error responses. + +A server **MUST** prepare responses, and a client **MUST** interpret +responses, in accordance with +[`HTTP semantics`](http://tools.ietf.org/html/rfc7231). + +### Inclusion of Related Resources + +An endpoint **MAY** return resources related to the primary data by default. + +An endpoint **MAY** also support an `include` query parameter to allow the +client to customize which related resources should be returned. + +If an endpoint does not support the `include` parameter, it **MUST** respond +with `400 Bad Request` to any requests that include it. + +If an endpoint supports the `include` parameter and a client supplies it: + + - The server's response **MUST** be a [compound document] with an `included` key — even if that `included` key holds an empty array (because the requested relationships are empty). + - The server **MUST NOT** include unrequested [resource objects] in the `included` +section of the [compound document]. + +The value of the `include` parameter **MUST** be a comma-separated (U+002C +COMMA, ",") list of relationship paths. A relationship path is a dot-separated +(U+002E FULL-STOP, ".") list of [relationship][relationships] names. An empty +value indicates that no related resources should be returned. + +If a server is unable to identify a relationship path or does not support +inclusion of resources from a path, it **MUST** respond with 400 Bad Request. + +> Note: For example, a relationship path could be `comments.author`, where +`comments` is a relationship listed under a `articles` [resource object][resource objects], and +`author` is a relationship listed under a `comments` [resource object][resource objects]. + +For instance, comments could be requested with an article: + +```http +GET /articles/1?include=comments HTTP/1.1 +Accept: application/vnd.api+json +``` + +In order to request resources related to other resources, a dot-separated path +for each relationship name can be specified: + +```http +GET /articles/1?include=comments.author HTTP/1.1 +Accept: application/vnd.api+json +``` + +> Note: Because [compound documents][compound document] require full linkage +(except when relationship linkage is excluded by sparse fieldsets), intermediate +resources in a multi-part path must be returned along with the leaf nodes. For +example, a response to a request for `comments.author` should include `comments` +as well as the `author` of each of those `comments`. + +> Note: A server may choose to expose a deeply nested relationship such as +`comments.author` as a direct relationship with an alternative name such as +`commentAuthors`. This would allow a client to request +`/articles/1?include=commentAuthors` instead of +`/articles/1?include=comments.author`. By exposing the nested relationship with +an alternative name, the server can still provide full linkage in compound +documents without including potentially unwanted intermediate resources. + +Multiple related resources can be requested in a comma-separated list: + +```http +GET /articles/1?include=comments.author,ratings HTTP/1.1 +Accept: application/vnd.api+json +``` + +Furthermore, related resources can be requested from a relationship endpoint: + +```http +GET /articles/1/relationships/comments?include=comments.author HTTP/1.1 +Accept: application/vnd.api+json +``` + +In this case, the primary data would be a collection of +[resource identifier objects][resource identifier object] that represent linkage to comments for an article, +while the full comments and comment authors would be returned as included data. + +> Note: This section applies to any endpoint that responds with primary +data, regardless of the request type. For instance, a server could support +the inclusion of related resources along with a `POST` request to create a +resource or relationship. + +### Sparse Fieldsets + +A client **MAY** request that an endpoint return only specific [fields] in the +response on a per-type basis by including a `fields[TYPE]` query parameter. + +The value of any `fields[TYPE]` parameter **MUST** be a comma-separated (U+002C +COMMA, ",") list that refers to the name(s) of the fields to be returned. +An empty value indicates that no fields should be returned. + +If a client requests a restricted set of [fields] for a given resource type, +an endpoint **MUST NOT** include additional [fields] in resource objects of +that type in its response. + +If a client does not specify the set of [fields] for a given resource type, the +server **MAY** send all fields, a subset of fields, or no fields for that +resource type. + +```http +GET /articles?include=author&fields[articles]=title,body&fields[people]=name HTTP/1.1 +Accept: application/vnd.api+json +``` + +> Note: The above example URI shows unencoded `[` and `]` characters simply for +readability. In practice, these characters should be percent-encoded. See +"[Square Brackets in Parameter Names](#appendix-query-details-square-brackets)". + +> Note: This section applies to any endpoint that responds with resources as +primary or included data, regardless of the request type. For instance, a +server could support sparse fieldsets along with a `POST` request to create +a resource. + +### Sorting + +A server **MAY** choose to support requests to sort resource collections +according to one or more criteria ("sort fields"). + +> Note: Although recommended, sort fields do not necessarily need to +correspond to resource attribute and relationship names. + +> Note: It is recommended that dot-separated (U+002E FULL-STOP, ".") sort +fields be used to request sorting based upon relationship attributes. For +example, a sort field of `author.name` could be used to request that the +primary data be sorted based upon the `name` attribute of the `author` +relationship. + +An endpoint **MAY** support requests to sort the primary data with a `sort` +query parameter. The value for `sort` **MUST** represent sort fields. + +```http +GET /people?sort=age HTTP/1.1 +Accept: application/vnd.api+json +``` + +An endpoint **MAY** support multiple sort fields by allowing comma-separated +(U+002C COMMA, ",") sort fields. Sort fields **SHOULD** be applied in the +order specified. + +```http +GET /people?sort=age,name HTTP/1.1 +Accept: application/vnd.api+json +``` + +The sort order for each sort field **MUST** be ascending unless it is prefixed +with a minus (U+002D HYPHEN-MINUS, "-"), in which case it **MUST** be descending. + +```http +GET /articles?sort=-created,title HTTP/1.1 +Accept: application/vnd.api+json +``` + +The above example should return the newest articles first. Any articles +created on the same date will then be sorted by their title in ascending +alphabetical order. + +If the server does not support sorting as specified in the query parameter +`sort`, it **MUST** return `400 Bad Request`. + +If sorting is supported by the server and requested by the client via query +parameter `sort`, the server **MUST** return elements of the top-level +`data` array of the response ordered according to the criteria specified. +The server **MAY** apply default sorting rules to top-level `data` if +request parameter `sort` is not specified. + +> Note: This section applies to any endpoint that responds with a resource +collection as primary data, regardless of the request type. + +### Pagination + +A server **MAY** choose to limit the number of resources returned in a response +to a subset ("page") of the whole set available. + +A server **MAY** provide links to traverse a paginated data set ("pagination +links"). + +Pagination links **MUST** appear in the links object that corresponds to a +collection. To paginate the primary data, supply pagination links in the +top-level `links` object. To paginate an included collection returned in +a [compound document], supply pagination links in the corresponding links +object. + +The following keys **MUST** be used for pagination links: + +* `first`: the first page of data +* `last`: the last page of data +* `prev`: the previous page of data +* `next`: the next page of data + +Keys **MUST** either be omitted or have a `null` value to indicate that a +particular link is unavailable. + +Concepts of order, as expressed in the naming of pagination links, **MUST** +remain consistent with JSON:API's [sorting rules](#fetching-sorting). + +The `page` [query parameter family] is reserved for pagination. Servers and +clients **SHOULD** use these parameters for pagination operations. + +> Note: JSON API is agnostic about the pagination strategy used by a server, but +> the `page` query parameter family can be used regardless of the strategy +> employed. For example, a page-based strategy might use query parameters such +> as `page[number]` and `page[size]`, while a cursor-based strategy might use +> `page[cursor]`. + +> Note: This section applies to any endpoint that responds with a resource +collection as primary data, regardless of the request type. + +### Filtering + +The `filter` [query parameter family] is reserved for filtering data. Servers +and clients **SHOULD** use these parameters for filtering operations. + +> Note: JSON API is agnostic about the strategies supported by a server. + +## Creating, Updating and Deleting Resources + +A server **MAY** allow resources of a given type to be created. It **MAY** +also allow existing resources to be modified or deleted. + +A request **MUST** completely succeed or fail (in a single "transaction"). No +partial updates are allowed. + +> Note: The `type` member is required in every [resource object][resource objects] throughout requests and +responses in JSON:API. There are some cases, such as when `POST`ing to an +endpoint representing heterogeneous data, when the `type` could not be inferred +from the endpoint. However, picking and choosing when it is required would be +confusing; it would be hard to remember when it was required and when it was +not. Therefore, to improve consistency and minimize confusion, `type` is +always required. + +### Creating Resources + +A resource can be created by sending a `POST` request to a URL that represents +a collection of resources. The request **MUST** include a single [resource object][resource objects] +as primary data. The [resource object][resource objects] **MUST** contain at least a `type` member. + +For instance, a new photo might be created with the following request: + +```http +POST /photos HTTP/1.1 +Content-Type: application/vnd.api+json +Accept: application/vnd.api+json + +{ + "data": { + "type": "photos", + "attributes": { + "title": "Ember Hamster", + "src": "http://example.com/images/productivity.png" + }, + "relationships": { + "photographer": { + "data": { "type": "people", "id": "9" } + } + } + } +} +``` + +If a relationship is provided in the `relationships` member of the +[resource object][resource objects], its value **MUST** be a relationship object with a `data` +member. The value of this key represents the [linkage][resource linkage] the new resource is to +have. + +#### Client-Generated IDs + +A server **MAY** accept a client-generated ID along with a request to create +a resource. An ID **MUST** be specified with an `id` key, the value of +which **MUST** be a universally unique identifier. The client **SHOULD** use +a properly generated and formatted *UUID* as described in RFC 4122 +[[RFC4122](http://tools.ietf.org/html/rfc4122.html)]. + +> NOTE: In some use-cases, such as importing data from another source, it +may be possible to use something other than a UUID that is still guaranteed +to be globally unique. Do not use anything other than a UUID unless you are +100% confident that the strategy you are using indeed generates globally +unique identifiers. + +For example: + +```http +POST /photos HTTP/1.1 +Content-Type: application/vnd.api+json +Accept: application/vnd.api+json + +{ + "data": { + "type": "photos", + "id": "550e8400-e29b-41d4-a716-446655440000", + "attributes": { + "title": "Ember Hamster", + "src": "http://example.com/images/productivity.png" + } + } +} +``` + +A server **MUST** return `403 Forbidden` in response to an unsupported request +to create a resource with a client-generated ID. + +#### Responses + +##### 201 Created + +If the requested resource has been created successfully and the server changes +the resource in any way (for example, by assigning an `id`), the server **MUST** +return a `201 Created` response and a document that contains the resource as +primary data. + +The response **SHOULD** include a `Location` header identifying the location +of the newly created resource, in order to comply with [RFC +7231](http://tools.ietf.org/html/rfc7231#section-6.3.2). + +If the [resource object][resource objects] returned by the response contains a `self` key in its +`links` member and a `Location` header is provided, the value of the `self` +member **MUST** match the value of the `Location` header. + +```http +HTTP/1.1 201 Created +Location: http://example.com/photos/550e8400-e29b-41d4-a716-446655440000 +Content-Type: application/vnd.api+json + +{ + "data": { + "type": "photos", + "id": "550e8400-e29b-41d4-a716-446655440000", + "attributes": { + "title": "Ember Hamster", + "src": "http://example.com/images/productivity.png" + }, + "links": { + "self": "http://example.com/photos/550e8400-e29b-41d4-a716-446655440000" + } + } +} +``` + +A server **MAY** return a `201 Created` response with a document that contains +no primary data if the requested resource has been created successfully and the +server does not change the resource in any way (for example, by assigning an +`id` or `createdAt` attribute). Other top-level members, such as [meta], could +be included in the response document. + +> Note: Only servers that accept [Client-Generated +IDs](#crud-creating-client-ids) can avoid assigning an `id` to a new resource. + +##### 202 Accepted + +If a request to create a resource has been accepted for processing, but the +processing has not been completed by the time the server responds, the +server **MUST** return a `202 Accepted` status code. + +##### 204 No Content + +If the requested resource has been created successfully and the server does not +change the resource in any way (for example, by assigning an `id` or `createdAt` +attribute), the server **MUST** return either a `201 Created` status code and +response document (as described above) or a `204 No Content` status code with no +response document. + +##### 403 Forbidden + +A server **MAY** return `403 Forbidden` in response to an unsupported request +to create a resource. + +##### 404 Not Found + +A server **MUST** return `404 Not Found` when processing a request that +references a related resource that does not exist. + +##### 409 Conflict + +A server **MUST** return `409 Conflict` when processing a `POST` request to +create a resource with a client-generated ID that already exists. + +A server **MUST** return `409 Conflict` when processing a `POST` request in +which the [resource object][resource objects]'s `type` is not among the type(s) that constitute the +collection represented by the endpoint. + +A server **SHOULD** include error details and provide enough information to +recognize the source of the conflict. + +##### Other Responses + +A server **MAY** respond with other HTTP status codes. + +A server **MAY** include [error details] with error responses. + +A server **MUST** prepare responses, and a client **MUST** interpret +responses, in accordance with +[`HTTP semantics`](http://tools.ietf.org/html/rfc7231). + +### Updating Resources + +A resource can be updated by sending a `PATCH` request to the URL that +represents the resource. + +The URL for a resource can be obtained in the `self` link of the resource +object. Alternatively, when a `GET` request returns a single [resource object][resource objects] as +primary data, the same request URL can be used for updates. + +The `PATCH` request **MUST** include a single [resource object][resource objects] as primary data. +The [resource object][resource objects] **MUST** contain `type` and `id` members. + +For example: + +```http +PATCH /articles/1 HTTP/1.1 +Content-Type: application/vnd.api+json +Accept: application/vnd.api+json + +{ + "data": { + "type": "articles", + "id": "1", + "attributes": { + "title": "To TDD or Not" + } + } +} +``` + +#### Updating a Resource's Attributes + +Any or all of a resource's [attributes] **MAY** be included in the resource +object included in a `PATCH` request. + +If a request does not include all of the [attributes] for a resource, the server +**MUST** interpret the missing [attributes] as if they were included with their +current values. The server **MUST NOT** interpret missing attributes as `null` +values. + +For example, the following `PATCH` request is interpreted as a request to +update only the `title` and `text` attributes of an article: + +```http +PATCH /articles/1 HTTP/1.1 +Content-Type: application/vnd.api+json +Accept: application/vnd.api+json + +{ + "data": { + "type": "articles", + "id": "1", + "attributes": { + "title": "To TDD or Not", + "text": "TLDR; It's complicated... but check your test coverage regardless." + } + } +} +``` + +#### Updating a Resource's Relationships + +Any or all of a resource's [relationships] **MAY** be included in the resource +object included in a `PATCH` request. + +If a request does not include all of the [relationships] for a resource, the server +**MUST** interpret the missing [relationships] as if they were included with their +current values. It **MUST NOT** interpret them as `null` or empty values. + +If a relationship is provided in the `relationships` member of a resource +object in a `PATCH` request, its value **MUST** be a relationship object +with a `data` member. The relationship's value will be replaced with the +value specified in this member. + +For instance, the following `PATCH` request will update the `author` relationship of an article: + +```http +PATCH /articles/1 HTTP/1.1 +Content-Type: application/vnd.api+json +Accept: application/vnd.api+json + +{ + "data": { + "type": "articles", + "id": "1", + "relationships": { + "author": { + "data": { "type": "people", "id": "1" } + } + } + } +} +``` + +Likewise, the following `PATCH` request performs a complete replacement of +the `tags` for an article: + +```http +PATCH /articles/1 HTTP/1.1 +Content-Type: application/vnd.api+json +Accept: application/vnd.api+json + +{ + "data": { + "type": "articles", + "id": "1", + "relationships": { + "tags": { + "data": [ + { "type": "tags", "id": "2" }, + { "type": "tags", "id": "3" } + ] + } + } + } +} +``` + +A server **MAY** reject an attempt to do a full replacement of a to-many +relationship. In such a case, the server **MUST** reject the entire update, +and return a `403 Forbidden` response. + +> Note: Since full replacement may be a very dangerous operation, a server +may choose to disallow it. For example, a server may reject full replacement if +it has not provided the client with the full list of associated objects, and +does not want to allow deletion of records the client has not seen. + +#### Responses + +##### 200 OK + +If a server accepts an update but also changes the targeted resource in ways +other than those specified by the request (for example, updating the +`updatedAt` attribute or a computed `sha`), it **MUST** return a `200 OK` +response and a document that contains the updated resource as primary data. + +A server **MAY** return a `200 OK` response with a document that contains no +primary data if an update is successful and the server does not change the +targeted resource in ways other than those specified by the request. Other +top-level members, such as [meta], could be included in the response document. + +##### 202 Accepted + +If an update request has been accepted for processing, but the processing +has not been completed by the time the server responds, the server **MUST** +return a `202 Accepted` status code. + +##### 204 No Content + +If an update is successful and the server doesn't change the targeted +resource in ways other than those specified by the request, the server +**MUST** return either a `200 OK` status code and response document (as +described above) or a `204 No Content` status code with no response document. + +##### 403 Forbidden + +A server **MUST** return `403 Forbidden` in response to an unsupported request +to update a resource or relationship. + +##### 404 Not Found + +A server **MUST** return `404 Not Found` when processing a request to modify +a resource that does not exist. + +A server **MUST** return `404 Not Found` when processing a request that +references a related resource that does not exist. + +##### 409 Conflict + +A server **MAY** return `409 Conflict` when processing a `PATCH` request to +update a resource if that update would violate other server-enforced +constraints (such as a uniqueness constraint on a property other than `id`). + +A server **MUST** return `409 Conflict` when processing a `PATCH` request in +which the resource object's `type` or `id` do not match the server's endpoint. + +A server **SHOULD** include error details and provide enough information to +recognize the source of the conflict. + +##### Other Responses + +A server **MAY** respond with other HTTP status codes. + +A server **MAY** include [error details] with error responses. + +A server **MUST** prepare responses, and a client **MUST** interpret +responses, in accordance with +[`HTTP semantics`](http://tools.ietf.org/html/rfc7231). + +### Updating Relationships + +Although relationships can be modified along with resources (as described +above), JSON:API also supports updating of relationships independently at +URLs from [relationship links][relationships]. + +> Note: Relationships are updated without exposing the underlying server +semantics, such as foreign keys. Furthermore, relationships can be updated +without necessarily affecting the related resources. For example, if an article +has many authors, it is possible to remove one of the authors from the article +without deleting the person itself. Similarly, if an article has many tags, it +is possible to add or remove tags. Under the hood on the server, the first +of these examples might be implemented with a foreign key, while the second +could be implemented with a join table, but the JSON:API protocol would be +the same in both cases. + +> Note: A server may choose to delete the underlying resource if a +relationship is deleted (as a garbage collection measure). + +#### Updating To-One Relationships + +A to-one relationship can be updated by sending a `PATCH` request to a URL +from a to-one [relationship link][relationships]. + +The `PATCH` request **MUST** include a top-level member named `data` containing +one of: + +* a [resource identifier object] corresponding to the new related resource. +* `null`, to remove the relationship. + +For example, the following request updates the author of an article: + +```http +PATCH /articles/1/relationships/author HTTP/1.1 +Content-Type: application/vnd.api+json +Accept: application/vnd.api+json + +{ + "data": { "type": "people", "id": "12" } +} +``` + +And the following request clears the author of the same article: + +```http +PATCH /articles/1/relationships/author HTTP/1.1 +Content-Type: application/vnd.api+json +Accept: application/vnd.api+json + +{ + "data": null +} +``` + +If the relationship is updated successfully then the server **MUST** return +a successful response. + +#### Updating To-Many Relationships + +A to-many relationship can be updated by sending a `PATCH`, `POST`, or +`DELETE` request to a URL from a to-many [relationship link][relationships]. + +For all request types, the body **MUST** contain a `data` member whose value +is an empty array or an array of [resource identifier objects][resource identifier object]. + +If a client makes a `PATCH` request to a URL from a to-many +[relationship link][relationships], the server **MUST** either completely +replace every member of the relationship, return an appropriate error response +if some resources cannot be found or accessed, or return a `403 Forbidden` +response if complete replacement is not allowed by the server. + +For example, the following request replaces every tag for an article: + +```http +PATCH /articles/1/relationships/tags HTTP/1.1 +Content-Type: application/vnd.api+json +Accept: application/vnd.api+json + +{ + "data": [ + { "type": "tags", "id": "2" }, + { "type": "tags", "id": "3" } + ] +} +``` + +And the following request clears every tag for an article: + +```http +PATCH /articles/1/relationships/tags HTTP/1.1 +Content-Type: application/vnd.api+json +Accept: application/vnd.api+json + +{ + "data": [] +} +``` + +If a client makes a `POST` request to a URL from a +[relationship link][relationships], the server **MUST** add the specified +members to the relationship unless they are already present. If a given `type` +and `id` is already in the relationship, the server **MUST NOT** add it again. + +> Note: This matches the semantics of databases that use foreign keys for +has-many relationships. Document-based storage should check the has-many +relationship before appending to avoid duplicates. + +If all of the specified resources can be added to, or are already present +in, the relationship then the server **MUST** return a successful response. + +> Note: This approach ensures that a request is successful if the server's +state matches the requested state, and helps avoid pointless race conditions +caused by multiple clients making the same changes to a relationship. + +In the following example, the comment with ID `123` is added to the list of +comments for the article with ID `1`: + +```http +POST /articles/1/relationships/comments HTTP/1.1 +Content-Type: application/vnd.api+json +Accept: application/vnd.api+json + +{ + "data": [ + { "type": "comments", "id": "123" } + ] +} +``` + +If the client makes a `DELETE` request to a URL from a +[relationship link][relationships] the server **MUST** delete the specified +members from the relationship or return a `403 Forbidden` response. If all of +the specified resources are able to be removed from, or are already missing +from, the relationship then the server **MUST** return a successful response. + +> Note: As described above for `POST` requests, this approach helps avoid +pointless race conditions between multiple clients making the same changes. + +Relationship members are specified in the same way as in the `POST` request. + +In the following example, comments with IDs of `12` and `13` are removed +from the list of comments for the article with ID `1`: + +```http +DELETE /articles/1/relationships/comments HTTP/1.1 +Content-Type: application/vnd.api+json +Accept: application/vnd.api+json + +{ + "data": [ + { "type": "comments", "id": "12" }, + { "type": "comments", "id": "13" } + ] +} +``` + +> Note: RFC 7231 specifies that a DELETE request may include a body, but +that a server may reject the request. This spec defines the semantics of a +server, and we are defining its semantics for JSON:API. + +#### Responses + +##### 200 OK + +If a server accepts an update but also changes the targeted relationship in +other ways than those specified by the request, it **MUST** return a `200 OK` +response and a document that includes the updated relationship data as its +primary data. + +A server **MAY** return a `200 OK` response with a document that contains no +primary data if an update is successful and the server does not change the +targeted relationship in ways other than those specified by the request. Other +top-level members, such as [meta], could be included in the response document. + +##### 202 Accepted + +If a relationship update request has been accepted for processing, but the +processing has not been completed by the time the server responds, the +server **MUST** return a `202 Accepted` status code. + +##### 204 No Content + +If an update is successful and the server doesn't change the targeted +relationship in ways other than those specified by the request, the server +**MUST** return either a `200 OK` status code and response document (as +described above) or a `204 No Content` status code with no response document. + +> Note: This is the appropriate response to a `POST` request sent to a URL +from a to-many [relationship link][relationships] when that relationship already +exists. It is also the appropriate response to a `DELETE` request sent to a URL +from a to-many [relationship link][relationships] when that relationship does +not exist. + +##### 403 Forbidden + +A server **MUST** return `403 Forbidden` in response to an unsupported request +to update a relationship. + +##### Other Responses + +A server **MAY** respond with other HTTP status codes. + +A server **MAY** include [error details] with error responses. + +A server **MUST** prepare responses, and a client **MUST** interpret +responses, in accordance with +[`HTTP semantics`](http://tools.ietf.org/html/rfc7231). + +### Deleting Resources + +A resource can be deleted by sending a `DELETE` request to the URL +that represents the resource: + +```http +DELETE /photos/1 HTTP/1.1 +Accept: application/vnd.api+json +``` + +#### Responses + +##### 200 OK + +A server **MAY** return a `200 OK` response with a document that contains no +primary data if a deletion request is successful. Other top-level members, such +as [meta], could be included in the response document. + +##### 202 Accepted + +If a deletion request has been accepted for processing, but the processing has +not been completed by the time the server responds, the server **MUST** +return a `202 Accepted` status code. + +##### 204 No Content + +If a deletion request is successful, the server **MUST** return either a `200 +OK` status code and response document (as described above) or a `204 No Content` +status code with no response document. + +##### 404 NOT FOUND + +A server **SHOULD** return a `404 Not Found` status code if a deletion request fails +due to the resource not existing. + +##### Other Responses + +A server **MAY** respond with other HTTP status codes. + +A server **MAY** include [error details] with error responses. + +A server **MUST** prepare responses, and a client **MUST** interpret +responses, in accordance with +[`HTTP semantics`](http://tools.ietf.org/html/rfc7231). + +## Query Parameters + +### Query Parameter Families + +Although "query parameter" is a common term in everyday web development, it is +not a well-standardized concept. Therefore, JSON:API provides its own +[definition of a query parameter](#appendix-query-details). + +For the most part, JSON:API's definition coincides with colloquial usage, and its +details can be safely ignored. However, one important consequence of this +definition is that a URL like the following is considered to have two distinct +query parameters: + +``` +/?page[offset]=0&page[limit]=10 +``` + +The two parameters are named `page[offset]` and `page[limit]`; there is no +single `page` parameter. + +In practice, however, parameters like `page[offset]` and `page[limit]` are +usually defined and processed together, and it's convenient to refer to them +collectively. Therefore, JSON:API introduces the concept of a query parameter +family. + +A "query parameter family" is the set of all query parameters whose name starts +with a "base name", followed by zero or more instances of empty square brackets +(i.e. `[]`) or square-bracketed legal [member names]. The family is referred to +by its base name. + +For example, the `filter` query parameter family includes parameters named: +`filter`, `filter[x]`, `filter[]`, `filter[x][]`, `filter[][]`, `filter[x][y]`, +etc. However, `filter[_]` is not a valid parameter name in the family, because +`_` is not a valid [member name][member names]. + +### Extension-Specific Query Parameters + +The base name of every query parameter introduced by an extension **MUST** be +prefixed with the extension's namespace followed by a colon (`:`). The +remainder of the base name **MUST** contain only the characters \[a-z\] (U+0061 +to U+007A, "a-z"). + +### Implementation-Specific Query Parameters + +Implementations **MAY** support custom query parameters. However, the names of +these query parameters **MUST** come from a [family][query parameter family] +whose base name is a legal [member name][member names] and also contains at least +one non a-z character (i.e., outside U+0061 to U+007A). + +It is **RECOMMENDED** that a capital letter (e.g. camelCasing) be used to +satisfy the above requirement. + +If a server encounters a query parameter that does not follow the naming +conventions above, or the server does not know how to process it as a query +parameter from this specification, it **MUST** return `400 Bad Request`. + +> Note: By forbidding the use of query parameters that contain only the characters +> \[a-z\], JSON:API is reserving the ability to standardize additional query +> parameters later without conflicting with existing implementations. + +## Errors + +### Processing Errors + +A server **MAY** choose to stop processing as soon as a problem is encountered, +or it **MAY** continue processing and encounter multiple problems. For instance, +a server might process multiple attributes and then return multiple validation +problems in a single response. + +When a server encounters multiple problems for a single request, the most +generally applicable HTTP error code **SHOULD** be used in the response. For +instance, `400 Bad Request` might be appropriate for multiple 4xx errors +or `500 Internal Server Error` might be appropriate for multiple 5xx errors. + +### Error Objects + +Error objects provide additional information about problems encountered while +performing an operation. Error objects **MUST** be returned as an array +keyed by `errors` in the top level of a JSON:API document. + +An error object **MAY** have the following members, and **MUST** contain at +least one of: + +* `id`: a unique identifier for this particular occurrence of the problem. +* `links`: a [links object][links] that **MAY** contain the following members: + * `about`: a [link][link] that leads to further details about this + particular occurrence of the problem. When derefenced, this URI **SHOULD** + return a human-readable description of the error. + * `type`: a [link][link] that identifies the type of error that this + particular error is an instance of. This URI **SHOULD** be dereferencable to + a human-readable explanation of the general error. +* `status`: the HTTP status code applicable to this problem, expressed as a + string value. This **SHOULD** be provided. +* `code`: an application-specific error code, expressed as a string value. +* `title`: a short, human-readable summary of the problem that **SHOULD NOT** + change from occurrence to occurrence of the problem, except for purposes of + localization. +* `detail`: a human-readable explanation specific to this occurrence of the + problem. Like `title`, this field's value can be localized. +* `source`: an object containing references to the primary source of the error. + It **SHOULD** include one of the following members or be omitted: + * `pointer`: a JSON Pointer [[RFC6901](https://tools.ietf.org/html/rfc6901)] + to the value in the request document that caused the error [e.g. `"/data"` + for a primary data object, or `"/data/attributes/title"` for a specific + attribute]. This **MUST** point to a value in the request document that + exists; if it doesn't, the client **SHOULD** simply ignore the pointer. + * `parameter`: a string indicating which URI query parameter caused + the error. + * `header`: a string indicating the name of a single request header which + caused the error. +* `meta`: a [meta object][meta] containing non-standard meta-information about the + error. + +## Appendix +### Query Parameters Details +#### Parsing/Serialization +A query parameter is a name–value pair extracted from, or serialized into, a +URI's query string. + +To extract the query parameters from a URI, an implementation **MUST** run the +URI's query string, excluding the leading question mark, through the +[`application/x-www-form-urlencoded` parsing algorithm](https://url.spec.whatwg.org/#urlencoded-parsing), +with one exception: JSON:API allows the specification that defines a query +parameter's usage to provide its own rules for parsing the parameter's value +from the `value` bytes identified in steps 3.2 and and 3.3 of the `application/x-www-form-urlencoded` +parsing algorithm. The resulting value might not be a string. + +> Note: In general, the query string parsing built in to servers and browsers +> will match the process specified above, so most implementations do not need +> to worry about this. +> +> The `application/x-www-form-urlencoded` format is referenced because it is +> the basis for the `a=b&c=d` style that almost all query strings use today. +> +> However, `application/x-www-form-urlencoded` parsing contains the bizarre +> historical artifact that `+` characters must be treated as spaces, and it +> requires that all values be percent-decoded during parsing, which makes it +> impossible to use [RFC 3986 delimiter characters](https://tools.ietf.org/html/rfc3986#section-2.2) +> as delimiters. These issues motivate the exception that JSON:API defines above. + +Similarly, to serialize a query parameter into a URI, an implementation **MUST** +use the [the `application/x-www-form-urlencoded` serializer](https://url.spec.whatwg.org/#concept-urlencoded-serializer), +with the corresponding exception that a parameter's value — but not its name — +may be serialized differently than that algorithm requires, provided the +serialization does not interfere with the ability to parse back the resulting URI. + +#### Square Brackets in Parameter Names +With [query parameter families][query parameter family], JSON:API allows for +query parameters whose names contain square brackets (i.e., U+005B "[" and +U+005D "]"). + +According to the query parameter serialization rules above, a compliant +implementation will percent-encode these square brackets. However, some URI +producers — namely browsers — do not always encode them. Servers **SHOULD** +accept requests in which these square brackets are left unencoded in a query +parameter's name. If a server does accept these requests, it **MUST** treat the +request as equivalent to one in which the square brackets were percent-encoded. + +[semantics]: #semantics +[top level]: #document-top-level +[resource objects]: #document-resource-objects +[attributes]: #document-resource-object-attributes +[relationships]: #document-resource-object-relationships +[fields]: #document-resource-object-fields +[related resource link]: #document-resource-object-related-resource-links +[resource linkage]: #document-resource-object-linkage +[resource links]: #document-resource-object-links +[resource identifier object]: #document-resource-identifier-objects +[compound document]: #document-compound-documents +[meta]: #document-meta +[links]: #document-links +[link]: #document-links-link +[link object]: #document-links-link-object +[link relation type]: #document-links-link-relation-type +[extensions]: #extensions +[profiles]: #profiles +[error details]: #errors +[error object]: #error-objects +[error objects]: #errror-objects +[member names]: #document-member-names +[pagination]: #fetching-pagination +[query parameter family]: #query-parameters-families diff --git a/tests/files/format_1.1/normative-statements.json b/tests/files/format_1.1/normative-statements.json new file mode 100644 index 0000000..55c742c --- /dev/null +++ b/tests/files/format_1.1/normative-statements.json @@ -0,0 +1,2737 @@ +{ + "jsonapi": { + "version": "1.1" + }, + "data": [ + { + "id": "content-negotiation", + "type": "sections", + "attributes": { + "title": "Content Negotiation" + }, + "links": { + "self": "http://jsonapi.org/format/#content-negotiation" + }, + "relationships": { + "statements": { + "data": [ + { "id": "request-content-type", "type": "normative-statements" }, + { "id": "request-accept", "type": "normative-statements" }, + { "id": "response-ignore-parameters", "type": "normative-statements" }, + { "id": "response-content-type", "type": "normative-statements"}, + { "id": "response-unsupported-media-type", "type": "normative-statements"}, + { "id": "response-not-acceptable", "type": "normative-statements"} + ] + } + } + }, + { + "id": "document-structure", + "type": "sections", + "attributes": { + "title": "Document Structure" + }, + "links": { + "self": "http://jsonapi.org/format/#document-structure" + }, + "relationships": { + "statements": { + "data": [ + { "id": "additional-members", "type": "normative-statements" }, + { "id": "ignore-additional-members", "type": "normative-statements" }, + { "id": "json-object", "type": "normative-statements" }, + { "id": "required-top-level", "type": "normative-statements" }, + { "id": "data-errors", "type": "normative-statements" }, + { "id": "optional-top-level", "type": "normative-statements" }, + { "id": "data-included", "type": "normative-statements" }, + { "id": "top-level-links", "type": "normative-statements" }, + { "id": "primary-data", "type": "normative-statements" }, + { "id": "logical-collection", "type": "normative-statements" }, + { "id": "resource-required-top-level", "type": "normative-statements" }, + { "id": "resource-optional-top-level", "type": "normative-statements" }, + { "id": "resource-id-type", "type": "normative-statements" }, + { "id": "resource-id-type-types", "type": "normative-statements" }, + { "id": "resource-unique", "type": "normative-statements" }, + { "id": "resource-type-constraints", "type": "normative-statements" }, + { "id": "resource-fields", "type": "normative-statements" }, + { "id": "resource-attributes-key", "type": "normative-statements" }, + { "id": "resource-attributes-reserve-members", "type": "normative-statements" }, + { "id": "resource-attributes-reserve-members", "type": "normative-statements" }, + { "id": "resource-relationships-key", "type": "normative-statements" }, + { "id": "resource-relationships-object", "type": "normative-statements" }, + { "id": "resource-relationships-pagination", "type": "normative-statements" }, + { "id": "resource-related-resource-link", "type": "normative-statements" }, + { "id": "resource-related-resource-link-change", "type": "normative-statements" }, + { "id": "resource-linkage", "type": "normative-statements" }, + { "id": "resource-links", "type": "normative-statements" }, + { "id": "resource-link-response", "type": "normative-statements" }, + { "id": "resource-identifier-required-members", "type": "normative-statements" }, + { "id": "resource-identifier-optional-member", "type": "normative-statements" }, + { "id": "compound-documents-allow", "type": "normative-statements" }, + { "id": "compound-documents-top-level-included", "type": "normative-statements" }, + { "id": "compound-documents-full-linkage", "type": "normative-statements" }, + { "id": "compound-documents-duplicates", "type": "normative-statements" }, + { "id": "meta-objects", "type": "normative-statements" }, + { "id": "meta-object-members", "type": "normative-statements" }, + { "id": "top-level-links", "type": "normative-statements" }, + { "id": "top-level-links-members", "type": "normative-statements" }, + { "id": "top-level-json-api-member", "type": "normative-statements" }, + { "id": "json-api-type", "type": "normative-statements" }, + { "id": "json-api-version", "type": "normative-statements" }, + { "id": "json-api-meta", "type": "normative-statements" }, + { "id": "member-name-case", "type": "normative-statements" }, + { "id": "member-name-character", "type": "normative-statements" }, + { "id": "member-name-allowed-characters-only", "type": "normative-statements" }, + { "id": "member-name-globally-allowed", "type": "normative-statements" }, + { "id": "member-name-url-safe", "type": "normative-statements" }, + { "id": "member-name-allowed-characters", "type": "normative-statements" }, + { "id": "member-name-reserved-characters", "type": "normative-statements" }, + { "id": "member-name-at-members-definition", "type": "normative-statements" }, + { "id": "member-name-at-members-where", "type": "normative-statements" }, + { "id": "member-name-at-members-no-process", "type": "normative-statements" }, + { "id": "member-name-at-members-ignore-for-definitions", "type": "normative-statements" } + ] + } + } + }, + { + "id": "reading", + "type": "sections", + "attributes": { + "title": "Fetching Data" + }, + "links": { + "self": "http://jsonapi.org/format/#fetching" + }, + "relationships": { + "statements": { + "data": [ + { "id": "fetch-url-support", "type": "normative-statements" }, + { "id": "fetch-response-code", "type": "normative-statements" }, + { "id": "fetch-primary-data-collection", "type": "normative-statements" }, + { "id": "fetch-primary-data-single", "type": "normative-statements" }, + { "id": "fetch-responses-404", "type": "normative-statements" }, + { "id": "fetch-responses-other-status-codes", "type": "normative-statements" }, + { "id": "fetch-responses-error-details", "type": "normative-statements" }, + { "id": "fetch-responses-http-semantics", "type": "normative-statements" }, + { "id": "fetch-relationships", "type": "normative-statements" }, + { "id": "fetch-relationships-response-200", "type": "normative-statements" }, + { "id": "fetch-relationships-response-200-primary-data", "type": "normative-statements" }, + { "id": "fetch-relationships-response-200-self-related", "type": "normative-statements" }, + { "id": "fetch-relationships-response-404", "type": "normative-statements" }, + { "id": "fetch-relationships-response-exists-empty", "type": "normative-statements" }, + { "id": "fetch-relationships-other-status-codes", "type": "normative-statements" }, + { "id": "fetch-relationships-other-error-details", "type": "normative-statements" }, + { "id": "fetch-relationships-http-semantics", "type": "normative-statements" }, + { "id": "inclusion-default", "type": "normative-statements" }, + { "id": "inclusion-include-parameter", "type": "normative-statements" }, + { "id": "inclusion-unrequested", "type": "normative-statements" }, + { "id": "inclusion-include-parameter-value", "type": "normative-statements" }, + { "id": "inclusion-bad-request", "type": "normative-statements" }, + { "id": "sparse-fieldsets-parameter", "type": "normative-statements" }, + { "id": "sparse-fieldsets-parameter-value", "type": "normative-statements" }, + { "id": "sparse-fieldsets-additional-fields", "type": "normative-statements" }, + { "id": "sorting-option", "type": "normative-statements" }, + { "id": "sorting-parameter", "type": "normative-statements" }, + { "id": "sorting-parameter-value", "type": "normative-statements" }, + { "id": "sorting-multiple-fields", "type": "normative-statements" }, + { "id": "sorting-multiple-fields-order", "type": "normative-statements" }, + { "id": "sorting-order", "type": "normative-statements" }, + { "id": "sorting-not-supported", "type": "normative-statements" }, + { "id": "sorting-specified-order", "type": "normative-statements" }, + { "id": "sorting-default", "type": "normative-statements" }, + { "id": "pagination", "type": "normative-statements" }, + { "id": "pagination-links", "type": "normative-statements" }, + { "id": "pagination-links-object", "type": "normative-statements" }, + { "id": "pagination-keys", "type": "normative-statements" }, + { "id": "pagination-unavailable-link", "type": "normative-statements" }, + { "id": "pagination-order", "type": "normative-statements" }, + { "id": "pagination-page-parameter", "type": "normative-statements" }, + { "id": "filtering", "type": "normative-statements" } + ] + } + } + }, + { + "id": "creating-updating-deleting", + "type": "sections", + "attributes": { + "title": "Creating, Updating and Deleting Resources" + }, + "links": { + "self": "http://jsonapi.org/format/#crud" + }, + "relationships": { + "statements": { + "data": [ + { "id": "create-support", "type": "normative-statements" }, + { "id": "modify-delete-support", "type": "normative-statements" }, + { "id": "crud-atomic", "type": "normative-statements" }, + { "id": "create-single-resource", "type": "normative-statements" }, + { "id": "create-type-member", "type": "normative-statements" }, + { "id": "create-relationships-member", "type": "normative-statements" }, + { "id": "create-accept-client-generated-ids", "type": "normative-statements" }, + { "id": "create-client-generated-ids-key", "type": "normative-statements" }, + { "id": "create-client-generated-ids-uuid", "type": "normative-statements" }, + { "id": "create-client-generated-ids-forbidden", "type": "normative-statements" }, + { "id": "create-responses-201-status", "type": "normative-statements" }, + { "id": "create-responses-201-location", "type": "normative-statements" }, + { "id": "create-responses-201-document", "type": "normative-statements" }, + { "id": "create-responses-201-self", "type": "normative-statements" }, + { "id": "create-responses-202", "type": "normative-statements" }, + { "id": "create-responses-204", "type": "normative-statements" }, + { "id": "create-responses-403", "type": "normative-statements" }, + { "id": "create-responses-404-related", "type": "normative-statements" }, + { "id": "create-responses-409-exists", "type": "normative-statements" }, + { "id": "create-responses-409-bad-type", "type": "normative-statements" }, + { "id": "create-responses-409-error-details", "type": "normative-statements" }, + { "id": "create-responses-other-status", "type": "normative-statements" }, + { "id": "create-responses-other-error-details", "type": "normative-statements" }, + { "id": "create-http-semantics", "type": "normative-statements" }, + { "id": "update-patch-resource", "type": "normative-statements" }, + { "id": "update-patch-resource-members", "type": "normative-statements" }, + { "id": "update-resource-attributes", "type": "normative-statements" }, + { "id": "update-interpret-resource-attributes", "type": "normative-statements" }, + { "id": "update-resource-relationships", "type": "normative-statements" }, + { "id": "update-interpret-resource-relationships", "type": "normative-statements" }, + { "id": "update-resource-relationship-value", "type": "normative-statements" }, + { "id": "update-resource-relationship-reject-full-replacement", "type": "normative-statements" }, + { "id": "update-resource-relationship-reject-full-replacement-response", "type": "normative-statements" }, + { "id": "update-resource-202-status", "type": "normative-statements" }, + { "id": "update-resource-200-status", "type": "normative-statements" }, + { "id": "update-resource-relationship-200-response", "type": "normative-statements" }, + { "id": "update-resource-200-meta", "type": "normative-statements" }, + { "id": "update-resource-200-meta-representation", "type": "normative-statements" }, + { "id": "update-resource-204-status", "type": "normative-statements" }, + { "id": "update-resource-403-status", "type": "normative-statements" }, + { "id": "update-resource-404-status", "type": "normative-statements" }, + { "id": "update-resource-404-related", "type": "normative-statements" }, + { "id": "update-resource-409-status", "type": "normative-statements" }, + { "id": "update-resource-409-no-match", "type": "normative-statements" }, + { "id": "update-resource-409-details", "type": "normative-statements" }, + { "id": "update-resource-409-details", "type": "normative-statements" }, + { "id": "update-resource-other-status", "type": "normative-statements" }, + { "id": "update-resource-other-status", "type": "normative-statements" }, + { "id": "update-resource-other-semantics", "type": "normative-statements" }, + { "id": "update-resource-http-semantics", "type": "normative-statements" }, + { "id": "respond-patch-to-one-relationship-link", "type": "normative-statements" }, + { "id": "patch-to-one-data-member", "type": "normative-statements" }, + { "id": "patch-to-one-response", "type": "normative-statements" }, + { "id": "respond-patch-post-delete-to-many-relationship-link", "type": "normative-statements" }, + { "id": "patch-post-delete-to-many-data-member", "type": "normative-statements" }, + { "id": "patch-to-many-complete-replace", "type": "normative-statements" }, + { "id": "post-to-many-add", "type": "normative-statements" }, + { "id": "post-to-many-add-again", "type": "normative-statements" }, + { "id": "post-to-many-add-again", "type": "normative-statements" }, + { "id": "post-to-many-response", "type": "normative-statements" }, + { "id": "delete-to-many", "type": "normative-statements" }, + { "id": "delete-to-many", "type": "normative-statements" }, + { "id": "delete-to-many-success", "type": "normative-statements" }, + { "id": "updating-relationship-202-status", "type": "normative-statements" }, + { "id": "updating-relationship-204-status", "type": "normative-statements" }, + { "id": "updating-relationship-200-status", "type": "normative-statements" }, + { "id": "updating-relationship-200-response", "type": "normative-statements" }, + { "id": "updating-relationship-200-meta", "type": "normative-statements" }, + { "id": "updating-relationship-200-meta-content", "type": "normative-statements" }, + { "id": "updating-relationship-403-status", "type": "normative-statements" }, + { "id": "updating-relationship-other-status", "type": "normative-statements" }, + { "id": "updating-relationship-other-details", "type": "normative-statements" }, + { "id": "update-relationship-http-semantics", "type": "normative-statements" }, + { "id": "delete-202-status", "type": "normative-statements" }, + { "id": "delete-204-status", "type": "normative-statements" }, + { "id": "delete-200-status", "type": "normative-statements" }, + { "id": "delete-404-status", "type": "normative-statements" }, + { "id": "deleting-other-status", "type": "normative-statements" }, + { "id": "deleting-other-details", "type": "normative-statements" }, + { "id": "deleting-http-semantics", "type": "normative-statements" } + ] + } + } + }, + { + "id": "query-parameters", + "type": "sections", + "attributes": { + "title": "Query Parameters" + }, + "links": { + "self": "http://jsonapi.org/format/#query-parameters" + }, + "relationships": { + "statements": { + "data": [ + { "id": "query-parameters-non-alpha", "type": "normative-statements"}, + { "id": "query-parameters-under-camel", "type": "normative-statements"}, + { "id": "query-parameters-bad-request", "type": "normative-statements"} + ] + } + } + }, + { + "id": "errors", + "type": "sections", + "attributes": { + "title": "Errors" + }, + "links": { + "self": "http://jsonapi.org/format/#errors" + }, + "relationships": { + "statements": { + "data": [ + { "id": "error-stop-processing", "type": "normative-statements" }, + { "id": "error-general", "type": "normative-statements" }, + { "id": "error-object-key", "type": "normative-statements" }, + { "id": "error-object-members", "type": "normative-statements" } + ] + } + } + } + ], + "included": [ + { + "id": "request-content-type", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "Clients **MUST** send all JSON:API data in request documents with the header `Content-Type: application/vnd.api+json` without any media type parameters." + }, + "relationships": { + "section": { + "data": { "id": "content-negotiation", "type": "sections" } + } + } + }, + { + "id": "request-accept", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "Clients that include the JSON:API media type in their `Accept` header **MUST** specify the media type there at least once without any media type parameters." + }, + "relationships": { + "section": { + "data": { "id": "content-negotiation", "type": "sections" } + } + } + }, + { + "id": "response-ignore-parameters", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "Clients **MUST** ignore any parameters for the `application/vnd.api+json` media type received in the `Content-Type` header of response documents." + }, + "relationships": { + "section": { + "data": { "id": "content-negotiation", "type": "sections" } + } + } + }, + { + "id": "response-content-type", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "Servers **MUST** send all JSON:API data in response documents with the header `Content-Type: application/vnd.api+json` without any media type parameters." + }, + "relationships": { + "section": { + "data": { "id": "content-negotiation", "type": "sections" } + } + } + }, + { + "id": "response-unsupported-media-type", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "Servers **MUST** respond with a `415 Unsupported Media Type` status code if a request specifies the header `Content-Type: application/vnd.api+json` with any media type parameters" + }, + "relationships": { + "section": { + "data": { "id": "content-negotiation", "type": "sections" } + } + } + }, + { + "id": "response-not-acceptable", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "Servers **MUST** respond with a `406 Not Acceptable` status code if a request's `Accept` header contains the JSON:API media type and all instances of that media type are modified with media type parameters." + }, + "relationships": { + "section": { + "data": { "id": "content-negotiation", "type": "sections" } + } + } + }, + { + "id": "additional-members", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "Unless otherwise noted, objects defined by this specification **MUST NOT** contain any additional members. " + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "ignore-additional-members", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "Client and server implementations **MUST** ignore members not recognized by this specification." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "json-object", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A JSON object MUST be at the root of every JSON:API request and response containing data. This object defines a document's \"top level\"." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "required-top-level", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A document **MUST** contain at least one of the following top-level members:\\n\\n- `data`: the document's \"primary data\"\\n- `errors`: an array of error objects\\n- meta`: a meta object that contains non-standard meta-information." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "data-errors", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "The members `data` and `errors` **MUST NOT** coexist in the same document." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "optional-top-level", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "A document **MAY** contain any of these top-level members:\\n\\n- `jsonapi`: an object describing the server's implementation\\n- `links`: a links object related to the primary data.\\n- `included`: an array of resource objects that are related to the primary data and/or each other (\"included resources\")." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "data-included", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "If a document does not contain a top-level `data` key, the `included` member **MUST NOT** be present either." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "top-level-links", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "The top-level links object **MAY** contain the following members:\\n\\n- `self`: the link that generated the current response document.\\n- `related`: a related resource link when the primary data represents a resource relationship.\\n- pagination links for the primary data." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "primary-data", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "Primary data **MUST** be either:\\n\\n- a single resource object, a single resource identifier object, or `null`, for requests that target single resources\\n- an array of resource objects, an array of resource identifier objects, or an empty array (`[]`), for requests that target resource collections" + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "logical-collection", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A logical collection of resources **MUST** be represented as an array, even if it only contains one item or is empty." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "resource-required-top-level", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A resource object MUST contain at least the following top-level members:\\n\\n- `id`\\n- `type`\\n\\nException: The `id` member is not required when the resource object originates at the client and represents a new resource to be created on the server." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "resource-optional-top-level", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "In addition, a resource object MAY contain any of these top-level members:\\n\\n- `attributes`: an attributes object representing some of the resource's data.\\n- `relationships`: a relationships object describing relationships between the resource and other JSON:API resources.\\n -`links`: a links object containing links related to the resource.\\n- `meta`: a meta object containing non-standard meta-information about a resource that can not be represented as an attribute or relationship." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "resource-id-type", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "Every resource object **MUST** contain an `id` member and a `type` member." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "resource-id-type-types", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "The values of the `id` and `type` members **MUST** be strings." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "resource-unique", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "Within a given API, each resource object's `type` and `id` pair **MUST** identify a single, unique resource. (The set of URIs controlled by a server, or multiple servers acting as one, constitute an API.)" + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "resource-type-constraints", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "The values of `type` members **MUST** adhere to the same constraints as member names." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "resource-fields", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "Fields for a resource object **MUST** share a common namespace with each other and with `type` and `id`. In other words, a resource can not have an attribute and relationship with the same name, nor can it have an attribute or relationship named `type` or id." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "resource-attributes-key", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "The value of the `attributes` key **MUST** be an object (an \"attributes object\")." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "resource-attributes-reserve-members", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "any object that constitutes or is contained in an attribute **MUST** reserve the `relationships` and `links` members for future use." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "resource-attributes-reserve-members", + "type": "normative-statements", + "attributes": { + "level": "SHOULD", + "description": "Although has-one foreign keys (e.g. author_id) are often stored internally alongside other information to be represented in a resource object, these keys **SHOULD NOT** appear as attributes." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "resource-relationships-key", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "The value of the `relationships` key **MUST** be an object (a \"relationships object\")." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "resource-relationships-object", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A \"relationship object\" **MUST** contain at least one of the following:]\\n\\n- `links`: a links object containing at least one of the following:\\n - `self`: a link for the relationship itself (a \"relationship link\"). This link 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.\\n - `related`: a related resource link\\n- `data`: resource linkage\\n- `meta`: a meta object that contains non-standard meta-information about the relationship." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "resource-relationships-pagination", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "A relationship object that represents a to-many relationship **MAY** also contain pagination links under the links member" + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "resource-related-resource-link", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "If present, a related resource link **MUST** reference a valid URL, even if the relationship isn't currently associated with any target resources." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "resource-related-resource-link-change", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "a related resource link **MUST NOT** change because its relationship's content changes." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "resource-linkage", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "Resource linkage MUST be represented as one of the following:\\n\\n- `null` for empty to-one relationships.\\n- an empty array (`[]`) for empty to-many relationships.\\n- a single resource identifier object for non-empty to-one relationships.\\n- an array of resource identifier objects for non-empty to-many relationships." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "resource-links", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "If present, this links object **MAY** contain a `self` link that identifies the resource represented by the resource object." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "resource-link-response", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A server **MUST** respond to a `GET` request to the specified URL with a response that includes the resource as the primary data." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "resource-identifier-required-members", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A \"resource identifier object\" **MUST** contain type and id members." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "resource-identifier-optional-member", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "A \"resource identifier object\" **MAY** also include a meta member, whose value is a meta object that contains non-standard meta-information." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "compound-documents-allow", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "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\"." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "compound-documents-top-level-included", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "In a compound document, all included resources **MUST** be represented as an array of resource objects in a top-level `included` member." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "compound-documents-full-linkage", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "Compound documents require \"full linkage\", meaning that every included resource **MUST** be identified by at least one resource identifier object in the same document. These resource identifier objects could either be primary data or represent resource linkage contained within primary or included resources. The only exception to the full linkage requirement is when relationship fields that would otherwise contain linkage data are excluded via sparse fieldsets." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "compound-documents-duplicates", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A compound document **MUST NOT** include more than one resource object for each type and id pair." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "meta-objects", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "The value of each meta member **MUST** be an object (a \"meta object\")." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "meta-object-members", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "Any members **MAY** be specified within meta objects." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "top-level-links", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "The value of each links member **MUST** be an object (a \"links object\")." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "top-level-links-members", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "Each member of a links object is a \"link\". A link **MUST** be represented as either:\\n\\n- a string containing the link's URL.\\n- an object (\"link object\") which can contain the following members:\\n - `href`: a string containing the link's URL.\\n - `meta`: a meta object containing non-standard meta-information about the link." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "top-level-json-api-member", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "A JSON:API document **MAY** include information about its implementation under a top level `jsonapi` member." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "json-api-type", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "If present, the value of the `jsonapi` member **MUST** be an object (a \"jsonapi object\")" + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "json-api-version", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "The jsonapi object **MAY** contain a `version` member whose value is a string indicating the highest JSON:API version supported." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "json-api-meta", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "The jsonapi object **MAY** also contain a `meta` member, whose value is a meta object that contains non-standard meta-information." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "member-name-case", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "All member names used in a JSON:API document **MUST** be treated as case sensitive by clients and servers" + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "member-name-character", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "Member names **MUST** contain at least one character." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "member-name-allowed-characters-only", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "Member names **MUST** contain only the allowed characters listed below." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "member-name-globally-allowed", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "Member names MUST start and end with a \"globally allowed character\", as defined below." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "member-name-url-safe", + "type": "normative-statements", + "attributes": { + "level": "RECOMMENDED", + "description": "it is **RECOMMENDED** that member names use only non-reserved, URL safe characters specified in RFC 3986." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "member-name-allowed-characters", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "The following \"globally allowed characters\" **MAY** be used anywhere in a member name:\\n\\n- U+0061 to U+007A, \"a-z\"\\n- U+0041 to U+005A, \"A-Z\"\\n- U+0030 to U+0039, \"0-9\"\\n- any UNICODE character except U+0000 to U+007F (not recommended, not URL safe)" + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "member-name-reserved-characters", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "The following characters **MUST NOT** be used in member names:\\n\\n- U+002B PLUS SIGN, \"+\" (used for ordering)\\n- U+002C COMMA, \",\" (used separator for multiple relationship paths)\\n- U+002E PERIOD, \".\" (used as relationship path separators)\\n- U+005B LEFT SQUARE BRACKET, \"[\" (use in sparse fieldsets)\\n- U+005D RIGHT SQUARE BRACKET, \"]\" (used in sparse fieldsets)\\n- U+0021 EXCLAMATION MARK, \"!\"\\n- U+0022 QUOTATION MARK, '\"'\\n- U+0023 NUMBER SIGN, \"#\"\\n- U+0024 DOLLAR SIGN, \"$\"\\n- U+0025 PERCENT SIGN, \"%\"\\n- U+0026 AMPERSAND, \"&\"\\n- U+0027 APOSTROPHE, \"'\"\\n- U+0028 LEFT PARENTHESIS, \"(\"\\n- U+0029 RIGHT PARENTHESIS, \")\"\\n- U+002A ASTERISK, \"*\"\\n- U+002F SOLIDUS, \"/\"\\n- U+003A COLON, \":\"\\n- U+003B SEMICOLON, \";\"\\n- U+003C LESS-THAN SIGN, \"<\"\\n- U+003D EQUALS SIGN, \"=\"\\n- U+003E GREATER-THAN SIGN, \">\"\\n- U+003F QUESTION MARK, \"?\"\\n- U+0040 COMMERCIAL AT, \"@\"\\n- U+005C REVERSE SOLIDUS, \"\\\"\\n- U+005E CIRCUMFLEX ACCENT, \"^\"\\n- U+0060 GRAVE ACCENT, \"`\"\\n- U+007B LEFT CURLY BRACKET, \"{\"\\n- U+007C VERTICAL LINE, \"|\"\\n- U+007D RIGHT CURLY BRACKET, \"}\"\\n- U+007E TILDE, \"~\"" + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "member-name-at-members-definition", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "Member names **MAY** also begin with an at sign (U+0040 COMMERCIAL AT, \"@\"). Members named this way are called \"@-Members\"." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "member-name-at-members-where", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "@-Members **MAY** appear anywhere in a JSON:API document." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "member-name-at-members-no-process", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "JSON:API processors **MUST** completely ignore @-Members (i.e. not treat them as JSON:API data)." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "member-name-at-members-ignore-for-definitions", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "Moreover, the existence of @-Members **MUST** be ignored when interpreting all JSON:API definitions and processing instructions given outside of the @-Members subsection." + }, + "relationships": { + "section": { + "data": { "id": "document-structure", "type": "sections" } + } + } + }, + { + "id": "fetch-url-support", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A server **MUST** support fetching resource data for every URL provided as:\\n\\n- a self link as part of the top-level links object\\n- a self link as part of a resource-level links object\\n- a related link as part of a relationship-level links object" + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "fetch-response-code", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A server **MUST** respond to a successful request to fetch an individual resource or resource collection with a `200 OK` response." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "fetch-primary-data-collection", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A server **MUST** respond to a successful request to fetch a resource collection with an array of resource objects or an empty array (`[]`) as the response document's primary data." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "fetch-primary-data-single", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A server **MUST** respond to a successful request to fetch an individual resource with a resource object or `null` provided as the response document's primary data.\\n\\n`null` is only an appropriate response when the requested URL is one that might correspond to a single resource, but doesn't currently." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "fetch-responses-404", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A server **MUST** respond with `404 Not Found` when processing a request to fetch a single resource that does not exist, except when the request warrants a `200 OK` response with `null` as the primary data (as described above)." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "fetch-responses-other-status-codes", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "A server **MAY** respond with other HTTP status codes." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "fetch-responses-error-details", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "A server **MAY** include error details with error responses." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "fetch-responses-http-semantics", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A server **MUST** prepare responses, and a client **MUST** interpret responses, in accordance with HTTP semantics." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "fetch-relationships", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A server **MUST** support fetching relationship data for every relationship URL provided as a self link as part of a relationship's links object." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "fetch-relationships-response-200", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A server **MUST** respond to a successful request to fetch a relationship with a `200 OK` response." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "fetch-relationships-response-200-primary-data", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "The primary data in the response document **MUST** match the appropriate value for resource linkage, as described above for relationship objects." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "fetch-relationships-response-200-self-related", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "The top-level links object **MAY** contain self and related links, as described above for relationship objects." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "fetch-relationships-response-404", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A server **MUST** return 404 Not Found when processing a request to fetch a relationship link URL that does not exist." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "fetch-relationships-response-exists-empty", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "If a relationship link URL exists but the relationship is empty, then `200 OK` **MUST** be returned, as described above." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "fetch-relationships-other-status-codes", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "A server **MAY** respond with other HTTP status codes." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "fetch-relationships-other-error-details", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "A server **MAY** include error details with error responses." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "fetch-relationships-http-semantics", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A server **MUST** prepare responses, and a client **MUST** interpret responses, in accordance with HTTP semantics." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "inclusion-default", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "An endpoint **MAY** return resources related to the primary data by default." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "inclusion-include-parameter", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "An endpoint **MAY** also support an `include` request parameter to allow the client to customize which related resources should be returned." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "inclusion-unrequested", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "If an endpoint supports the include parameter and a client supplies it, the server **MUST NOT** include unrequested resource objects in the included section of the compound document." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "inclusion-include-parameter-value", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "The value of the include parameter **MUST** be a comma-separated (U+002C COMMA, \",\") list of relationship paths. A relationship path is a dot-separated (U+002E FULL-STOP, \".\") list of relationship names." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "inclusion-bad-request", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "If a server is unable to identify a relationship path or does not support inclusion of resources from a path, it **MUST** respond with 400 Bad Request." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "sparse-fieldsets-parameter", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "An endpoint **MAY** also support a `fields[TYPE]` request parameter to allow the client to customize which related resources should be returned." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "sparse-fieldsets-parameter-value", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "The value of the fields parameter **MUST** be a comma-separated (U+002C COMMA, \",\") list that refers to the name(s) of the fields to be returned." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "sparse-fieldsets-additional-fields", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "If a client requests a restricted set of fields, an endpoint **MUST NOT** include additional fields in the response." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "sorting-option", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "A server **MAY** choose to support requests to sort resource collections according to one or more criteria (\"sort fields\")." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "sorting-parameter", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "An endpoint **MAY** support requests to sort the primary data with a sort query parameter." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "sorting-parameter-value", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "The value for sort **MUST** represent sort fields." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "sorting-multiple-fields", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "An endpoint **MAY** support multiple sort fields by allowing comma-separated (U+002C COMMA, \",\") sort fields." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "sorting-multiple-fields-order", + "type": "normative-statements", + "attributes": { + "level": "SHOULD", + "description": "Sort fields **SHOULD** be applied in the order specified." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "sorting-order", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "The sort order for each sort field **MUST** be ascending unless it is prefixed with a minus (U+002D HYPHEN-MINUS, \"-\"), in which case it **MUST** be descending." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "sorting-not-supported", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "If the server does not support sorting as specified in the query parameter sort, it **MUST** return `400 Bad Request`." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "sorting-specified-order", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "If sorting is supported by the server and requested by the client via query parameter sort, the server **MUST** return elements of the top-level data array of the response ordered according to the criteria specified." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "sorting-default", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "The server **MAY** apply default sorting rules to top-level data if request parameter sort is not specified." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "pagination", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "A server **MAY** choose to limit the number of resources returned in a response to a subset (\"page\") of the whole set available." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "pagination-links", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "A server **MAY** provide links to traverse a paginated data set (\"pagination links\")." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "pagination-links-object", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "Pagination links **MUST** appear in the links object that corresponds to a collection." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "pagination-keys", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "The following keys MUST be used for pagination links:\\n\\n- `first`: the first page of data\\n- `last`: the last page of data\\n- `prev`: the previous page of data\\n- `next`: the next page of data" + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "pagination-unavailable-link", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "Keys **MUST** either be omitted or have a `null` value to indicate that a particular link is unavailable." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "pagination-order", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "Concepts of order, as expressed in the naming of pagination links, **MUST** remain consistent with JSON:API's sorting rules." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "pagination-page-parameter", + "type": "normative-statements", + "attributes": { + "level": "SHOULD", + "description": "The `page` query parameter is reserved for pagination. Servers and clients *SHOULD* use this key for pagination operations." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "filtering", + "type": "normative-statements", + "attributes": { + "level": "SHOULD", + "description": "The `filter` query parameter is reserved for filtering data. Servers and clients **SHOULD** use this key for filtering operations." + }, + "relationships": { + "section": { + "data": { "id": "reading", "type": "sections" } + } + } + }, + { + "id": "create-support", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "A server **MAY** allow resources of a given type to be created." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "modify-delete-support", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "It **MAY** also allow existing resources to be modified or deleted." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "crud-atomic", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A request **MUST** completely succeed or fail (in a single \"transaction\"). No partial updates are allowed." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "create-single-resource", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "The request **MUST** include a single resource object as primary data." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "create-type-member", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "The resource object **MUST** contain at least a type member." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "create-relationships-member", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "If a relationship is provided in the `relationships` member of the resource object, its value **MUST** be a relationship object with a `data` member." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "create-accept-client-generated-ids", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "A server **MAY** accept a client-generated ID along with a request to create a resource." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "create-client-generated-ids-key", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "An ID **MUST** be specified with an `id` key, the value of which **MUST** be a universally unique identifier." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "create-client-generated-ids-uuid", + "type": "normative-statements", + "attributes": { + "level": "SHOULD", + "description": "The client **SHOULD** use a properly generated and formatted *UUID* as described in RFC 4122 [RFC4122]." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "create-client-generated-ids-forbidden", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A server **MUST** return `403 Forbidden` in response to an unsupported request to create a resource with a client-generated ID." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "create-responses-201-status", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "If a `POST` request did not include a Client-Generated ID and the requested resource has been created successfully, the server **MUST** return a `201 Created` status code." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "create-responses-201-location", + "type": "normative-statements", + "attributes": { + "level": "SHOULD", + "description": "The response **SHOULD** include a `Location` header identifying the location of the newly created resource." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "create-responses-201-document", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "The response **MUST** also include a document that contains the primary resource created." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "create-responses-201-self", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "If the resource object returned by the response contains a `self` key in its `links` member and a `Location` header is provided, the value of the `self` member **MUST** match the value of the `Location` header." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "create-responses-202", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "If a request to create a resource has been accepted for processing, but the processing has not been completed by the time the server responds, the server **MUST** return a `202 Accepted` status code." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "create-responses-204", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "If a `POST` request did include a Client-Generated ID and the requested resource has been created successfully, the server **MUST** return either a `201 Created` status code and response document (as described above) or a `204 No Content` status code with no response document." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "create-responses-403", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "A server **MAY** return `403 Forbidden` in response to an unsupported request to create a resource." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "create-responses-404-related", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "A server **MUST** return `404 Not Found` when processing a request that references a related resource that does not exist." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "create-responses-409-exists", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A server **MUST** return `409 Conflict` when processing a `POST` request to create a resource with a client-generated ID that already exists." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "create-responses-409-bad-type", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A server **MUST** return `409 Conflict` when processing a `POST` request in which the resource object's type is not among the type(s) that constitute the collection represented by the endpoint." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "create-responses-409-error-details", + "type": "normative-statements", + "attributes": { + "level": "SHOULD", + "description": "A server **SHOULD** include error details and provide enough information to recognize the source of the conflict." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "create-responses-other-status", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "A server **MAY** respond with other HTTP status codes." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "create-responses-other-error-details", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "A server **MAY** include error details with error responses." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "create-http-semantics", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A server **MUST** prepare responses, and a client **MUST** interpret responses, in accordance with HTTP semantics." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "update-patch-resource", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "The `PATCH` request **MUST** include a single resource object as primary data." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "update-patch-resource-members", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "The resource object **MUST** contain `type` and `id` members." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "update-resource-attributes", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "Any or all of a resource's attributes **MAY** be included in the resource object included in a `PATCH` request." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "update-interpret-resource-attributes", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "If a request does not include all of the attributes for a resource, the server **MUST** interpret the missing attributes as if they were included with their current values. It **MUST NOT** interpret them as `null` values." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "update-resource-relationships", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "Any or all of a resource's relationships **MAY** be included in the resource object included in a `PATCH` request." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "update-interpret-resource-relationships", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "If a request does not include all of the relationships for a resource, the server **MUST** interpret the missing relationships as if they were included with their current values. It **MUST NOT** interpret them as `null` or empty values." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "update-resource-relationship-value", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "If a relationship is provided in the relationships member of a resource object in a `PATCH` request, its value **MUST** be a relationship object with a data member." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "update-resource-relationship-reject-full-replacement", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "A server **MAY** reject an attempt to do a full replacement of a to-many relationship." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "update-resource-relationship-reject-full-replacement-response", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "In such a case, the server **MUST** reject the entire update, and return a `403 Forbidden` response." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "update-resource-202-status", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "If an update request has been accepted for processing, but the processing has not been completed by the time the server responds, the server **MUST** return a `202 Accepted` status code." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "update-resource-200-status", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "If a server accepts an update but also changes the resource(s) in ways other than those specified by the request (for example, updating the `updated-at` attribute or a computed `sha`), it **MUST** return a `200 OK` response." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "update-resource-relationship-200-response", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "The response document **MUST** include a representation of the updated resource(s) as if a `GET` request was made to the request URL." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "update-resource-200-meta", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A server **MUST** return a `200 OK` status code if an update is successful, the client's current attributes remain up to date, and the server responds only with top-level meta data." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "update-resource-200-meta-representation", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "In this case the server **MUST NOT** include a representation of the updated resource(s)." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "update-resource-204-status", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "If an update is successful and the server doesn't update any attributes besides those provided, the server **MUST** return either a `200 OK` status code and response document (as described above) or a `204 No Content` status code with no response document." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "update-resource-403-status", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A server **MUST** return `403 Forbidden` in response to an unsupported request to update a resource or relationship." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "update-resource-404-status", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A server **MUST** return `404 Not Found` when processing a request to modify a resource that does not exist." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "update-resource-404-related", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A server **MUST** return `404 Not Found` when processing a request that references a related resource that does not exist." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "update-resource-409-status", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "A server **MAY** return `409 Conflict` when processing a `PATCH` request to update a resource if that update would violate other server-enforced constraints (such as a uniqueness constraint on a property other than `id`)." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "update-resource-409-no-match", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A server **MUST** return `409 Conflict` when processing a `PATCH` request in which the resource object's `type` and `id` do not match the server's endpoint." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "update-resource-409-details", + "type": "normative-statements", + "attributes": { + "level": "SHOULD", + "description": "A server **SHOULD** include error details and provide enough information to recognize the source of the conflict." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "update-resource-409-details", + "type": "normative-statements", + "attributes": { + "level": "SHOULD", + "description": "A server **SHOULD** include error details and provide enough information to recognize the source of the conflict." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "update-resource-other-status", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "A server **MAY** respond with other `HTTP` status codes." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "update-resource-other-status", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "A server **MAY** include error details with error responses." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "update-resource-other-semantics", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "A server **MAY** include error details with error responses." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "update-resource-http-semantics", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A server **MUST** prepare responses, and a client **MUST** interpret responses, in accordance with HTTP semantics." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "respond-patch-to-one-relationship-link", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A server **MUST** respond to `PATCH` requests to a URL from a to-one relationship link as described below." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "patch-to-one-data-member", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "The PATCH request MUST include a top-level member named data containing one of:\\n\\n- a resource identifier object corresponding to the new related resource.\\n- `null`, to remove the relationship." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "patch-to-one-response", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "If the relationship is updated successfully then the server **MUST** return a successful response." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "respond-patch-post-delete-to-many-relationship-link", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A server **MUST** respond to `PATCH`, `POST`, and `DELETE` requests to a URL from a to-many relationship link as described below." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "patch-post-delete-to-many-data-member", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "For all request types, the body **MUST** contain a `data` member whose value is an empty array or an array of resource identifier objects." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "patch-to-many-complete-replace", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "If a client makes a `PATCH` request to a URL from a to-many relationship link, the server **MUST** either completely replace every member of the relationship, return an appropriate error response if some resources can not be found or accessed, or return a `403 Forbidden` response if complete replacement is not allowed by the server." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "post-to-many-add", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "If a client makes a `POST` request to a URL from a relationship link, the server **MUST** add the specified members to the relationship unless they are already present." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "post-to-many-add-again", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "If a given `type` and `id` is already in the relationship, the server **MUST NOT** add it again." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "post-to-many-add-again", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "If a given `type` and `id` is already in the relationship, the server **MUST NOT** add it again." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "post-to-many-response", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "If all of the specified resources can be added to, or are already present in, the relationship then the server **MUST** return a successful response." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "delete-to-many", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "If the client makes a `DELETE` request to a URL from a relationship link the server **MUST** delete the specified members from the relationship or return a `403 Forbidden` response." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "delete-to-many", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "If the client makes a `DELETE` request to a URL from a relationship link the server **MUST** delete the specified members from the relationship or return a `403 Forbidden` response." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "delete-to-many-success", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "If all of the specified resources are able to be removed from, or are already missing from, the relationship then the server **MUST** return a successful response." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "updating-relationship-202-status", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "If a relationship update request has been accepted for processing, but the processing has not been completed by the time the server responds, the server **MUST** return a `202 Accepted` status code." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "updating-relationship-204-status", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A server **MUST** return a `204 No Content` status code if an update is successful and the representation of the resource in the request matches the result." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "updating-relationship-200-status", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "If a server accepts an update but also changes the targeted relationship(s) in other ways than those specified by the request, it **MUST** return a `200 OK` response." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "updating-relationship-200-response", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "The response document **MUST** include a representation of the updated relationship(s)." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "updating-relationship-200-meta", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A server **MUST** return a `200 OK` status code if an update is successful, the client's current data remain up to date, and the server responds only with top-level meta data." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "updating-relationship-200-meta-content", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "In this case the server **MUST NOT** include a representation of the updated relationship(s)." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "updating-relationship-403-status", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A server **MUST** return `403 Forbidden` in response to an unsupported request to update a relationship." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "updating-relationship-other-status", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "A server **MAY** respond with other HTTP status codes." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "updating-relationship-other-details", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "A server **MAY** include error details with error responses." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "update-relationship-http-semantics", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A server **MUST** prepare responses, and a client **MUST** interpret responses, in accordance with HTTP semantics." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "delete-202-status", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "If a deletion request has been accepted for processing, but the processing has not been completed by the time the server responds, the server **MUST** return a `202 Accepted` status code." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "delete-204-status", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A server **MUST** return a `204 No Content` status code if a deletion request is successful and no content is returned." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "delete-200-status", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A server **MUST** return a `200 OK` status code if a deletion request is successful and the server responds with only top-level meta data." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "delete-404-status", + "type": "normative-statements", + "attributes": { + "level": "SHOULD", + "description": "A server **SHOULD** return a 404 Not Found status code if a deletion request fails due to the resource not existing." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "deleting-other-status", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "A server **MAY** respond with other HTTP status codes." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "deleting-other-details", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "A server **MAY** include error details with error responses." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "deleting-http-semantics", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "A server **MUST** prepare responses, and a client **MUST** interpret responses, in accordance with HTTP semantics." + }, + "relationships": { + "section": { + "data": { "id": "creating-updating-deleting", "type": "sections" } + } + } + }, + { + "id": "query-parameters-non-alpha", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "Implementation specific query parameters **MUST** adhere to the same constraints as member names with the additional requirement that they **MUST** contain at least one non a-z character (U+0061 to U+007A)." + }, + "relationships": { + "section": { + "data": { "id": "query-parameters", "type": "sections" } + } + } + }, + { + "id": "query-parameters-under-camel", + "type": "normative-statements", + "attributes": { + "level": "RECOMMENDED", + "description": "It is RECOMMENDED that a U+002D HYPHEN-MINUS, \"-\", U+005F LOW LINE, \"_\", or capital letter is used (e.g. camelCasing)." + }, + "relationships": { + "section": { + "data": { "id": "query-parameters", "type": "sections" } + } + } + }, + { + "id": "query-parameters-bad-request", + "type": "normative-statements", + "attributes": { + "level": "RECOMMENDED", + "description": "If a server encounters a query parameter that does not follow the naming conventions above, and the server does not know how to process it as a query parameter from this specification, it **MUST** return `400 Bad Request`." + }, + "relationships": { + "section": { + "data": { "id": "query-parameters", "type": "sections" } + } + } + }, + { + "id": "error-stop-processing", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "A server **MAY** choose to stop processing as soon as a problem is encountered, or it **MAY** continue processing and encounter multiple problems." + }, + "relationships": { + "section": { + "data": { "id": "errors", "type": "sections" } + } + } + }, + { + "id": "error-general", + "type": "normative-statements", + "attributes": { + "level": "SHOULD", + "description": "When a server encounters multiple problems for a single request, the most generally applicable HTTP error code **SHOULD** be used in the response." + }, + "relationships": { + "section": { + "data": { "id": "errors", "type": "sections" } + } + } + }, + { + "id": "error-object-key", + "type": "normative-statements", + "attributes": { + "level": "MUST", + "description": "Error objects **MUST** be returned as an array keyed by `errors` in the top level of a JSON:API document." + }, + "relationships": { + "section": { + "data": { "id": "errors", "type": "sections" } + } + } + }, + { + "id": "error-object-members", + "type": "normative-statements", + "attributes": { + "level": "MAY", + "description": "An error object **MAY** have the following members:\\n\\n- `id`: a unique identifier for this particular occurrence of the problem.\\n- `links`: a links object containing the following members:\\n - `about`: a link that leads to further details about this particular occurrence of the problem.\\n- `status`: the HTTP status code applicable to this problem, expressed as a string value.\\n- `code`: an application-specific error code, expressed as a string value.\\n- `title`: a short, human-readable summary of the problem that **SHOULD NOT** change from occurrence to occurrence of the problem, except for purposes of localization.\\n- `detail`: a human-readable explanation specific to this occurrence of the problem.\\n- `source`: an object containing references to the source of the error, optionally including any of the following members:\\n - `pointer`: 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].\\n - `parameter`: a string indicating which URI query parameter caused the error.\\n- `meta`: a meta object containing non-standard meta-information about the error." + }, + "relationships": { + "section": { + "data": { "id": "errors", "type": "sections" } + } + } + } + ] +} From 3e6c56c89cf772d6bfbe027d333bf812c45e2d0b Mon Sep 17 00:00:00 2001 From: Art4 Date: Fri, 27 Oct 2023 16:03:32 +0200 Subject: [PATCH 3/9] Add test for parsing format 1.1 properties --- tests/Functional/ParsingTest.php | 13 +++++++++++++ tests/files/18_jsonapi_object_with_ext_profile.json | 13 +++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 tests/files/18_jsonapi_object_with_ext_profile.json diff --git a/tests/Functional/ParsingTest.php b/tests/Functional/ParsingTest.php index 7d4bdc8..ea88979 100644 --- a/tests/Functional/ParsingTest.php +++ b/tests/Functional/ParsingTest.php @@ -671,4 +671,17 @@ public function testParseNormativeStatementsForVersion11Correctly(): void $this->assertCount(6, $document->get('data')->getKeys()); $this->assertCount(188, $document->get('included')->getKeys()); } + + public function testParseJsonApiObjectWithVersion11Correctly(): void + { + $string = $this->getJsonString('18_jsonapi_object_with_ext_profile.json'); + $document = Parser::parseResponseString($string); + + $this->assertInstanceOf('Art4\JsonApiClient\V1\Document', $document); + $this->assertSame(['meta', 'jsonapi'], $document->getKeys()); + $this->assertSame(['version'], $document->get('jsonapi')->getKeys()); + // TODO: Add support for unknown properties + // $this->assertSame(['version', 'ext', 'profile'], $document->get('jsonapi')->getKeys()); + $this->assertSame('1.1', $document->get('jsonapi.version')); + } } diff --git a/tests/files/18_jsonapi_object_with_ext_profile.json b/tests/files/18_jsonapi_object_with_ext_profile.json new file mode 100644 index 0000000..04750b0 --- /dev/null +++ b/tests/files/18_jsonapi_object_with_ext_profile.json @@ -0,0 +1,13 @@ +{ + "meta": {}, + "jsonapi": { + "version": "1.1", + "ext": [ + "https://jsonapi.org/ext/atomic" + ], + "profile": [ + "http://example.com/profiles/flexible-pagination", + "http://example.com/profiles/resource-versioning" + ] + } + } From c5149a2d1a92930a7ea457e024fe26af2f2d8aed Mon Sep 17 00:00:00 2001 From: Art4 Date: Fri, 27 Oct 2023 16:07:13 +0200 Subject: [PATCH 4/9] Update achieved goals --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 8954472..4d61b29 100644 --- a/README.md +++ b/README.md @@ -12,12 +12,12 @@ Format: [JSON API 1.0](http://jsonapi.org/format/1.0/) ## :checkered_flag: Goals -* :heavy_check_mark: Be 100% JSON API spec conform -* :heavy_check_mark: Be open for new spec versions -* :heavy_check_mark: Handle/validate a server response body -* :heavy_check_mark: Handle/validate a client request body -* :heavy_check_mark: Offer an easy way to retrieve the data -* :heavy_check_mark: Allow extendability and injection of classes/models +* ✅ Be 100% JSON API spec conform +* ⬜ Be open for new spec versions (see [#83](https://github.com/Art4/json-api-client/issues/83)) +* ✅ Handle/validate a server response body +* ✅ Handle/validate a client request body +* ✅ Offer an easy way to retrieve the data +* ✅ Allow extendability and injection of classes/models ## :package: Install From 9c4824ff6097a5f6d32b13af1fc40af03b85960a Mon Sep 17 00:00:00 2001 From: Art4 Date: Tue, 28 Nov 2023 13:41:09 +0100 Subject: [PATCH 5/9] Fix phpstan errors --- tests/Functional/ParsingTest.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/Functional/ParsingTest.php b/tests/Functional/ParsingTest.php index ea88979..8729849 100644 --- a/tests/Functional/ParsingTest.php +++ b/tests/Functional/ParsingTest.php @@ -656,7 +656,9 @@ public function testParseNormativeStatementsForVersion10Correctly(): void $this->assertInstanceOf('Art4\JsonApiClient\V1\Document', $document); $this->assertSame(['data', 'included', 'jsonapi'], $document->getKeys()); $this->assertSame('1.0', $document->get('jsonapi.version')); + $this->assertInstanceOf(Accessable::class, $document->get('data')); $this->assertCount(6, $document->get('data')->getKeys()); + $this->assertInstanceOf(Accessable::class, $document->get('included')); $this->assertCount(184, $document->get('included')->getKeys()); } @@ -668,7 +670,9 @@ public function testParseNormativeStatementsForVersion11Correctly(): void $this->assertInstanceOf('Art4\JsonApiClient\V1\Document', $document); $this->assertSame(['data', 'included', 'jsonapi'], $document->getKeys()); $this->assertSame('1.1', $document->get('jsonapi.version')); + $this->assertInstanceOf(Accessable::class, $document->get('data')); $this->assertCount(6, $document->get('data')->getKeys()); + $this->assertInstanceOf(Accessable::class, $document->get('included')); $this->assertCount(188, $document->get('included')->getKeys()); } @@ -679,8 +683,9 @@ public function testParseJsonApiObjectWithVersion11Correctly(): void $this->assertInstanceOf('Art4\JsonApiClient\V1\Document', $document); $this->assertSame(['meta', 'jsonapi'], $document->getKeys()); + $this->assertInstanceOf(Accessable::class, $document->get('jsonapi')); $this->assertSame(['version'], $document->get('jsonapi')->getKeys()); - // TODO: Add support for unknown properties + // TODO #90: Add support for unknown properties // $this->assertSame(['version', 'ext', 'profile'], $document->get('jsonapi')->getKeys()); $this->assertSame('1.1', $document->get('jsonapi.version')); } From 67576b4daf08573ece92ec56333ff81845555ad3 Mon Sep 17 00:00:00 2001 From: Art4 Date: Tue, 28 Nov 2023 13:42:16 +0100 Subject: [PATCH 6/9] Remove twitter badge from README.md, fixes #92 --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4d61b29..ec7859c 100644 --- a/README.md +++ b/README.md @@ -12,8 +12,8 @@ Format: [JSON API 1.0](http://jsonapi.org/format/1.0/) ## :checkered_flag: Goals -* ✅ Be 100% JSON API spec conform -* ⬜ Be open for new spec versions (see [#83](https://github.com/Art4/json-api-client/issues/83)) +* ✅ Be 100% JSON API 1.0 spec conform +* ⬜ Be open for new spec minor versions (see [#90](https://github.com/Art4/json-api-client/issues/90)) * ✅ Handle/validate a server response body * ✅ Handle/validate a client request body * ✅ Offer an easy way to retrieve the data @@ -170,7 +170,7 @@ composer run reuse-annotate ## :heart: Credits -- [Artur Weigandt](https://github.com/Art4) [![Twitter](http://img.shields.io/badge/Twitter-@weigandtlabs-blue.svg)](https://twitter.com/weigandtlabs) +- [Artur Weigandt](https://github.com/Art4) - [All Contributors](../../contributors) ## :page_facing_up: License From 5b9443993c68c4124f1caf2f90e9aa34d1de44de Mon Sep 17 00:00:00 2001 From: Art4 Date: Tue, 28 Nov 2023 13:48:40 +0100 Subject: [PATCH 7/9] temporary fix for unknown properties --- tests/Functional/SerializerTest.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/Functional/SerializerTest.php b/tests/Functional/SerializerTest.php index c835969..0adec5d 100644 --- a/tests/Functional/SerializerTest.php +++ b/tests/Functional/SerializerTest.php @@ -78,9 +78,14 @@ public function testParseJsonapiDataWithErrorAbortManager(string $filename, bool $document = $manager->parse($input); + $expected = json_decode($string, true); + // TODO #90: Add support for unknown properties + unset($expected['jsonapi']['ext']); + unset($expected['jsonapi']['profile']); + // Test full array $this->assertEquals( - json_decode($string, true), + $expected, (new ArraySerializer(['recursive' => true]))->serialize($document), $filename ); From 73b67e7bbfa5cb5bb5f391d16f39683a7fbdab05 Mon Sep 17 00:00:00 2001 From: Art4 Date: Tue, 28 Nov 2023 13:57:27 +0100 Subject: [PATCH 8/9] Update code style --- src/Helper/AccessableTrait.php | 10 +++++----- src/Input/RequestStringInput.php | 2 +- src/Input/ResponseStringInput.php | 2 +- src/Input/StringInputTrait.php | 2 +- src/Serializer/ArraySerializer.php | 2 +- src/V1/Attributes.php | 2 +- src/V1/Document.php | 8 ++++---- src/V1/DocumentLink.php | 10 +++++----- src/V1/Error.php | 12 ++++++------ src/V1/ErrorCollection.php | 2 +- src/V1/ErrorLink.php | 8 ++++---- src/V1/ErrorSource.php | 6 +++--- src/V1/Factory.php | 4 ++-- src/V1/Jsonapi.php | 2 +- src/V1/Link.php | 6 +++--- src/V1/Meta.php | 2 +- src/V1/Relationship.php | 4 ++-- src/V1/RelationshipCollection.php | 2 +- src/V1/RelationshipLink.php | 14 +++++++------- src/V1/ResourceCollection.php | 4 ++-- src/V1/ResourceIdentifier.php | 10 +++++----- src/V1/ResourceIdentifierCollection.php | 2 +- src/V1/ResourceItem.php | 12 ++++++------ src/V1/ResourceItemLink.php | 4 ++-- src/V1/ResourceNull.php | 2 +- 25 files changed, 67 insertions(+), 67 deletions(-) diff --git a/src/Helper/AccessableTrait.php b/src/Helper/AccessableTrait.php index 4a6a6ce..6ab503d 100644 --- a/src/Helper/AccessableTrait.php +++ b/src/Helper/AccessableTrait.php @@ -55,7 +55,7 @@ final public function getKeys(): array */ final public function has($key): bool { - if (! is_int($key) && ! is_string($key) && (! is_object($key) || ! $key instanceof AccessKey)) { + if (!is_int($key) && !is_string($key) && (!is_object($key) || !$key instanceof AccessKey)) { trigger_error(sprintf( '%s::has(): Providing Argument #1 ($key) as `%s` is deprecated since 1.2.0, please provide as `int|string` instead.', get_class($this), @@ -74,14 +74,14 @@ final public function has($key): bool return array_key_exists($string, $this->data); } - if (! array_key_exists($string, $this->data)) { + if (!array_key_exists($string, $this->data)) { return false; } $value = $this->getValue($string); // #TODO Handle other objects and arrays - if (! $value instanceof Accessable) { + if (!$value instanceof Accessable) { // throw new AccessException('The existance for the key "' . $key->raw . '" could\'nt be checked.'); return false; } @@ -98,7 +98,7 @@ final public function has($key): bool */ public function get($key) { - if (! is_int($key) && ! is_string($key) && (! is_object($key) || ! $key instanceof AccessKey)) { + if (!is_int($key) && !is_string($key) && (!is_object($key) || !$key instanceof AccessKey)) { trigger_error(sprintf( '%s::get(): Providing Argument #1 ($key) as `%s` is deprecated since 1.2.0, please provide as `int|string` instead.', get_class($this), @@ -120,7 +120,7 @@ public function get($key) } // #TODO Handle other objects and arrays - if (! $value instanceof Accessable) { + if (!$value instanceof Accessable) { throw new AccessException('Could not get the value for the key "' . $key->raw . '".'); } diff --git a/src/Input/RequestStringInput.php b/src/Input/RequestStringInput.php index 322c62b..110f20a 100644 --- a/src/Input/RequestStringInput.php +++ b/src/Input/RequestStringInput.php @@ -43,7 +43,7 @@ public function getAsObject(): \stdClass { $data = $this->decodeJson($this->rawString); - if (! $data instanceof \stdClass) { + if (!$data instanceof \stdClass) { throw new InputException('JSON must contain an object (e.g. `{}`).'); } diff --git a/src/Input/ResponseStringInput.php b/src/Input/ResponseStringInput.php index 10a9b30..fcf300f 100644 --- a/src/Input/ResponseStringInput.php +++ b/src/Input/ResponseStringInput.php @@ -43,7 +43,7 @@ public function getAsObject(): \stdClass { $data = $this->decodeJson($this->rawString); - if (! $data instanceof \stdClass) { + if (!$data instanceof \stdClass) { throw new InputException('JSON must contain an object (e.g. `{}`).'); } diff --git a/src/Input/StringInputTrait.php b/src/Input/StringInputTrait.php index a343026..f04042c 100644 --- a/src/Input/StringInputTrait.php +++ b/src/Input/StringInputTrait.php @@ -27,7 +27,7 @@ trait StringInputTrait */ final public function prepareString($string): string { - if (! is_string($string)) { + if (!is_string($string)) { throw new InputException(sprintf( '$string must be a string, "%s" given.', gettype($string) diff --git a/src/Serializer/ArraySerializer.php b/src/Serializer/ArraySerializer.php index 422037f..14efd00 100644 --- a/src/Serializer/ArraySerializer.php +++ b/src/Serializer/ArraySerializer.php @@ -69,7 +69,7 @@ public function serialize(Accessable $data): ?array */ private function objectTransform($val) { - if (! is_object($val)) { + if (!is_object($val)) { return $val; } elseif ($val instanceof Accessable) { return $this->serialize($val); diff --git a/src/V1/Attributes.php b/src/V1/Attributes.php index 2f4a660..b9ae944 100644 --- a/src/V1/Attributes.php +++ b/src/V1/Attributes.php @@ -29,7 +29,7 @@ final class Attributes extends AbstractElement */ protected function parse($object): void { - if (! is_object($object)) { + if (!is_object($object)) { throw new ValidationException('Attributes has to be an object, "' . gettype($object) . '" given.'); } diff --git a/src/V1/Document.php b/src/V1/Document.php index 4e971cb..1717509 100644 --- a/src/V1/Document.php +++ b/src/V1/Document.php @@ -30,11 +30,11 @@ final class Document extends AbstractElement */ protected function parse($object): void { - if (! is_object($object)) { + if (!is_object($object)) { throw new ValidationException('Document has to be an object, "' . gettype($object) . '" given.'); } - if (! property_exists($object, 'data') and ! property_exists($object, 'meta') and ! property_exists($object, 'errors')) { + if (!property_exists($object, 'data') and !property_exists($object, 'meta') and !property_exists($object, 'errors')) { throw new ValidationException('Document MUST contain at least one of the following properties: data, errors, meta'); } @@ -55,7 +55,7 @@ protected function parse($object): void } if (property_exists($object, 'included')) { - if (! property_exists($object, 'data')) { + if (!property_exists($object, 'data')) { throw new ValidationException('If Document does not contain a `data` property, the `included` property MUST NOT be present either.'); } @@ -104,7 +104,7 @@ private function parseData($data): Accessable return $this->create('ResourceCollection', $data); } - if (! is_object($data)) { + if (!is_object($data)) { throw new ValidationException('Data value has to be null or an object, "' . gettype($data) . '" given.'); } diff --git a/src/V1/DocumentLink.php b/src/V1/DocumentLink.php index 4d0dc0b..569c4e5 100644 --- a/src/V1/DocumentLink.php +++ b/src/V1/DocumentLink.php @@ -34,7 +34,7 @@ final class DocumentLink extends AbstractElement */ protected function parse($object): void { - if (! is_object($object)) { + if (!is_object($object)) { throw new ValidationException( 'DocumentLink has to be an object, "' . gettype($object) . '" given.' ); @@ -43,7 +43,7 @@ protected function parse($object): void $links = get_object_vars($object); if (array_key_exists('self', $links)) { - if (! is_string($links['self']) and ! is_object($links['self'])) { + if (!is_string($links['self']) and !is_object($links['self'])) { throw new ValidationException( 'property "self" has to be a string or object, "' . gettype($links['self']) . '" given.' @@ -56,7 +56,7 @@ protected function parse($object): void } if (array_key_exists('related', $links)) { - if (! is_string($links['related']) and ! is_object($links['related'])) { + if (!is_string($links['related']) and !is_object($links['related'])) { throw new ValidationException( 'property "related" has to be a string or object, "' . gettype($links['related']) . '" given.' @@ -125,7 +125,7 @@ public function get($key) */ private function setPaginationLink(string $name, $value): void { - if (! is_object($value) and ! is_string($value) and ! is_null($value)) { + if (!is_object($value) and !is_string($value) and !is_null($value)) { throw new ValidationException( 'property "' . $name . '" has to be an object, a string or null, "' . gettype($value) . '" given.' @@ -147,7 +147,7 @@ private function setPaginationLink(string $name, $value): void */ private function setLink(string $name, $link): void { - if (! is_string($link) and ! is_object($link)) { + if (!is_string($link) and !is_object($link)) { throw new ValidationException( 'Link attribute has to be an object or string, "' . gettype($link) . '" given.' diff --git a/src/V1/Error.php b/src/V1/Error.php index 346cd55..250f737 100644 --- a/src/V1/Error.php +++ b/src/V1/Error.php @@ -29,14 +29,14 @@ final class Error extends AbstractElement */ protected function parse($object): void { - if (! is_object($object)) { + if (!is_object($object)) { throw new ValidationException( 'Error has to be an object, "' . gettype($object) . '" given.' ); } if (property_exists($object, 'id')) { - if (! is_string($object->id)) { + if (!is_string($object->id)) { throw new ValidationException( 'property "id" has to be a string, "' . gettype($object->id) . '" given.' @@ -51,7 +51,7 @@ protected function parse($object): void } if (property_exists($object, 'status')) { - if (! is_string($object->status)) { + if (!is_string($object->status)) { throw new ValidationException( 'property "status" has to be a string, "' . gettype($object->status) . '" given.' @@ -62,7 +62,7 @@ protected function parse($object): void } if (property_exists($object, 'code')) { - if (! is_string($object->code)) { + if (!is_string($object->code)) { throw new ValidationException( 'property "code" has to be a string, "' . gettype($object->code) . '" given.' @@ -73,7 +73,7 @@ protected function parse($object): void } if (property_exists($object, 'title')) { - if (! is_string($object->title)) { + if (!is_string($object->title)) { throw new ValidationException( 'property "title" has to be a string, "' . gettype($object->title) . '" given.' @@ -84,7 +84,7 @@ protected function parse($object): void } if (property_exists($object, 'detail')) { - if (! is_string($object->detail)) { + if (!is_string($object->detail)) { throw new ValidationException( 'property "detail" has to be a string, "' . gettype($object->detail) . '" given.' diff --git a/src/V1/ErrorCollection.php b/src/V1/ErrorCollection.php index b61e0f2..39e4766 100644 --- a/src/V1/ErrorCollection.php +++ b/src/V1/ErrorCollection.php @@ -29,7 +29,7 @@ final class ErrorCollection extends AbstractElement */ protected function parse($object): void { - if (! is_array($object)) { + if (!is_array($object)) { throw new ValidationException('Errors for a collection has to be in an array, "' . gettype($object) . '" given.'); } diff --git a/src/V1/ErrorLink.php b/src/V1/ErrorLink.php index 6c08772..b4f8d2f 100644 --- a/src/V1/ErrorLink.php +++ b/src/V1/ErrorLink.php @@ -33,17 +33,17 @@ final class ErrorLink extends AbstractElement */ protected function parse($object): void { - if (! is_object($object)) { + if (!is_object($object)) { throw new ValidationException('Link has to be an object, "' . gettype($object) . '" given.'); } $links = get_object_vars($object); - if (! array_key_exists('about', $links)) { + if (!array_key_exists('about', $links)) { throw new ValidationException('ErrorLink MUST contain these properties: about'); } - if (! is_string($links['about']) and ! is_object($links['about'])) { + if (!is_string($links['about']) and !is_object($links['about'])) { throw new ValidationException('Link has to be an object or string, "' . gettype($links['about']) . '" given.'); } @@ -85,7 +85,7 @@ public function get($key) */ private function setLink(string $name, $link): void { - if (! is_string($link) and ! is_object($link)) { + if (!is_string($link) and !is_object($link)) { throw new ValidationException('Link attribute has to be an object or string, "' . gettype($link) . '" given.'); } diff --git a/src/V1/ErrorSource.php b/src/V1/ErrorSource.php index e8136e2..c9730c3 100644 --- a/src/V1/ErrorSource.php +++ b/src/V1/ErrorSource.php @@ -29,12 +29,12 @@ final class ErrorSource extends AbstractElement */ protected function parse($object): void { - if (! is_object($object)) { + if (!is_object($object)) { throw new ValidationException('ErrorSource has to be an object, "' . gettype($object) . '" given.'); } if (property_exists($object, 'pointer')) { - if (! is_string($object->pointer)) { + if (!is_string($object->pointer)) { throw new ValidationException('property "pointer" has to be a string, "' . gettype($object->pointer) . '" given.'); } @@ -42,7 +42,7 @@ protected function parse($object): void } if (property_exists($object, 'parameter')) { - if (! is_string($object->parameter)) { + if (!is_string($object->parameter)) { throw new ValidationException('property "parameter" has to be a string, "' . gettype($object->parameter) . '" given.'); } diff --git a/src/V1/Factory.php b/src/V1/Factory.php index 33efadf..579d225 100644 --- a/src/V1/Factory.php +++ b/src/V1/Factory.php @@ -59,7 +59,7 @@ public function __construct(array $overload = []) */ public function make(string $name, array $args = []): Accessable { - if (! isset($this->classes[$name])) { + if (!isset($this->classes[$name])) { throw new FactoryException('"' . $name . '" is not a registered class'); } @@ -67,7 +67,7 @@ public function make(string $name, array $args = []): Accessable $object = $class->newInstanceArgs($args); - if (! $object instanceof Accessable) { + if (!$object instanceof Accessable) { throw new FactoryException(sprintf( '%s must be instance of `%s`', $this->classes[$name], diff --git a/src/V1/Jsonapi.php b/src/V1/Jsonapi.php index 6a5acbb..3e0cf53 100644 --- a/src/V1/Jsonapi.php +++ b/src/V1/Jsonapi.php @@ -29,7 +29,7 @@ final class Jsonapi extends AbstractElement */ protected function parse($object): void { - if (! is_object($object)) { + if (!is_object($object)) { throw new ValidationException('Jsonapi has to be an object, "' . gettype($object) . '" given.'); } diff --git a/src/V1/Link.php b/src/V1/Link.php index 5e585b1..6a8088e 100644 --- a/src/V1/Link.php +++ b/src/V1/Link.php @@ -29,11 +29,11 @@ final class Link extends AbstractElement */ protected function parse($object): void { - if (! is_object($object)) { + if (!is_object($object)) { throw new ValidationException('Link has to be an object or string, "' . gettype($object) . '" given.'); } - if (! property_exists($object, 'href')) { + if (!property_exists($object, 'href')) { throw new ValidationException('Link must have a "href" attribute.'); } @@ -72,7 +72,7 @@ private function setAsLink(string $name, $link): void } // every link must be an URL - if (! is_string($link)) { + if (!is_string($link)) { throw new ValidationException('Every link attribute has to be a string, "' . gettype($link) . '" given.'); } diff --git a/src/V1/Meta.php b/src/V1/Meta.php index db1dcc1..30bce64 100644 --- a/src/V1/Meta.php +++ b/src/V1/Meta.php @@ -29,7 +29,7 @@ final class Meta extends AbstractElement */ protected function parse($object): void { - if (! is_object($object)) { + if (!is_object($object)) { throw new ValidationException('Meta has to be an object, "' . gettype($object) . '" given.'); } diff --git a/src/V1/Relationship.php b/src/V1/Relationship.php index ec6b0a4..b85017d 100644 --- a/src/V1/Relationship.php +++ b/src/V1/Relationship.php @@ -29,11 +29,11 @@ final class Relationship extends AbstractElement */ protected function parse($object): void { - if (! is_object($object)) { + if (!is_object($object)) { throw new ValidationException('Relationship has to be an object, "' . gettype($object) . '" given.'); } - if (! property_exists($object, 'links') and ! property_exists($object, 'data') and ! property_exists($object, 'meta')) { + if (!property_exists($object, 'links') and !property_exists($object, 'data') and !property_exists($object, 'meta')) { throw new ValidationException('A Relationship object MUST contain at least one of the following properties: links, data, meta'); } diff --git a/src/V1/RelationshipCollection.php b/src/V1/RelationshipCollection.php index fd9c9dc..e14a6de 100644 --- a/src/V1/RelationshipCollection.php +++ b/src/V1/RelationshipCollection.php @@ -29,7 +29,7 @@ final class RelationshipCollection extends AbstractElement */ protected function parse($object): void { - if (! is_object($object)) { + if (!is_object($object)) { throw new ValidationException('Relationships has to be an object, "' . gettype($object) . '" given.'); } diff --git a/src/V1/RelationshipLink.php b/src/V1/RelationshipLink.php index 3cb303f..602b13c 100644 --- a/src/V1/RelationshipLink.php +++ b/src/V1/RelationshipLink.php @@ -38,18 +38,18 @@ final class RelationshipLink extends AbstractElement */ protected function parse($object): void { - if (! is_object($object)) { + if (!is_object($object)) { throw new ValidationException('RelationshipLink has to be an object, "' . gettype($object) . '" given.'); } - if (! property_exists($object, 'self') and ! property_exists($object, 'related')) { + if (!property_exists($object, 'self') and !property_exists($object, 'related')) { throw new ValidationException('RelationshipLink has to be at least a "self" or "related" link'); } $links = get_object_vars($object); if (array_key_exists('self', $links)) { - if (! is_string($links['self']) and ! is_object($links['self'])) { + if (!is_string($links['self']) and !is_object($links['self'])) { throw new ValidationException('property "self" has to be a string or object, "' . gettype($links['self']) . '" given.'); } @@ -59,7 +59,7 @@ protected function parse($object): void } if (array_key_exists('related', $links)) { - if (! is_string($links['related']) and ! is_object($links['related'])) { + if (!is_string($links['related']) and !is_object($links['related'])) { throw new ValidationException('property "related" has to be a string or object, "' . gettype($links['related']) . '" given.'); } @@ -127,12 +127,12 @@ public function get($key) */ private function setPaginationLink(string $name, $value): void { - if (! is_string($value) and ! is_null($value)) { + if (!is_string($value) and !is_null($value)) { throw new ValidationException('property "' . $name . '" has to be a string or null, "' . gettype($value) . '" given.'); } // null is ignored - if (! is_null($value)) { + if (!is_null($value)) { $this->set($name, strval($value)); } } @@ -145,7 +145,7 @@ private function setPaginationLink(string $name, $value): void */ private function setLink(string $name, $link): void { - if (! is_string($link) and ! is_object($link)) { + if (!is_string($link) and !is_object($link)) { throw new ValidationException('Link attribute has to be an object or string, "' . gettype($link) . '" given.'); } diff --git a/src/V1/ResourceCollection.php b/src/V1/ResourceCollection.php index 2b515e0..852ff5d 100644 --- a/src/V1/ResourceCollection.php +++ b/src/V1/ResourceCollection.php @@ -30,13 +30,13 @@ final class ResourceCollection extends AbstractElement */ protected function parse($object): void { - if (! is_array($object)) { + if (!is_array($object)) { throw new ValidationException('Resources for a collection has to be in an array, "' . gettype($object) . '" given.'); } if (count($object) > 0) { foreach ($object as $resource) { - if (! is_object($resource)) { + if (!is_object($resource)) { throw new ValidationException('Resources inside a collection MUST be objects, "' . gettype($resource) . '" given.'); } diff --git a/src/V1/ResourceIdentifier.php b/src/V1/ResourceIdentifier.php index df39116..6f6452c 100644 --- a/src/V1/ResourceIdentifier.php +++ b/src/V1/ResourceIdentifier.php @@ -29,23 +29,23 @@ final class ResourceIdentifier extends AbstractElement */ protected function parse($object): void { - if (! is_object($object)) { + if (!is_object($object)) { throw new ValidationException('Resource has to be an object, "' . gettype($object) . '" given.'); } - if (! property_exists($object, 'type')) { + if (!property_exists($object, 'type')) { throw new ValidationException('A resource object MUST contain a type'); } - if (! is_string($object->type)) { + if (!is_string($object->type)) { throw new ValidationException('A resource type MUST be a string'); } - if (! property_exists($object, 'id')) { + if (!property_exists($object, 'id')) { throw new ValidationException('A resource object MUST contain an id'); } - if (! is_string($object->id)) { + if (!is_string($object->id)) { throw new ValidationException('A resource id MUST be a string'); } diff --git a/src/V1/ResourceIdentifierCollection.php b/src/V1/ResourceIdentifierCollection.php index e820d37..7c2af51 100644 --- a/src/V1/ResourceIdentifierCollection.php +++ b/src/V1/ResourceIdentifierCollection.php @@ -29,7 +29,7 @@ final class ResourceIdentifierCollection extends AbstractElement */ protected function parse($object): void { - if (! is_array($object)) { + if (!is_array($object)) { throw new ValidationException('Resources for a collection has to be in an array, "' . gettype($object) . '" given.'); } diff --git a/src/V1/ResourceItem.php b/src/V1/ResourceItem.php index 35c43b0..913f701 100644 --- a/src/V1/ResourceItem.php +++ b/src/V1/ResourceItem.php @@ -29,15 +29,15 @@ final class ResourceItem extends AbstractElement */ protected function parse($object): void { - if (! is_object($object)) { + if (!is_object($object)) { throw new ValidationException('Resource has to be an object, "' . gettype($object) . '" given.'); } - if (! property_exists($object, 'type')) { + if (!property_exists($object, 'type')) { throw new ValidationException('A resource object MUST contain a type'); } - if (! is_string($object->type)) { + if (!is_string($object->type)) { throw new ValidationException('A resource type MUST be a string'); } @@ -45,13 +45,13 @@ protected function parse($object): void if ( $this->getManager()->getParam('optional_item_id', false) === false - or ! $this->getParent()->has('data') // If parent has no `data` than parent is a ResourceCollection + or !$this->getParent()->has('data') // If parent has no `data` than parent is a ResourceCollection ) { - if (! property_exists($object, 'id')) { + if (!property_exists($object, 'id')) { throw new ValidationException('A resource object MUST contain an id'); } - if (! is_string($object->id)) { + if (!is_string($object->id)) { throw new ValidationException('A resource id MUST be a string'); } diff --git a/src/V1/ResourceItemLink.php b/src/V1/ResourceItemLink.php index cf31ef6..73964d8 100644 --- a/src/V1/ResourceItemLink.php +++ b/src/V1/ResourceItemLink.php @@ -29,7 +29,7 @@ final class ResourceItemLink extends AbstractElement */ protected function parse($object): void { - if (! is_object($object)) { + if (!is_object($object)) { throw new ValidationException('ItemLink has to be an object, "' . gettype($object) . '" given.'); } @@ -64,7 +64,7 @@ private function setLink(string $name, $link): void // from spec: aA link MUST be represented as either: // - a string containing the link's URL. // - an object ("link object") which can contain the following members: - if (! is_object($link) and ! is_string($link)) { + if (!is_object($link) and !is_string($link)) { throw new ValidationException('Link attribute has to be an object or string, "' . gettype($link) . '" given.'); } diff --git a/src/V1/ResourceNull.php b/src/V1/ResourceNull.php index d687293..93c71ab 100644 --- a/src/V1/ResourceNull.php +++ b/src/V1/ResourceNull.php @@ -37,7 +37,7 @@ public function __construct($data, Manager $manager, Accessable $parent) {} */ public function has($key): bool { - if (! is_int($key) && ! is_string($key) && (! is_object($key) || ! $key instanceof AccessKey)) { + if (!is_int($key) && !is_string($key) && (!is_object($key) || !$key instanceof AccessKey)) { trigger_error(sprintf( '%s::has(): Providing Argument #1 ($key) as `%s` is deprecated since 1.2.0, please provide as `int|string` instead.', get_class($this), From 83bddb0ae30186ac012f9a0eb6fd2a51dcfae9db Mon Sep 17 00:00:00 2001 From: Art4 Date: Tue, 28 Nov 2023 13:58:20 +0100 Subject: [PATCH 9/9] Let PHPStan ignore the temporary fix --- tests/Functional/SerializerTest.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/Functional/SerializerTest.php b/tests/Functional/SerializerTest.php index 0adec5d..1838e87 100644 --- a/tests/Functional/SerializerTest.php +++ b/tests/Functional/SerializerTest.php @@ -80,7 +80,9 @@ public function testParseJsonapiDataWithErrorAbortManager(string $filename, bool $expected = json_decode($string, true); // TODO #90: Add support for unknown properties + //** @phpstan-ignore-next-line */ unset($expected['jsonapi']['ext']); + //** @phpstan-ignore-next-line */ unset($expected['jsonapi']['profile']); // Test full array