From 968ddffe5df2bff14ac6a2eb8a644cddcbce03f9 Mon Sep 17 00:00:00 2001
From: Andreas Eberhart <61732435+aeberhart@users.noreply.github.com>
Date: Fri, 21 Oct 2022 13:21:24 +0200
Subject: [PATCH 01/34] Add Dashjoin under Server / Java
---
implementations/index.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/implementations/index.md b/implementations/index.md
index 725a5e8e7..ea45af95b 100644
--- a/implementations/index.md
+++ b/implementations/index.md
@@ -299,6 +299,7 @@ includes related resources.
* [JSON:API for Spring HATEOAS](https://github.com/toedter/spring-hateoas-jsonapi) provides serialization/deserialization of the Spring HATEOAS representation models. The project also provides a builder to easily create relationships, included resource objects, pagination, and more.
* [jsonapi-rvp](https://github.com/xlate/jsonapi-rvp) utilizes the platform capabilities available in the Jakarta EE environment (formerly Java EE) to expose JPA entities via JSON:API. Features include query and entity validation (using Bean Validators), pagination, use of surrogate keys as record identifiers, support for LEFT outer join conditions, and a simple JavaScript client.
* [JSON:API object converter](https://github.com/MieskeB/json-api-spring-boot) converts normal Java objects to JSON:API standard with the use of annotations in the viewmodels (dtos).
+* [Dashjoin Low Code Development Platform](https://github.com/dashjoin/platform) connects to relational, document, and graph databases and makes them available via JSON:API. The platform offers powerful access control mechanisms, a query editor, and graphical layout designers.
### Scala
* [scala-jsonapi](https://github.com/scala-jsonapi/scala-jsonapi) A Scala library for producing JSON output (and deserializing JSON input) based on JSON:API specification.
From 61e0067f7d67b67a8832db1dc406dd2f7c2002f9 Mon Sep 17 00:00:00 2001
From: Jeldrik Hanschke
Date: Fri, 28 Oct 2022 16:47:56 +0200
Subject: [PATCH 02/34] Remove unnecessary and potential confusing alias
"relationships" (#1676)
* remove-confusing-alias-for-relationships-object
* backport to v1.1
---
_format/1.1/index.md | 2 +-
_format/1.2/index.md | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/_format/1.1/index.md b/_format/1.1/index.md
index 54a96e775..9ba32ce84 100644
--- a/_format/1.1/index.md
+++ b/_format/1.1/index.md
@@ -473,7 +473,7 @@ as attributes. Instead, [relationships] **SHOULD** be used.
##### Relationships
The value of the `relationships` key **MUST** be an object (a "relationships
-object"). Each member of a relationships object ("relationships") represents
+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.
diff --git a/_format/1.2/index.md b/_format/1.2/index.md
index f647647a4..c71cef936 100644
--- a/_format/1.2/index.md
+++ b/_format/1.2/index.md
@@ -474,7 +474,7 @@ as attributes. Instead, [relationships] **SHOULD** be used.
##### Relationships
The value of the `relationships` key **MUST** be an object (a "relationships
-object"). Each member of a relationships object ("relationships") represents
+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.
From 5b3b34d97fefcd78509ec7c456f12d718e8f75d2 Mon Sep 17 00:00:00 2001
From: Jeldrik Hanschke
Date: Fri, 21 Apr 2023 23:36:00 +0200
Subject: [PATCH 03/34] allow relationship paths in query parameter families
---
_format/1.2/index.md | 12 +++++++++---
1 file changed, 9 insertions(+), 3 deletions(-)
diff --git a/_format/1.2/index.md b/_format/1.2/index.md
index c71cef936..7a7b92e57 100644
--- a/_format/1.2/index.md
+++ b/_format/1.2/index.md
@@ -2091,13 +2091,19 @@ 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
+(i.e. `[]`), square-bracketed legal [member names] or square-bracketed
+dot-separated lists of 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].
+`filter[x.y]`, etc. However, `filter[_]` is not a valid parameter name in the
+family, because `_` is not a valid [member name][member names].
+
+> Note: Dot separated lists of legal member names are intended to be used for
+> relationship paths. E.g. this allows filtering strategies using relationship
+> paths as defined for [sorting][fetching sorting] in query parameters such as
+> `GET /posts?sort=author.name&filter[author.status]=active`.
### Extension-Specific Query Parameters
From 3dbdc675081b30044a59d5ee1e0cfecdd6e9f2b9 Mon Sep 17 00:00:00 2001
From: Jeldrik Hanschke
Date: Mon, 24 Apr 2023 15:45:38 +0200
Subject: [PATCH 04/34] improve grammar per review comments
---
_format/1.2/index.md | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/_format/1.2/index.md b/_format/1.2/index.md
index 7a7b92e57..4b3462858 100644
--- a/_format/1.2/index.md
+++ b/_format/1.2/index.md
@@ -2091,7 +2091,7 @@ 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. `[]`), square-bracketed legal [member names] or square-bracketed
+(i.e. `[]`), square-bracketed legal [member names], or square-bracketed
dot-separated lists of legal member names. The family is referred to
by its base name.
@@ -2101,9 +2101,9 @@ For example, the `filter` query parameter family includes parameters named:
family, because `_` is not a valid [member name][member names].
> Note: Dot separated lists of legal member names are intended to be used for
-> relationship paths. E.g. this allows filtering strategies using relationship
-> paths as defined for [sorting][fetching sorting] in query parameters such as
-> `GET /posts?sort=author.name&filter[author.status]=active`.
+> relationship paths. For example, this allows filtering strategies using
+> relationship paths as defined for [sorting][fetching sorting] in query parameters
+> such as `GET /posts?sort=author.name&filter[author.status]=active`.
### Extension-Specific Query Parameters
From 3f581040fa7b84667f15caa66d4dc6f0414ab1bc Mon Sep 17 00:00:00 2001
From: Aleksei Nekrasov
Date: Wed, 23 Aug 2023 11:50:02 +0300
Subject: [PATCH 05/34] Add FastAPI-JSONAPI to Python Server libraries
---
implementations/index.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/implementations/index.md b/implementations/index.md
index ea45af95b..353ca62d6 100644
--- a/implementations/index.md
+++ b/implementations/index.md
@@ -267,6 +267,7 @@ the moment.
* [starlette-jsonapi](https://github.com/vladmunteanu/starlette-jsonapi) Microframework on top of Starlette and marshmallow-jsonapi with support for asynchronous ORMs
* [kt.jsonapi](https://pypi.org/project/kt.jsonapi/) JSON:API response generation using the Zope Component Architecture.
* [Flask-COMBO-JSONAPI](https://github.com/AdCombo/flask-combo-jsonapi) JSON:API flask extension for building REST APIs.
+* [FastAPI-JSONAPI](https://github.com/mts-ai/FastAPI-JSONAPI) FastAPI-JSONAPI is a FastAPI extension for building REST APIs
### Go
From 8275119124b4dd51b056052b641a2bb2edfe1358 Mon Sep 17 00:00:00 2001
From: Jeldrik Hanschke
Date: Thu, 2 Nov 2023 21:58:49 +0100
Subject: [PATCH 06/34] backport to v1.1
---
_format/1.1/index.md | 12 +++++++++---
1 file changed, 9 insertions(+), 3 deletions(-)
diff --git a/_format/1.1/index.md b/_format/1.1/index.md
index 9ba32ce84..673422412 100644
--- a/_format/1.1/index.md
+++ b/_format/1.1/index.md
@@ -2090,13 +2090,19 @@ 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
+(i.e. `[]`), square-bracketed legal [member names], or square-bracketed
+dot-separated lists of 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].
+`filter[x.y]`, etc. However, `filter[_]` is not a valid parameter name in the
+family, because `_` is not a valid [member name][member names].
+
+> Note: Dot separated lists of legal member names are intended to be used for
+> relationship paths. For example, this allows filtering strategies using
+> relationship paths as defined for [sorting][fetching sorting] in query parameters
+> such as `GET /posts?sort=author.name&filter[author.status]=active`.
### Extension-Specific Query Parameters
From ba999fdaa2e9c5e8ecae89f3543956e2286119c8 Mon Sep 17 00:00:00 2001
From: Jeldrik Hanschke
Date: Wed, 6 Dec 2023 15:59:42 +0800
Subject: [PATCH 07/34] allow profiles to define implementation-specific query
parameters
---
_format/1.2/index.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/_format/1.2/index.md b/_format/1.2/index.md
index 4b3462858..653d9dab0 100644
--- a/_format/1.2/index.md
+++ b/_format/1.2/index.md
@@ -143,7 +143,7 @@ The rules for profile usage are dictated by [RFC
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** define any query parameters except [implementation-specific query parameters](#query-parameters-custom).
A profile **MUST NOT** alter or remove processing rules that have been defined
by this specification or by an [extension][extensions]. However, a profile
From 86a16dcfe765f429bf271276da448abc9c2f3a97 Mon Sep 17 00:00:00 2001
From: Jeldrik Hanschke
Date: Wed, 6 Dec 2023 16:02:07 +0800
Subject: [PATCH 08/34] backport to v1.1
---
_format/1.1/index.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/_format/1.1/index.md b/_format/1.1/index.md
index 673422412..5271037ad 100644
--- a/_format/1.1/index.md
+++ b/_format/1.1/index.md
@@ -142,7 +142,7 @@ The rules for profile usage are dictated by [RFC
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** define any query parameters except [implementation-specific query parameters](#query-parameters-custom).
A profile **MUST NOT** alter or remove processing rules that have been defined
by this specification or by an [extension][extensions]. However, a profile
From 1e455b05a011b46a25d7515b86294125dec42c9b Mon Sep 17 00:00:00 2001
From: Jeldrik Hanschke
Date: Wed, 6 Dec 2023 21:48:14 +0800
Subject: [PATCH 09/34] avoid kebab-case case usage in example
JSON:API specification recommends using camelCase since some time. But was still using kebab-case for resource type and in URL in one example.
---
recommendations/index.md | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/recommendations/index.md b/recommendations/index.md
index 32903033d..50873358c 100644
--- a/recommendations/index.md
+++ b/recommendations/index.md
@@ -211,17 +211,17 @@ The request **SHOULD** return a status `202 Accepted` with a link in the `Conten
```http
HTTP/1.1 202 Accepted
Content-Type: application/vnd.api+json
-Content-Location: https://example.com/photos/queue-jobs/5234
+Content-Location: https://example.com/photos/jobs/5234
{
"data": {
- "type": "queue-jobs",
+ "type": "jobs",
"id": "5234",
"attributes": {
"status": "Pending request, waiting other process"
},
"links": {
- "self": "/photos/queue-jobs/5234"
+ "self": "/photos/jobs/5234"
}
}
}
@@ -230,7 +230,7 @@ Content-Location: https://example.com/photos/queue-jobs/5234
To check the status of the job process, a client can send a request to the location given earlier.
```http
-GET /photos/queue-jobs/5234 HTTP/1.1
+GET /photos/jobs/5234 HTTP/1.1
Accept: application/vnd.api+json
```
@@ -243,13 +243,13 @@ Retry-After: 10
{
"data": {
- "type": "queue-jobs",
+ "type": "jobs",
"id": "5234",
"attributes": {
"status": "Pending request, waiting other process"
},
"links": {
- "self": "/photos/queue-jobs/5234"
+ "self": "/photos/jobs/5234"
}
}
}
From aa08ac1f4822be27615ae9db2ad811d968390256 Mon Sep 17 00:00:00 2001
From: Antoine Bluchet
Date: Tue, 5 Dec 2023 17:54:48 +0100
Subject: [PATCH 10/34] feat: API Platform implementation
---
implementations/index.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/implementations/index.md b/implementations/index.md
index 353ca62d6..1fbcf07e9 100644
--- a/implementations/index.md
+++ b/implementations/index.md
@@ -184,6 +184,7 @@ and writing of JSON:API documents.
* [pz/doctrine-rest](https://github.com/R3VoLuT1OneR/doctrine-rest) library provides basic tools for implementation of JSON:API with Doctrine 2
* [pz/jsonapi-resource](https://github.com/R3VoLuT1OneR/jsonapi-resource) Serialize PHP class object into JSON:API resource object using PHP 8.0 Attributes.
* [binarcode/laravel-restify](https://github.com/binarcode/laravel-restify) The fastest way to make a powerful JSON:API compatible Rest API with Laravel [Reference Docs](https://restify.binarcode.com/).
+* [API Platform](https://api-platform.com) is an API-first Framework supporting JSON:API.
### Node.js
* [Fortune.js](http://fortune.js.org/) is a library that includes a [comprehensive implementation of JSON:API](https://github.com/fortunejs/fortune-json-api).
From d2355b4c5ddf1cb7a69a0a1f9aa03af03d4d2f56 Mon Sep 17 00:00:00 2001
From: "LB (Ben Johnston)"
Date: Wed, 27 Mar 2024 13:24:47 +1000
Subject: [PATCH 11/34] Docs - Update examples to use lates jsonapi
The example output that has a declared 'jsonapi' value is not updated to use the latest 1.1 version.
---
examples/index.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/examples/index.md b/examples/index.md
index aafcbfa77..5ede6eab7 100644
--- a/examples/index.md
+++ b/examples/index.md
@@ -303,7 +303,7 @@ HTTP/1.1 422 Unprocessable Entity
Content-Type: application/vnd.api+json
{
- "jsonapi": { "version": "1.0" },
+ "jsonapi": { "version": "1.1" },
"errors": [
{
"code": "123",
From 2f577e1891d490d6c43c7026a24e0f9742c5a687 Mon Sep 17 00:00:00 2001
From: Chris Read
Date: Thu, 4 Apr 2024 00:09:01 +1000
Subject: [PATCH 12/34] Add new link for a Python implementation of JSON:API
using generics complete with OpenAPI helper functions
---
implementations/index.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/implementations/index.md b/implementations/index.md
index 1fbcf07e9..3939939a4 100644
--- a/implementations/index.md
+++ b/implementations/index.md
@@ -264,6 +264,7 @@ the moment.
* [Flump](https://github.com/rolepoint/flump) Database agnostic JSON:API builder which depends on Flask and Marshmallow.
* [SAFRS JSON API Framework](https://github.com/thomaxxl/safrs) Flask-SQLAlchemy jsonapi implementation with auto-generated openapi (fka swagger) interface.
* [pydantic-jsonapi](https://github.com/DeanWay/pydantic-jsonapi) JSON:api validation with python type hinting using [pydantic](https://pydantic-docs.helpmanual.io/)
+* [pydanja](https://github.com/Centurix/pydanja) JSON:API implementation for [pydantic](https://pydantic-docs.helpmanual.io/) and [FastAPI](https://fastapi.tiangolo.com/) using generic types, complete with helpers for simplifying OpenAPI docs
* [Flask-Restless-NG](https://github.com/mrevutskyi/flask-restless-ng) Builds JSON:API from SQLAlchemy models using Flask
* [starlette-jsonapi](https://github.com/vladmunteanu/starlette-jsonapi) Microframework on top of Starlette and marshmallow-jsonapi with support for asynchronous ORMs
* [kt.jsonapi](https://pypi.org/project/kt.jsonapi/) JSON:API response generation using the Zope Component Architecture.
From e2b5e0717835e00d19eb67c9cb245601c095e1d6 Mon Sep 17 00:00:00 2001
From: Larry Gebhardt
Date: Mon, 29 Apr 2024 15:43:28 -0400
Subject: [PATCH 13/34] Update CNAME
---
CNAME | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/CNAME b/CNAME
index 7ccb154b0..3c2e2247d 100644
--- a/CNAME
+++ b/CNAME
@@ -1 +1 @@
-jsonapi.org
+www.jsonapi.org
\ No newline at end of file
From 40329de64b556ed48b351473c000aee7c94d0302 Mon Sep 17 00:00:00 2001
From: Larry Gebhardt
Date: Mon, 29 Apr 2024 15:44:04 -0400
Subject: [PATCH 14/34] Update CNAME
---
CNAME | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/CNAME b/CNAME
index 3c2e2247d..f8f8a9e05 100644
--- a/CNAME
+++ b/CNAME
@@ -1 +1 @@
-www.jsonapi.org
\ No newline at end of file
+jsonapi.org
\ No newline at end of file
From af47518badf387775184872680b7f6fb4763ecda Mon Sep 17 00:00:00 2001
From: VGirol
Date: Sun, 19 May 2024 23:48:32 +0200
Subject: [PATCH 15/34] Update JSON Schema to be v1.0 compliant (#1600)
* Update JSON Schema to be v1.0 compliant
* Update schema to be more reusable
* Add schema to validate a request that update a resource or a relationship
* Change naming convention and folder structure to be more consistent
* Enhance reusability
* Lint test files
* Fix typo
* Update JSON Schema to be v1.0 compliant
* Update schema to be more reusable
* Add schema to validate a request that update a resource or a relationship
* Change naming convention and folder structure to be more consistent
* Change all title to more neutral text.
Add meta member to all test files with informations about errors present in the document.
* Add script to valide JSON schema against all test files and update schemas based on the results of the script.
* Add documentation on how to run the tests for the JSON Schemas
* Update validation script of JSON Schema so that validation is also done using files from previous versions
* script linting
* Revert "script linting"
This reverts commit 8ce88f1bb22ab12281e03b480c0273fb89c51167.
* Script linting
* Enhance script output
* Enhance readability
* Add -f and -v options to test script
* Fix typo
* Add test-schema to CI
* Fix typo in bash command to run script
* Change trigger for the "Schema validation test" workflow
* Allow for parallel relationships in JSON Schema
* Bump ajv version
* Update to latest version of json-schema
* Remove node_modules
---
.github/workflows/node.yml | 27 +
.gitignore | 4 +
CONTRIBUTING.md | 25 +
_schemas/1.0/schema.json | 587 ++++++++++++++++++
_schemas/1.0/schema_create_resource.json | 49 ++
_schemas/1.0/schema_update_relationship.json | 23 +
_schemas/1.0/schema_update_resource.json | 49 ++
...source_identifier_must_have_id_member.json | 15 +
.../update/valid/patch_relationship.json | 12 +
.../invalid/data_is_not_resource_object.json | 22 +
.../create/invalid/no_data_member.json | 12 +
...tionship_with_bad_resource_identifier.json | 25 +
.../relationship_with_forbidden_name.json | 26 +
...lationship_with_not_allowed_character.json | 26 +
.../relationship_without_data_member.json | 25 +
.../resource/create/valid/post_resource.json | 8 +
...ost_resource_with_client_generated_id.json | 9 +
.../post_resource_with_relationships.json | 28 +
.../post_resource_without_attributes.json | 5 +
.../invalid/data_must_have_id_member.json | 18 +
.../resource/update/valid/patch_resource.json | 9 +
.../patch_resource_with_relationships.json | 29 +
.../patch_resource_without_attributes.json | 6 +
.../attributes_member_not_valid.json | 20 +
.../attributes_must_not_have_id_member.json | 20 +
.../attributes_must_not_have_type_member.json | 20 +
.../data/data_can_not_be_a_string.json | 13 +
.../data/data_can_not_be_array_of_string.json | 15 +
.../errors/error_must_be_an_object.json | 15 +
.../errors/errors_must_be_an_array.json | 16 +
.../invalid/errors/invalid_error_objects.json | 137 ++++
.../included_member_must_be_collection.json | 39 ++
.../included/included_resource_not_valid.json | 41 ++
.../included/resource_included_twice.json | 50 ++
.../tests/response/invalid/invalid_multi.json | 26 +
.../jsonapi_with_not_allowed_members.json | 16 +
.../invalid/jsonapi/meta_is_not_valid.json | 18 +
.../invalid/jsonapi/not_an_object.json | 22 +
.../jsonapi/version_is_not_a_string.json | 15 +
.../links/link_href_must_be_a_string.json | 17 +
.../links/link_must_be_string_or_object.json | 15 +
.../invalid/links/link_must_be_valid_uri.json | 15 +
.../links/links_must_be_an_object.json | 13 +
.../invalid/meta/meta_must_be_an_object.json | 14 +
.../meta/meta_must_have_valid_members.json | 13 +
.../relationships/link_name_not_allowed.json | 32 +
.../relationships/linkage_must_be_object.json | 24 +
.../relationships/links_not_valid.json | 28 +
.../invalid/relationships/meta_not_valid.json | 30 +
.../relationship_must_not_be_empty.json | 22 +
.../relationship_must_not_be_named_id.json | 27 +
.../relationship_must_not_be_named_type.json | 27 +
...p_must_not_have_additional_properties.json | 28 +
.../relationship_name_is_not_valid.json | 27 +
.../relationships_is_not_an_object.json | 20 +
.../to_many_linkage_not_valid.json | 30 +
.../to_one_linkage_not_valid.json | 28 +
.../invalid/resource/id_must_be_string.json | 19 +
.../resource/relationship_named_id.json | 27 +
.../resource/relationship_named_type.json | 27 +
.../resource_must_have_id_member.json | 18 +
.../resource_must_have_type_member.json | 18 +
.../invalid/resource/type_must_be_string.json | 19 +
.../resource/type_must_not_be_empty.json | 19 +
.../resource/type_value_is_not_valid.json | 19 +
.../resource/with_additional_properties.json | 20 +
.../resource_included_twice.json | 32 +
.../id_must_be_string.json | 16 +
.../resource_must_have_id_member.json | 18 +
.../resource_must_have_type_member.json | 18 +
.../type_must_be_string.json | 16 +
.../type_must_not_be_empty.json | 16 +
.../type_value_is_not_valid.json | 16 +
.../with_additional_properties.json | 17 +
.../data_and_errors_must_not_coexist.json | 22 +
.../top-level/included_must_not_be_alone.json | 21 +
.../invalid/top-level/invalid_root.json | 3 +
...s_must_not_have_additional_properties.json | 15 +
.../no_mandatory_top_level_members.json | 18 +
.../top-level/with_additional_properties.json | 13 +
.../valid/with_failure/errors_and_meta.json | 31 +
.../with_failure/only_errors/one_error.json | 16 +
.../response/valid/with_success/complete.json | 87 +++
.../data_and_included/single_resource.json | 93 +++
.../valid/with_success/data_and_meta.json | 12 +
.../valid/with_success/data_is_null.json | 3 +
.../with_success/linkage/empty_to_many.json | 14 +
.../with_success/linkage/empty_to_one.json | 14 +
.../valid/with_success/linkage/to_many.json | 34 +
.../valid/with_success/linkage/to_one.json | 20 +
.../only_data/empty_resource_collection.json | 3 +
.../only_data/no_resource_null.json | 3 +
.../only_data/parallel_relationships.json | 33 +
.../only_data/resource_collection.json | 28 +
.../resource_identifier_collection.json | 16 +
.../only_data/single_resource.json | 16 +
.../only_data/single_resource_identifier.json | 6 +
...single_resource_with_empty_attributes.json | 7 +
.../valid/with_success/only_meta.json | 9 +
.../with_success/only_meta/empty_meta.json | 3 +
.../only_meta/meta_with_members.json | 9 +
_schemas/scripts/validator.js | 317 ++++++++++
package-lock.json | 117 ++++
package.json | 11 +
schema | 397 ------------
105 files changed, 3261 insertions(+), 397 deletions(-)
create mode 100644 .github/workflows/node.yml
create mode 100644 _schemas/1.0/schema.json
create mode 100644 _schemas/1.0/schema_create_resource.json
create mode 100644 _schemas/1.0/schema_update_relationship.json
create mode 100644 _schemas/1.0/schema_update_resource.json
create mode 100644 _schemas/1.0/tests/request/relationship/update/invalid/resource_identifier_must_have_id_member.json
create mode 100644 _schemas/1.0/tests/request/relationship/update/valid/patch_relationship.json
create mode 100644 _schemas/1.0/tests/request/resource/create/invalid/data_is_not_resource_object.json
create mode 100644 _schemas/1.0/tests/request/resource/create/invalid/no_data_member.json
create mode 100644 _schemas/1.0/tests/request/resource/create/invalid/relationship_with_bad_resource_identifier.json
create mode 100644 _schemas/1.0/tests/request/resource/create/invalid/relationship_with_forbidden_name.json
create mode 100644 _schemas/1.0/tests/request/resource/create/invalid/relationship_with_not_allowed_character.json
create mode 100644 _schemas/1.0/tests/request/resource/create/invalid/relationship_without_data_member.json
create mode 100644 _schemas/1.0/tests/request/resource/create/valid/post_resource.json
create mode 100644 _schemas/1.0/tests/request/resource/create/valid/post_resource_with_client_generated_id.json
create mode 100644 _schemas/1.0/tests/request/resource/create/valid/post_resource_with_relationships.json
create mode 100644 _schemas/1.0/tests/request/resource/create/valid/post_resource_without_attributes.json
create mode 100644 _schemas/1.0/tests/request/resource/update/invalid/data_must_have_id_member.json
create mode 100644 _schemas/1.0/tests/request/resource/update/valid/patch_resource.json
create mode 100644 _schemas/1.0/tests/request/resource/update/valid/patch_resource_with_relationships.json
create mode 100644 _schemas/1.0/tests/request/resource/update/valid/patch_resource_without_attributes.json
create mode 100644 _schemas/1.0/tests/response/invalid/attributes/attributes_member_not_valid.json
create mode 100644 _schemas/1.0/tests/response/invalid/attributes/attributes_must_not_have_id_member.json
create mode 100644 _schemas/1.0/tests/response/invalid/attributes/attributes_must_not_have_type_member.json
create mode 100644 _schemas/1.0/tests/response/invalid/data/data_can_not_be_a_string.json
create mode 100644 _schemas/1.0/tests/response/invalid/data/data_can_not_be_array_of_string.json
create mode 100644 _schemas/1.0/tests/response/invalid/errors/error_must_be_an_object.json
create mode 100644 _schemas/1.0/tests/response/invalid/errors/errors_must_be_an_array.json
create mode 100644 _schemas/1.0/tests/response/invalid/errors/invalid_error_objects.json
create mode 100644 _schemas/1.0/tests/response/invalid/included/included_member_must_be_collection.json
create mode 100644 _schemas/1.0/tests/response/invalid/included/included_resource_not_valid.json
create mode 100644 _schemas/1.0/tests/response/invalid/included/resource_included_twice.json
create mode 100644 _schemas/1.0/tests/response/invalid/invalid_multi.json
create mode 100644 _schemas/1.0/tests/response/invalid/jsonapi/jsonapi_with_not_allowed_members.json
create mode 100644 _schemas/1.0/tests/response/invalid/jsonapi/meta_is_not_valid.json
create mode 100644 _schemas/1.0/tests/response/invalid/jsonapi/not_an_object.json
create mode 100644 _schemas/1.0/tests/response/invalid/jsonapi/version_is_not_a_string.json
create mode 100644 _schemas/1.0/tests/response/invalid/links/link_href_must_be_a_string.json
create mode 100644 _schemas/1.0/tests/response/invalid/links/link_must_be_string_or_object.json
create mode 100644 _schemas/1.0/tests/response/invalid/links/link_must_be_valid_uri.json
create mode 100644 _schemas/1.0/tests/response/invalid/links/links_must_be_an_object.json
create mode 100644 _schemas/1.0/tests/response/invalid/meta/meta_must_be_an_object.json
create mode 100644 _schemas/1.0/tests/response/invalid/meta/meta_must_have_valid_members.json
create mode 100644 _schemas/1.0/tests/response/invalid/relationships/link_name_not_allowed.json
create mode 100644 _schemas/1.0/tests/response/invalid/relationships/linkage_must_be_object.json
create mode 100644 _schemas/1.0/tests/response/invalid/relationships/links_not_valid.json
create mode 100644 _schemas/1.0/tests/response/invalid/relationships/meta_not_valid.json
create mode 100644 _schemas/1.0/tests/response/invalid/relationships/relationship_must_not_be_empty.json
create mode 100644 _schemas/1.0/tests/response/invalid/relationships/relationship_must_not_be_named_id.json
create mode 100644 _schemas/1.0/tests/response/invalid/relationships/relationship_must_not_be_named_type.json
create mode 100644 _schemas/1.0/tests/response/invalid/relationships/relationship_must_not_have_additional_properties.json
create mode 100644 _schemas/1.0/tests/response/invalid/relationships/relationship_name_is_not_valid.json
create mode 100644 _schemas/1.0/tests/response/invalid/relationships/relationships_is_not_an_object.json
create mode 100644 _schemas/1.0/tests/response/invalid/relationships/to_many_linkage_not_valid.json
create mode 100644 _schemas/1.0/tests/response/invalid/relationships/to_one_linkage_not_valid.json
create mode 100644 _schemas/1.0/tests/response/invalid/resource/id_must_be_string.json
create mode 100644 _schemas/1.0/tests/response/invalid/resource/relationship_named_id.json
create mode 100644 _schemas/1.0/tests/response/invalid/resource/relationship_named_type.json
create mode 100644 _schemas/1.0/tests/response/invalid/resource/resource_must_have_id_member.json
create mode 100644 _schemas/1.0/tests/response/invalid/resource/resource_must_have_type_member.json
create mode 100644 _schemas/1.0/tests/response/invalid/resource/type_must_be_string.json
create mode 100644 _schemas/1.0/tests/response/invalid/resource/type_must_not_be_empty.json
create mode 100644 _schemas/1.0/tests/response/invalid/resource/type_value_is_not_valid.json
create mode 100644 _schemas/1.0/tests/response/invalid/resource/with_additional_properties.json
create mode 100644 _schemas/1.0/tests/response/invalid/resource_collection/resource_included_twice.json
create mode 100644 _schemas/1.0/tests/response/invalid/resource_identifier/id_must_be_string.json
create mode 100644 _schemas/1.0/tests/response/invalid/resource_identifier/resource_must_have_id_member.json
create mode 100644 _schemas/1.0/tests/response/invalid/resource_identifier/resource_must_have_type_member.json
create mode 100644 _schemas/1.0/tests/response/invalid/resource_identifier/type_must_be_string.json
create mode 100644 _schemas/1.0/tests/response/invalid/resource_identifier/type_must_not_be_empty.json
create mode 100644 _schemas/1.0/tests/response/invalid/resource_identifier/type_value_is_not_valid.json
create mode 100644 _schemas/1.0/tests/response/invalid/resource_identifier/with_additional_properties.json
create mode 100644 _schemas/1.0/tests/response/invalid/top-level/data_and_errors_must_not_coexist.json
create mode 100644 _schemas/1.0/tests/response/invalid/top-level/included_must_not_be_alone.json
create mode 100644 _schemas/1.0/tests/response/invalid/top-level/invalid_root.json
create mode 100644 _schemas/1.0/tests/response/invalid/top-level/links_must_not_have_additional_properties.json
create mode 100644 _schemas/1.0/tests/response/invalid/top-level/no_mandatory_top_level_members.json
create mode 100644 _schemas/1.0/tests/response/invalid/top-level/with_additional_properties.json
create mode 100644 _schemas/1.0/tests/response/valid/with_failure/errors_and_meta.json
create mode 100644 _schemas/1.0/tests/response/valid/with_failure/only_errors/one_error.json
create mode 100644 _schemas/1.0/tests/response/valid/with_success/complete.json
create mode 100644 _schemas/1.0/tests/response/valid/with_success/data_and_included/single_resource.json
create mode 100644 _schemas/1.0/tests/response/valid/with_success/data_and_meta.json
create mode 100644 _schemas/1.0/tests/response/valid/with_success/data_is_null.json
create mode 100644 _schemas/1.0/tests/response/valid/with_success/linkage/empty_to_many.json
create mode 100644 _schemas/1.0/tests/response/valid/with_success/linkage/empty_to_one.json
create mode 100644 _schemas/1.0/tests/response/valid/with_success/linkage/to_many.json
create mode 100644 _schemas/1.0/tests/response/valid/with_success/linkage/to_one.json
create mode 100644 _schemas/1.0/tests/response/valid/with_success/only_data/empty_resource_collection.json
create mode 100644 _schemas/1.0/tests/response/valid/with_success/only_data/no_resource_null.json
create mode 100644 _schemas/1.0/tests/response/valid/with_success/only_data/parallel_relationships.json
create mode 100644 _schemas/1.0/tests/response/valid/with_success/only_data/resource_collection.json
create mode 100644 _schemas/1.0/tests/response/valid/with_success/only_data/resource_identifier_collection.json
create mode 100644 _schemas/1.0/tests/response/valid/with_success/only_data/single_resource.json
create mode 100644 _schemas/1.0/tests/response/valid/with_success/only_data/single_resource_identifier.json
create mode 100644 _schemas/1.0/tests/response/valid/with_success/only_data/single_resource_with_empty_attributes.json
create mode 100644 _schemas/1.0/tests/response/valid/with_success/only_meta.json
create mode 100644 _schemas/1.0/tests/response/valid/with_success/only_meta/empty_meta.json
create mode 100644 _schemas/1.0/tests/response/valid/with_success/only_meta/meta_with_members.json
create mode 100644 _schemas/scripts/validator.js
create mode 100644 package-lock.json
create mode 100644 package.json
delete mode 100644 schema
diff --git a/.github/workflows/node.yml b/.github/workflows/node.yml
new file mode 100644
index 000000000..21917fc17
--- /dev/null
+++ b/.github/workflows/node.yml
@@ -0,0 +1,27 @@
+name: Schema validation tests
+
+on:
+ push:
+ branches: [ gh-pages, schema-1.0 ]
+ pull_request:
+ branches: [ gh-pages ]
+
+jobs:
+ build:
+
+ runs-on: ubuntu-latest
+
+ strategy:
+ matrix:
+ node-version: [16.x]
+
+ steps:
+ - uses: actions/checkout@v4
+ - name: Use Node.js ${{ matrix.node-version }}
+ uses: actions/setup-node@v3
+ with:
+ node-version: ${{ matrix.node-version }}
+ - name: Install dependencies
+ run: npm ci
+ - name: Test schemas
+ run: npm run test-schema
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index e369c5255..7ae6b8609 100644
--- a/.gitignore
+++ b/.gitignore
@@ -25,3 +25,7 @@ Gemfile.lock
/bin
/b
.rbx
+
+# Ignore the node_modules directory
+
+/node_modules
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 551f1251d..4fdfb8d1a 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -17,3 +17,28 @@ place. Ideas for new extensions and questions about how to correctly implement
or consume an API that adheres to the JSON:API specification belong here.
[about]: http://jsonapi.org/about
+
+## JSON Schema
+If you would like to update the JSON schemas, you must first install the node.js dependencies :
+
+`
+npm install
+`
+
+Then you can run the schema validator by running one of the following commands :
+
+`
+node ./_schemas/scripts/validator.js
+`
+
+or
+
+`
+npm run test-schema
+`
+
+This script validates all test files against all available versions of the specification.
+Some options can be used :
+- `--verbose` : allows more verbose output.
+- `-f relative-path` : the relative path of the only file to test. For example : `npm run test-schema -- -f _schemas/1.0/response/valid/with_success/complete.json`
+- `-v version` : the version of the specification to use to test the files. For example : `npm run test-schema -- -v 1.0`
diff --git a/_schemas/1.0/schema.json b/_schemas/1.0/schema.json
new file mode 100644
index 000000000..c7a0679eb
--- /dev/null
+++ b/_schemas/1.0/schema.json
@@ -0,0 +1,587 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://jsonapi.org/schemas/spec/v1.0/draft",
+ "$comment": "The $id URI should be modified before releasing. This URI does not need to be network addressable. It is an ID only.",
+ "title": "JSON:API Schema",
+ "description": "This schema only validates RESPONSES from a request. Validating request payloads requires slightly different constraints.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/requiredTopLevelMembers"
+ },
+ {
+ "$ref": "#/definitions/oneOfDataOrErrors"
+ }
+ ],
+ "type": "object",
+ "properties": {
+ "data": {
+ "$ref": "#/definitions/data"
+ },
+ "errors": {
+ "$ref": "#/definitions/errors"
+ },
+ "included": {
+ "$ref": "#/definitions/included"
+ },
+ "jsonapi": {
+ "$ref": "#/definitions/jsonapi"
+ },
+ "links": {
+ "description": "Link members related to the primary data.",
+ "$ref": "#/definitions/topLevelLinks"
+ },
+ "meta": {
+ "$ref": "#/definitions/meta"
+ }
+ },
+ "dependencies": {
+ "included": ["data"]
+ },
+ "additionalProperties": false,
+ "definitions": {
+ "attributes": {
+ "description": "Members of the attributes object (\"attributes\") represent information about the resource object in which it's defined.",
+ "type": "object",
+ "propertyNames": {
+ "$ref": "#/definitions/memberName"
+ },
+ "patternProperties": {
+ "": true
+ },
+ "not": {
+ "$comment": "This is what the specification requires, but it seems bad. https://github.com/json-api/json-api/issues/1553",
+ "anyOf": [
+ {
+ "required": [
+ "type"
+ ]
+ },
+ {
+ "required": [
+ "id"
+ ]
+ }
+ ]
+ },
+ "additionalProperties": false
+ },
+ "data": {
+ "description": "The document's \"primary data\" is a representation of the resource or collection of resources targeted by a request.",
+ "oneOf": [
+ {
+ "$ref": "#/definitions/resource"
+ },
+ {
+ "description": "An array of resource objects, an array of resource identifier objects, or an empty array ([]), for requests that target resource collections.",
+ "$ref": "#/definitions/resourceCollection"
+ },
+ {
+ "description": "null if the request is one that might correspond to a single resource, but doesn't currently.",
+ "type": "null"
+ }
+ ]
+ },
+ "empty": {
+ "description": "Describes an empty to-one relationship.",
+ "type": "null"
+ },
+ "error": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "description": "A unique identifier for this particular occurrence of the problem.",
+ "type": "string",
+ "$comment": "The spec doesn't specify that this MUST be a string, so this could be changed to additionally allow numbers"
+ },
+ "links": {
+ "$ref": "#/definitions/errorLinks"
+ },
+ "status": {
+ "description": "The HTTP status code applicable to this problem, expressed as a string value.",
+ "type": "string"
+ },
+ "code": {
+ "description": "An application-specific error code, expressed as a string value.",
+ "type": "string"
+ },
+ "title": {
+ "description": "A short, human-readable summary of the problem. It **SHOULD NOT** change from occurrence to occurrence of the problem, except for purposes of localization.",
+ "type": "string"
+ },
+ "detail": {
+ "description": "A human-readable explanation specific to this occurrence of the problem.",
+ "type": "string"
+ },
+ "source": {
+ "type": "object",
+ "properties": {
+ "pointer": {
+ "description": "A JSON Pointer [RFC6901] to the associated entity in the request document [e.g. \"/data\" for a primary data object, or \"/data/attributes/title\" for a specific attribute].",
+ "type": "string",
+ "pattern": "^(?:\\/(?:[^~/]|~0|~1)*)*$"
+ },
+ "parameter": {
+ "description": "A string indicating which query parameter caused the error.",
+ "type": "string"
+ }
+ }
+ },
+ "meta": {
+ "$ref": "#/definitions/meta"
+ }
+ },
+ "additionalProperties": false
+ },
+ "errorLinks": {
+ "description": "The error links object **MAY** contain the following members: about.",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/definitions/links"
+ }
+ ],
+ "properties": {
+ "about": {
+ "description": "A link that leads to further details about this particular occurrence of the problem.",
+ "$ref": "#/definitions/link"
+ }
+ },
+ "additionalProperties": false
+ },
+ "errors": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/error"
+ },
+ "uniqueItems": true
+ },
+ "included": {
+ "description": "To reduce the number of HTTP requests, servers **MAY** allow responses that include related resources along with the requested primary resources. Such responses are called \"compound documents\".",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/resource"
+ },
+ "uniqueItems": true
+ },
+ "jsonapi": {
+ "description": "An object describing the server's implementation",
+ "type": "object",
+ "properties": {
+ "version": {
+ "type": "string"
+ },
+ "meta": {
+ "$ref": "#/definitions/meta"
+ }
+ },
+ "additionalProperties": false
+ },
+ "linkObject": {
+ "type": "object",
+ "properties": {
+ "href": {
+ "$ref": "#/definitions/linkUrl"
+ },
+ "meta": {
+ "$ref": "#/definitions/meta"
+ }
+ }
+ },
+ "linkUrl": {
+ "description": "A string containing the link's URL.",
+ "type": "string",
+ "format": "uri",
+ "$comment": "URI regex as per https://tools.ietf.org/html/rfc3986#appendix-B",
+ "pattern": "^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?"
+ },
+ "link": {
+ "description": "A link **MUST** be represented as either: a string containing the link's URL or a link object.",
+ "oneOf": [
+ {
+ "$ref": "#/definitions/linkUrl"
+ },
+ {
+ "$ref": "#/definitions/linkObject"
+ }
+ ]
+ },
+ "linkage": {
+ "description": "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.",
+ "oneOf": [
+ {
+ "$ref": "#/definitions/relationshipToOne"
+ },
+ {
+ "$ref": "#/definitions/relationshipToMany"
+ }
+ ]
+ },
+ "links": {
+ "type": "object"
+ },
+ "memberName": {
+ "description": "Member name may contain any valid JSON value.",
+ "type": "string",
+ "pattern": "^[a-zA-Z0-9]{1}(?:[-\\w]*[a-zA-Z0-9])?$"
+ },
+ "meta": {
+ "description": "Non-standard meta-information that can not be represented as an attribute or relationship.",
+ "type": "object",
+ "propertyNames": {
+ "$ref": "#/definitions/memberName"
+ },
+ "patternProperties": {
+ "": true
+ },
+ "additionalProperties": false
+ },
+ "oneOfDataOrErrors": {
+ "type": "object",
+ "dependencies": {
+ "data": {
+ "not": {
+ "required": [
+ "errors"
+ ]
+ }
+ }
+ }
+ },
+ "pagination": {
+ "type": "object",
+ "properties": {
+ "first": {
+ "description": "The first page of data",
+ "oneOf": [
+ {
+ "$ref": "#/definitions/link"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "last": {
+ "description": "The last page of data",
+ "oneOf": [
+ {
+ "$ref": "#/definitions/link"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "prev": {
+ "description": "The previous page of data",
+ "oneOf": [
+ {
+ "$ref": "#/definitions/link"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "next": {
+ "description": "The next page of data",
+ "oneOf": [
+ {
+ "$ref": "#/definitions/link"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ }
+ },
+ "relationship": {
+ "type": "object",
+ "properties": {
+ "links": {
+ "$ref": "#/definitions/relationshipLinks"
+ },
+ "data": {
+ "$ref": "#/definitions/linkage"
+ },
+ "meta": {
+ "$ref": "#/definitions/meta"
+ }
+ },
+ "anyOf": [
+ {
+ "required": ["data"]
+ },
+ {
+ "required": ["meta"]
+ },
+ {
+ "required": ["links"]
+ }
+ ],
+ "additionalProperties": false
+ },
+ "relationshipFromRequest": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "$ref": "#/definitions/linkage"
+ },
+ "meta": {
+ "$ref": "#/definitions/meta"
+ }
+ },
+ "required": [
+ "data"
+ ],
+ "additionalProperties": false
+ },
+ "relationshipLinks": {
+ "description": "A resource object **MAY** contain references to other resource objects (\"relationships\"). Relationships may be to-one or to-many. Relationships can be specified by including a member in a resource's links object.",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/definitions/links"
+ },
+ {
+ "description": "Pagination links",
+ "$ref": "#/definitions/pagination"
+ }
+ ],
+ "properties": {
+ "self": {
+ "description": "A `self` member, whose value is a URL for the relationship itself (a \"relationship URL\"). This URL allows the client to directly manipulate the relationship. For example, it would allow a client to remove an `author` from an `article` without deleting the people resource itself.",
+ "$ref": "#/definitions/link"
+ },
+ "related": {
+ "$ref": "#/definitions/link"
+ },
+ "first": true,
+ "last": true,
+ "next": true,
+ "prev": true
+ },
+ "additionalProperties": false
+ },
+ "relationshipToMany": {
+ "description": "An array of objects each containing \"type\" and \"id\" members for to-many relationships.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/resourceIdentifier"
+ }
+ },
+ "relationshipToOne": {
+ "description": "References to other resource objects in a to-one (\"relationship\"). Relationships can be specified by including a member in a resource's links object.",
+ "oneOf": [
+ {
+ "$ref": "#/definitions/empty"
+ },
+ {
+ "$ref": "#/definitions/resourceIdentifier"
+ }
+ ]
+ },
+ "relationships": {
+ "description": "Members of the relationships object (\"relationships\") represent references from the resource object in which it's defined to other resource objects.",
+ "type": "object",
+ "propertyNames": {
+ "$ref": "#/definitions/memberName"
+ },
+ "patternProperties": {
+ "": {
+ "$ref": "#/definitions/relationship"
+ }
+ },
+ "allOf": [
+ {
+ "$ref": "#/definitions/relationshipsForbiddenMemberName"
+ }
+ ],
+ "additionalProperties": false
+ },
+ "relationshipsFromRequest": {
+ "description": "Members of the relationships object (\"relationships\") represent references from the resource object in which it's defined to other resource objects.",
+ "type": "object",
+ "propertyNames": {
+ "$ref": "#/definitions/memberName"
+ },
+ "patternProperties": {
+ "": {
+ "$ref": "#/definitions/relationshipFromRequest"
+ }
+ },
+ "allOf": [
+ {
+ "$ref": "#/definitions/relationshipsForbiddenMemberName"
+ }
+ ],
+ "additionalProperties": false
+ },
+ "relationshipsForbiddenMemberName": {
+ "not": {
+ "anyOf": [
+ {
+ "type": "object",
+ "required": [
+ "type"
+ ]
+ },
+ {
+ "type": "object",
+ "required": [
+ "id"
+ ]
+ }
+ ]
+ }
+ },
+ "requiredTopLevelMembers": {
+ "anyOf": [
+ {
+ "type": "object",
+ "required": [
+ "meta"
+ ]
+ },
+ {
+ "type": "object",
+ "required": [
+ "data"
+ ]
+ },
+ {
+ "type": "object",
+ "required": [
+ "errors"
+ ]
+ }
+ ]
+ },
+ "resource": {
+ "description": "\"Resource objects\" appear in a JSON:API document to represent resources.",
+ "$comment": "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.",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/definitions/resourceIdentification"
+ }
+ ],
+ "properties": {
+ "id": true,
+ "type": true,
+ "attributes": {
+ "$ref": "#/definitions/attributes"
+ },
+ "links": {
+ "$ref": "#/definitions/resourceLinks"
+ },
+ "meta": {
+ "$ref": "#/definitions/meta"
+ },
+ "relationships": {
+ "$ref": "#/definitions/relationships"
+ }
+ },
+ "additionalProperties": false
+ },
+ "resourceCollection": {
+ "description": "An array of resource objects.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/resource"
+ },
+ "uniqueItems": true
+ },
+ "resourceIdentification": {
+ "allOf": [
+ {
+ "$ref": "#/definitions/resourceIdentificationNew"
+ },
+ {
+ "type": "object",
+ "required": [
+ "type",
+ "id"
+ ]
+ }
+ ]
+ },
+ "resourceIdentificationNew": {
+ "type": "object",
+ "required": [
+ "type"
+ ],
+ "properties": {
+ "type": {
+ "type": "string",
+ "pattern": "^[a-zA-Z0-9]{1}(?:[-\\w]*[a-zA-Z0-9])?$"
+ },
+ "id": {
+ "type": "string"
+ }
+ }
+ },
+ "resourceIdentifier": {
+ "description": "The \"type\" and \"id\" to non-empty members.",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/definitions/resourceIdentification"
+ }
+ ],
+ "properties": {
+ "id": true,
+ "type": true,
+ "meta": {
+ "$ref": "#/definitions/meta"
+ }
+ },
+ "additionalProperties": false
+ },
+ "resourceLinks": {
+ "description": "The top-level links object **MAY** contain the following members: self, related, pagination links.",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/definitions/links"
+ }
+ ],
+ "properties": {
+ "self": {
+ "description": "",
+ "$ref": "#/definitions/link"
+ }
+ },
+ "additionalProperties": false
+ },
+ "topLevelLinks": {
+ "description": "The top-level links object **MAY** contain the following members: self, related, pagination links.",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/definitions/links"
+ },
+ {
+ "description": "Pagination links for the primary data.",
+ "$ref": "#/definitions/pagination"
+ }
+ ],
+ "properties": {
+ "self": {
+ "description": "The link that generated the current response document.",
+ "$ref": "#/definitions/link"
+ },
+ "related": {
+ "description": "A related resource link when the primary data represents a resource relationship.",
+ "$ref": "#/definitions/link"
+ },
+ "first": true,
+ "last": true,
+ "next": true,
+ "prev": true
+ },
+ "additionalProperties": false
+ }
+ }
+}
diff --git a/_schemas/1.0/schema_create_resource.json b/_schemas/1.0/schema_create_resource.json
new file mode 100644
index 000000000..012651689
--- /dev/null
+++ b/_schemas/1.0/schema_create_resource.json
@@ -0,0 +1,49 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://jsonapi.org/schemas/spec/v1.0/draft/create/resource",
+ "$comment": "The $id URI should be modified before releasing. This URI does not need to be network addressable. It is an ID only.",
+ "title": "JSON:API Schema for POST request",
+ "description": "This schema only validates a request for POSTing a new resource.",
+ "type": "object",
+ "required": [
+ "data"
+ ],
+ "properties": {
+ "data": {
+ "$ref": "#/definitions/resource"
+ },
+ "jsonapi": {
+ "$ref": "https://jsonapi.org/schemas/spec/v1.0/draft#/definitions/jsonapi"
+ },
+ "meta": {
+ "$ref": "https://jsonapi.org/schemas/spec/v1.0/draft#/definitions/meta"
+ }
+ },
+ "additionalProperties": false,
+ "definitions": {
+ "resource": {
+ "description": "\"Resource objects\" appear in a JSON:API document to represent resources.",
+ "$comment": "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.",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "https://jsonapi.org/schemas/spec/v1.0/draft#/definitions/resourceIdentificationNew"
+ }
+ ],
+ "properties": {
+ "id": true,
+ "type": true,
+ "attributes": {
+ "$ref": "https://jsonapi.org/schemas/spec/v1.0/draft#/definitions/attributes"
+ },
+ "meta": {
+ "$ref": "https://jsonapi.org/schemas/spec/v1.0/draft#/definitions/meta"
+ },
+ "relationships": {
+ "$ref": "https://jsonapi.org/schemas/spec/v1.0/draft#/definitions/relationshipsFromRequest"
+ }
+ },
+ "additionalProperties": false
+ }
+ }
+}
diff --git a/_schemas/1.0/schema_update_relationship.json b/_schemas/1.0/schema_update_relationship.json
new file mode 100644
index 000000000..2fbcdba64
--- /dev/null
+++ b/_schemas/1.0/schema_update_relationship.json
@@ -0,0 +1,23 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://jsonapi.org/schemas/spec/v1.0/draft/update/relationship",
+ "$comment": "The $id URI should be modified before releasing. This URI does not need to be network addressable. It is an ID only.",
+ "title": "JSON:API Schema for POST request",
+ "description": "This schema only validates a request for PATCHing a resource's relationship.",
+ "type": "object",
+ "required": [
+ "data"
+ ],
+ "properties": {
+ "data": {
+ "$ref": "https://jsonapi.org/schemas/spec/v1.0/draft#/definitions/linkage"
+ },
+ "jsonapi": {
+ "$ref": "https://jsonapi.org/schemas/spec/v1.0/draft#/definitions/jsonapi"
+ },
+ "meta": {
+ "$ref": "https://jsonapi.org/schemas/spec/v1.0/draft#/definitions/meta"
+ }
+ },
+ "additionalProperties": false
+}
diff --git a/_schemas/1.0/schema_update_resource.json b/_schemas/1.0/schema_update_resource.json
new file mode 100644
index 000000000..d83a37e5a
--- /dev/null
+++ b/_schemas/1.0/schema_update_resource.json
@@ -0,0 +1,49 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://jsonapi.org/schemas/spec/v1.0/draft/update/resource",
+ "$comment": "The $id URI should be modified before releasing. This URI does not need to be network addressable. It is an ID only.",
+ "title": "JSON:API Schema for POST request",
+ "description": "This schema only validates a request for PATCHing a resource.",
+ "type": "object",
+ "required": [
+ "data"
+ ],
+ "properties": {
+ "data": {
+ "$ref": "#/definitions/resource"
+ },
+ "jsonapi": {
+ "$ref": "https://jsonapi.org/schemas/spec/v1.0/draft#/definitions/jsonapi"
+ },
+ "meta": {
+ "$ref": "https://jsonapi.org/schemas/spec/v1.0/draft#/definitions/meta"
+ }
+ },
+ "additionalProperties": false,
+ "definitions": {
+ "resource": {
+ "description": "\"Resource objects\" appear in a JSON:API document to represent resources.",
+ "$comment": "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.",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "https://jsonapi.org/schemas/spec/v1.0/draft#/definitions/resourceIdentification"
+ }
+ ],
+ "properties": {
+ "id": true,
+ "type": true,
+ "attributes": {
+ "$ref": "https://jsonapi.org/schemas/spec/v1.0/draft#/definitions/attributes"
+ },
+ "meta": {
+ "$ref": "https://jsonapi.org/schemas/spec/v1.0/draft#/definitions/meta"
+ },
+ "relationships": {
+ "$ref": "https://jsonapi.org/schemas/spec/v1.0/draft#/definitions/relationshipsFromRequest"
+ }
+ },
+ "additionalProperties": false
+ }
+ }
+}
diff --git a/_schemas/1.0/tests/request/relationship/update/invalid/resource_identifier_must_have_id_member.json b/_schemas/1.0/tests/request/relationship/update/invalid/resource_identifier_must_have_id_member.json
new file mode 100644
index 000000000..b1853ade7
--- /dev/null
+++ b/_schemas/1.0/tests/request/relationship/update/invalid/resource_identifier_must_have_id_member.json
@@ -0,0 +1,15 @@
+{
+ "data": {
+ "type": "tag"
+ },
+ "meta": {
+ "errors-present-in-document": [
+ {
+ "detail": "A resource object **MUST** contain at least the following top-level members: id, type.",
+ "source": {
+ "pointer": "/data"
+ }
+ }
+ ]
+ }
+}
diff --git a/_schemas/1.0/tests/request/relationship/update/valid/patch_relationship.json b/_schemas/1.0/tests/request/relationship/update/valid/patch_relationship.json
new file mode 100644
index 000000000..0947edab2
--- /dev/null
+++ b/_schemas/1.0/tests/request/relationship/update/valid/patch_relationship.json
@@ -0,0 +1,12 @@
+{
+ "data": [
+ {
+ "type": "tag",
+ "id": "2"
+ },
+ {
+ "type": "tag",
+ "id": "13"
+ }
+ ]
+}
diff --git a/_schemas/1.0/tests/request/resource/create/invalid/data_is_not_resource_object.json b/_schemas/1.0/tests/request/resource/create/invalid/data_is_not_resource_object.json
new file mode 100644
index 000000000..3e9814515
--- /dev/null
+++ b/_schemas/1.0/tests/request/resource/create/invalid/data_is_not_resource_object.json
@@ -0,0 +1,22 @@
+{
+ "data": [
+ {
+ "type": "article",
+ "id": "1"
+ },
+ {
+ "type": "article",
+ "id": "2"
+ }
+ ],
+ "meta": {
+ "errors-present-in-document": [
+ {
+ "detail": "Primary data **MUST** be a single resource object.",
+ "source": {
+ "pointer": "/data"
+ }
+ }
+ ]
+ }
+}
diff --git a/_schemas/1.0/tests/request/resource/create/invalid/no_data_member.json b/_schemas/1.0/tests/request/resource/create/invalid/no_data_member.json
new file mode 100644
index 000000000..ba1685f68
--- /dev/null
+++ b/_schemas/1.0/tests/request/resource/create/invalid/no_data_member.json
@@ -0,0 +1,12 @@
+{
+ "meta": {
+ "errors-present-in-document": [
+ {
+ "detail": "The request **MUST** include a top-level member named \"data\".",
+ "source": {
+ "pointer": "/"
+ }
+ }
+ ]
+ }
+}
diff --git a/_schemas/1.0/tests/request/resource/create/invalid/relationship_with_bad_resource_identifier.json b/_schemas/1.0/tests/request/resource/create/invalid/relationship_with_bad_resource_identifier.json
new file mode 100644
index 000000000..a7dd08fec
--- /dev/null
+++ b/_schemas/1.0/tests/request/resource/create/invalid/relationship_with_bad_resource_identifier.json
@@ -0,0 +1,25 @@
+{
+ "data": {
+ "type": "article",
+ "attributes": {
+ "title": "JSON:API, a specification for building APIs in JSON"
+ },
+ "relationships": {
+ "toOne": {
+ "data": {
+ "type": "wrong"
+ }
+ }
+ }
+ },
+ "meta": {
+ "errors-present-in-document": [
+ {
+ "detail": "A resource object **MUST** contain at least the following top-level members: id, type.",
+ "source": {
+ "pointer": "/data/relationships/toOne/data"
+ }
+ }
+ ]
+ }
+}
diff --git a/_schemas/1.0/tests/request/resource/create/invalid/relationship_with_forbidden_name.json b/_schemas/1.0/tests/request/resource/create/invalid/relationship_with_forbidden_name.json
new file mode 100644
index 000000000..4ca3b0bce
--- /dev/null
+++ b/_schemas/1.0/tests/request/resource/create/invalid/relationship_with_forbidden_name.json
@@ -0,0 +1,26 @@
+{
+ "data": {
+ "type": "article",
+ "attributes": {
+ "title": "JSON:API, a specification for building APIs in JSON"
+ },
+ "relationships": {
+ "type": {
+ "data": {
+ "type": "status",
+ "id": "140"
+ }
+ }
+ }
+ },
+ "meta": {
+ "errors-present-in-document": [
+ {
+ "detail": "A relationship **CAN NOT** be named type or id.",
+ "source": {
+ "pointer": "/data/relationships"
+ }
+ }
+ ]
+ }
+}
diff --git a/_schemas/1.0/tests/request/resource/create/invalid/relationship_with_not_allowed_character.json b/_schemas/1.0/tests/request/resource/create/invalid/relationship_with_not_allowed_character.json
new file mode 100644
index 000000000..73f740963
--- /dev/null
+++ b/_schemas/1.0/tests/request/resource/create/invalid/relationship_with_not_allowed_character.json
@@ -0,0 +1,26 @@
+{
+ "data": {
+ "type": "article",
+ "attributes": {
+ "title": "JSON:API, a specification for building APIs in JSON"
+ },
+ "relationships": {
+ "not-allowed+": {
+ "data": {
+ "type": "status",
+ "id": "140"
+ }
+ }
+ }
+ },
+ "meta": {
+ "errors-present-in-document": [
+ {
+ "detail": "Member names **MUST** contain only allowed characters.",
+ "source": {
+ "pointer": "/data/relationships"
+ }
+ }
+ ]
+ }
+}
diff --git a/_schemas/1.0/tests/request/resource/create/invalid/relationship_without_data_member.json b/_schemas/1.0/tests/request/resource/create/invalid/relationship_without_data_member.json
new file mode 100644
index 000000000..68c93c621
--- /dev/null
+++ b/_schemas/1.0/tests/request/resource/create/invalid/relationship_without_data_member.json
@@ -0,0 +1,25 @@
+{
+ "data": {
+ "type": "article",
+ "attributes": {
+ "title": "JSON:API, a specification for building APIs in JSON"
+ },
+ "relationships": {
+ "toOne": {
+ "meta": {
+ "bad": "wrong"
+ }
+ }
+ }
+ },
+ "meta": {
+ "errors-present-in-document": [
+ {
+ "detail": "A \"relationship object\" **MUST** have a data member.",
+ "source": {
+ "pointer": "/data/relationships/toOne"
+ }
+ }
+ ]
+ }
+}
diff --git a/_schemas/1.0/tests/request/resource/create/valid/post_resource.json b/_schemas/1.0/tests/request/resource/create/valid/post_resource.json
new file mode 100644
index 000000000..dfacd1761
--- /dev/null
+++ b/_schemas/1.0/tests/request/resource/create/valid/post_resource.json
@@ -0,0 +1,8 @@
+{
+ "data": {
+ "type": "article",
+ "attributes": {
+ "title": "JSON:API, a specification for building APIs in JSON"
+ }
+ }
+}
diff --git a/_schemas/1.0/tests/request/resource/create/valid/post_resource_with_client_generated_id.json b/_schemas/1.0/tests/request/resource/create/valid/post_resource_with_client_generated_id.json
new file mode 100644
index 000000000..80e731357
--- /dev/null
+++ b/_schemas/1.0/tests/request/resource/create/valid/post_resource_with_client_generated_id.json
@@ -0,0 +1,9 @@
+{
+ "data": {
+ "type": "article",
+ "id": "c0f10761-a507-4a9f-920a-9d967bcec335",
+ "attributes": {
+ "title": "JSON:API, a specification for building APIs in JSON"
+ }
+ }
+}
diff --git a/_schemas/1.0/tests/request/resource/create/valid/post_resource_with_relationships.json b/_schemas/1.0/tests/request/resource/create/valid/post_resource_with_relationships.json
new file mode 100644
index 000000000..9c9a5e816
--- /dev/null
+++ b/_schemas/1.0/tests/request/resource/create/valid/post_resource_with_relationships.json
@@ -0,0 +1,28 @@
+{
+ "data": {
+ "type": "article",
+ "attributes": {
+ "title": "JSON:API, a specification for building APIs in JSON"
+ },
+ "relationships": {
+ "toOne": {
+ "data": {
+ "type": "status",
+ "id": "140"
+ }
+ },
+ "toMany": {
+ "data": [
+ {
+ "type": "tag",
+ "id": "15"
+ },
+ {
+ "type": "tag",
+ "id": "32"
+ }
+ ]
+ }
+ }
+ }
+}
diff --git a/_schemas/1.0/tests/request/resource/create/valid/post_resource_without_attributes.json b/_schemas/1.0/tests/request/resource/create/valid/post_resource_without_attributes.json
new file mode 100644
index 000000000..8d7e4e1be
--- /dev/null
+++ b/_schemas/1.0/tests/request/resource/create/valid/post_resource_without_attributes.json
@@ -0,0 +1,5 @@
+{
+ "data": {
+ "type": "article"
+ }
+}
diff --git a/_schemas/1.0/tests/request/resource/update/invalid/data_must_have_id_member.json b/_schemas/1.0/tests/request/resource/update/invalid/data_must_have_id_member.json
new file mode 100644
index 000000000..37c5513dc
--- /dev/null
+++ b/_schemas/1.0/tests/request/resource/update/invalid/data_must_have_id_member.json
@@ -0,0 +1,18 @@
+{
+ "data": {
+ "type": "article",
+ "attributes": {
+ "title": "JSON:API, a specification for building APIs in JSON"
+ }
+ },
+ "meta": {
+ "errors-present-in-document": [
+ {
+ "detail": "A resource object **MUST** contain at least the following top-level members: id, type.",
+ "source": {
+ "pointer": "/data"
+ }
+ }
+ ]
+ }
+}
diff --git a/_schemas/1.0/tests/request/resource/update/valid/patch_resource.json b/_schemas/1.0/tests/request/resource/update/valid/patch_resource.json
new file mode 100644
index 000000000..bc9f043b2
--- /dev/null
+++ b/_schemas/1.0/tests/request/resource/update/valid/patch_resource.json
@@ -0,0 +1,9 @@
+{
+ "data": {
+ "type": "article",
+ "id": "2",
+ "attributes": {
+ "title": "JSON:API, a specification for building APIs in JSON"
+ }
+ }
+}
diff --git a/_schemas/1.0/tests/request/resource/update/valid/patch_resource_with_relationships.json b/_schemas/1.0/tests/request/resource/update/valid/patch_resource_with_relationships.json
new file mode 100644
index 000000000..5769713ec
--- /dev/null
+++ b/_schemas/1.0/tests/request/resource/update/valid/patch_resource_with_relationships.json
@@ -0,0 +1,29 @@
+{
+ "data": {
+ "type": "article",
+ "id": "2",
+ "attributes": {
+ "title": "JSON:API, a specification for building APIs in JSON"
+ },
+ "relationships": {
+ "toOne": {
+ "data": {
+ "type": "status",
+ "id": "140"
+ }
+ },
+ "toMany": {
+ "data": [
+ {
+ "type": "tag",
+ "id": "15"
+ },
+ {
+ "type": "tag",
+ "id": "32"
+ }
+ ]
+ }
+ }
+ }
+}
diff --git a/_schemas/1.0/tests/request/resource/update/valid/patch_resource_without_attributes.json b/_schemas/1.0/tests/request/resource/update/valid/patch_resource_without_attributes.json
new file mode 100644
index 000000000..2c36d919b
--- /dev/null
+++ b/_schemas/1.0/tests/request/resource/update/valid/patch_resource_without_attributes.json
@@ -0,0 +1,6 @@
+{
+ "data": {
+ "type": "article",
+ "id": "2"
+ }
+}
diff --git a/_schemas/1.0/tests/response/invalid/attributes/attributes_member_not_valid.json b/_schemas/1.0/tests/response/invalid/attributes/attributes_member_not_valid.json
new file mode 100644
index 000000000..95bf69898
--- /dev/null
+++ b/_schemas/1.0/tests/response/invalid/attributes/attributes_member_not_valid.json
@@ -0,0 +1,20 @@
+{
+ "data": {
+ "type": "article",
+ "id": "1",
+ "attributes": {
+ "key+": "bad",
+ "something": "wrong"
+ }
+ },
+ "meta": {
+ "errors-present-in-document": [
+ {
+ "detail": "Member names **MUST** contain only allowed characters.",
+ "source": {
+ "pointer": "/data/attributes"
+ }
+ }
+ ]
+ }
+}
diff --git a/_schemas/1.0/tests/response/invalid/attributes/attributes_must_not_have_id_member.json b/_schemas/1.0/tests/response/invalid/attributes/attributes_must_not_have_id_member.json
new file mode 100644
index 000000000..2d11c8116
--- /dev/null
+++ b/_schemas/1.0/tests/response/invalid/attributes/attributes_must_not_have_id_member.json
@@ -0,0 +1,20 @@
+{
+ "data": {
+ "type": "article",
+ "id": "1",
+ "attributes": {
+ "id": "bad",
+ "something": "wrong"
+ }
+ },
+ "meta": {
+ "errors-present-in-document": [
+ {
+ "detail": "A resource **CAN NOT** have an attribute named type or id.",
+ "source": {
+ "pointer": "/data/attributes"
+ }
+ }
+ ]
+ }
+}
diff --git a/_schemas/1.0/tests/response/invalid/attributes/attributes_must_not_have_type_member.json b/_schemas/1.0/tests/response/invalid/attributes/attributes_must_not_have_type_member.json
new file mode 100644
index 000000000..3807c51a2
--- /dev/null
+++ b/_schemas/1.0/tests/response/invalid/attributes/attributes_must_not_have_type_member.json
@@ -0,0 +1,20 @@
+{
+ "data": {
+ "type": "article",
+ "id": "2",
+ "attributes": {
+ "type": "bad",
+ "something": "wrong"
+ }
+ },
+ "meta": {
+ "errors-present-in-document": [
+ {
+ "detail": "A resource **CAN NOT** have an attribute named type or id.",
+ "source": {
+ "pointer": "/data/attributes"
+ }
+ }
+ ]
+ }
+}
diff --git a/_schemas/1.0/tests/response/invalid/data/data_can_not_be_a_string.json b/_schemas/1.0/tests/response/invalid/data/data_can_not_be_a_string.json
new file mode 100644
index 000000000..fbf9a4dfa
--- /dev/null
+++ b/_schemas/1.0/tests/response/invalid/data/data_can_not_be_a_string.json
@@ -0,0 +1,13 @@
+{
+ "data": "not valid",
+ "meta": {
+ "errors-present-in-document": [
+ {
+ "detail": "Primary data **MUST** be either: 1) a single resource object, a single resource identifier object, or null, for requests that target single resource 2) an array of resource objects, an array of resource identifier objects, or an empty array ([]), for requests that target resource collections",
+ "source": {
+ "pointer": "/data"
+ }
+ }
+ ]
+ }
+}
diff --git a/_schemas/1.0/tests/response/invalid/data/data_can_not_be_array_of_string.json b/_schemas/1.0/tests/response/invalid/data/data_can_not_be_array_of_string.json
new file mode 100644
index 000000000..995eba76a
--- /dev/null
+++ b/_schemas/1.0/tests/response/invalid/data/data_can_not_be_array_of_string.json
@@ -0,0 +1,15 @@
+{
+ "data": [
+ "not valid"
+ ],
+ "meta": {
+ "errors-present-in-document": [
+ {
+ "detail": "A resource **MUST** be an object.",
+ "source": {
+ "pointer": "/data/0"
+ }
+ }
+ ]
+ }
+}
diff --git a/_schemas/1.0/tests/response/invalid/errors/error_must_be_an_object.json b/_schemas/1.0/tests/response/invalid/errors/error_must_be_an_object.json
new file mode 100644
index 000000000..97d00a423
--- /dev/null
+++ b/_schemas/1.0/tests/response/invalid/errors/error_must_be_an_object.json
@@ -0,0 +1,15 @@
+{
+ "errors": [
+ "wrong"
+ ],
+ "meta": {
+ "errors-present-in-document": [
+ {
+ "detail": "An error object **MUST** be an object.",
+ "source": {
+ "pointer": "/errors/0"
+ }
+ }
+ ]
+ }
+}
diff --git a/_schemas/1.0/tests/response/invalid/errors/errors_must_be_an_array.json b/_schemas/1.0/tests/response/invalid/errors/errors_must_be_an_array.json
new file mode 100644
index 000000000..b3de3808d
--- /dev/null
+++ b/_schemas/1.0/tests/response/invalid/errors/errors_must_be_an_array.json
@@ -0,0 +1,16 @@
+{
+ "errors": {
+ "status": "400",
+ "title": "Oh no!"
+ },
+ "meta": {
+ "errors-present-in-document": [
+ {
+ "detail": "The top-level errors member **MUST** be an array of error objects.",
+ "source": {
+ "pointer": "/errors"
+ }
+ }
+ ]
+ }
+}
diff --git a/_schemas/1.0/tests/response/invalid/errors/invalid_error_objects.json b/_schemas/1.0/tests/response/invalid/errors/invalid_error_objects.json
new file mode 100644
index 000000000..bc8142e26
--- /dev/null
+++ b/_schemas/1.0/tests/response/invalid/errors/invalid_error_objects.json
@@ -0,0 +1,137 @@
+{
+ "errors": [
+ "wrong : An error object **MUST** be an object.",
+ {
+ "id": 0,
+ "status": "400",
+ "code": "4",
+ "title": "Oh no!",
+ "detail": "The id member **MUST** be a string.",
+ "source": {
+ "pointer": "/id"
+ }
+ },
+ {
+ "id": "0",
+ "status": 400,
+ "code": "4",
+ "title": "Oh no!",
+ "detail": "The status member **MUST** be a string.",
+ "source": {
+ "pointer": "/status"
+ }
+ },
+ {
+ "id": "0",
+ "status": "400",
+ "code": 4,
+ "title": "Oh no!",
+ "detail": "The code member **MUST** be a string.",
+ "source": {
+ "pointer": "/code"
+ }
+ },
+ {
+ "id": "0",
+ "status": "400",
+ "code": "4",
+ "title": {
+ "wrong": "Oh no!"
+ },
+ "detail": "The title member **MUST** be a string.",
+ "source": {
+ "pointer": "/title"
+ }
+ },
+ {
+ "id": "0",
+ "status": "400",
+ "code": "4",
+ "title": "Oh no!",
+ "detail": {
+ "wrong": "The detail member **MUST** be a string."
+ },
+ "source": {
+ "pointer": "/details"
+ }
+ },
+ {
+ "id": "0",
+ "status": "400",
+ "code": "4",
+ "title": "Oh no!",
+ "detail": "The pointer member **MUST** be a string.",
+ "source": {
+ "pointer": {
+ "wrong": "/source/pointer"
+ }
+ }
+ },
+ {
+ "id": "0",
+ "status": "400",
+ "code": "4",
+ "title": "Oh no!",
+ "detail": "The pointer member **MUST** be a valid JSON Pointer [RFC6901].",
+ "source": {
+ "pointer": "bad pattern for /source/pointer"
+ }
+ },
+ {
+ "id": "0",
+ "status": "400",
+ "code": "4",
+ "title": "Oh no!",
+ "detail": "The parameter member **MUST** be a string.",
+ "source": {
+ "pointer": "/source/parameter",
+ "parameter": {
+ "wrong": "not a string"
+ }
+ }
+ },
+ {
+ "id": "0",
+ "status": "400",
+ "code": "4",
+ "title": "Oh no!",
+ "detail": "Unless otherwise noted, objects defined by this specification **MUST NOT** contain any additional members (here, \"wrong\").",
+ "source": {
+ "pointer": "/wrong"
+ },
+ "wrong": "not allowed"
+ },
+ {
+ "id": "0",
+ "status": "400",
+ "code": "4",
+ "title": "Oh no!",
+ "detail": "Unless otherwise noted, objects defined by this specification **MUST NOT** contain any additional members (here, \"wrong\").",
+ "source": {
+ "pointer": "/links/wrong"
+ },
+ "links": {
+ "wrong": "http://www.example.com/not/allowed"
+ }
+ },
+ {
+ "id": "0",
+ "status": "400",
+ "code": "4",
+ "title": "Oh no!",
+ "detail": "The source member **MUST** be an object.",
+ "source": "not an object"
+ },
+ {
+ "id": "0",
+ "status": "400",
+ "code": "4",
+ "title": "Oh no!",
+ "detail": "The value of each meta member **MUST** be an object (a \"meta object\").",
+ "source": {
+ "pointer": "/meta"
+ },
+ "meta": "not an object"
+ }
+ ]
+}
diff --git a/_schemas/1.0/tests/response/invalid/included/included_member_must_be_collection.json b/_schemas/1.0/tests/response/invalid/included/included_member_must_be_collection.json
new file mode 100644
index 000000000..727ec0fbd
--- /dev/null
+++ b/_schemas/1.0/tests/response/invalid/included/included_member_must_be_collection.json
@@ -0,0 +1,39 @@
+{
+ "links": {
+ "self": "http://example.com/articles/1"
+ },
+ "data": {
+ "type": "articles",
+ "id": "1",
+ "attributes": {
+ "title": "JSON:API, a specification for building APIs in JSON"
+ },
+ "relationships": {
+ "author": {
+ "data": {
+ "type": "people",
+ "id": "9"
+ }
+ }
+ }
+ },
+ "included": {
+ "type": "people",
+ "id": "9",
+ "attributes": {
+ "firstName": "Dan",
+ "lastName": "Gebhardt",
+ "twitter": "dgeb"
+ }
+ },
+ "meta": {
+ "errors-present-in-document": [
+ {
+ "detail": "The top-level included member **MUST** be an array of resource objects.",
+ "source": {
+ "pointer": "/included"
+ }
+ }
+ ]
+ }
+}
diff --git a/_schemas/1.0/tests/response/invalid/included/included_resource_not_valid.json b/_schemas/1.0/tests/response/invalid/included/included_resource_not_valid.json
new file mode 100644
index 000000000..6add9e58b
--- /dev/null
+++ b/_schemas/1.0/tests/response/invalid/included/included_resource_not_valid.json
@@ -0,0 +1,41 @@
+{
+ "links": {
+ "self": "http://example.com/articles/1"
+ },
+ "data": {
+ "type": "articles",
+ "id": "1",
+ "attributes": {
+ "title": "JSON:API, a specification for building APIs in JSON"
+ },
+ "relationships": {
+ "author": {
+ "data": {
+ "type": "people",
+ "id": "9"
+ }
+ }
+ }
+ },
+ "included": [
+ {
+ "type": "people",
+ "id": 9,
+ "attributes": {
+ "firstName": "Dan",
+ "lastName": "Gebhardt",
+ "twitter": "dgeb"
+ }
+ }
+ ],
+ "meta": {
+ "errors-present-in-document": [
+ {
+ "detail": "A resource's id member **MUST** be a string.",
+ "source": {
+ "pointer": "/included/0/id"
+ }
+ }
+ ]
+ }
+}
diff --git a/_schemas/1.0/tests/response/invalid/included/resource_included_twice.json b/_schemas/1.0/tests/response/invalid/included/resource_included_twice.json
new file mode 100644
index 000000000..855b9f9ab
--- /dev/null
+++ b/_schemas/1.0/tests/response/invalid/included/resource_included_twice.json
@@ -0,0 +1,50 @@
+{
+ "links": {
+ "self": "http://example.com/articles/1"
+ },
+ "data": {
+ "type": "articles",
+ "id": "1",
+ "attributes": {
+ "title": "JSON:API, a specification for building APIs in JSON"
+ },
+ "relationships": {
+ "author": {
+ "data": {
+ "type": "people",
+ "id": "9"
+ }
+ }
+ }
+ },
+ "included": [
+ {
+ "type": "people",
+ "id": "9",
+ "attributes": {
+ "firstName": "Dan",
+ "lastName": "Gebhardt",
+ "twitter": "dgeb"
+ }
+ },
+ {
+ "type": "people",
+ "id": "9",
+ "attributes": {
+ "firstName": "Dan",
+ "lastName": "Gebhardt",
+ "twitter": "dgeb"
+ }
+ }
+ ],
+ "meta": {
+ "errors-present-in-document": [
+ {
+ "detail": "A compound document **MUST NOT** include more than one resource object for each \"type\" and \"id\" pair.",
+ "source": {
+ "pointer": "/included"
+ }
+ }
+ ]
+ }
+}
diff --git a/_schemas/1.0/tests/response/invalid/invalid_multi.json b/_schemas/1.0/tests/response/invalid/invalid_multi.json
new file mode 100644
index 000000000..1320f6299
--- /dev/null
+++ b/_schemas/1.0/tests/response/invalid/invalid_multi.json
@@ -0,0 +1,26 @@
+{
+ "data": {
+ "type": "article",
+ "id": 1
+ },
+ "jsonapi": {
+ "version": "1.0",
+ "oups": "error"
+ },
+ "meta": {
+ "errors-present-in-document": [
+ {
+ "detail": "A resource's id member **MUST** be a string.",
+ "source": {
+ "pointer": "/data/id"
+ }
+ },
+ {
+ "detail": "Unless otherwise noted, objects defined by this specification **MUST NOT** contain any additional members (here, \"oups\").",
+ "source": {
+ "pointer": "/jsonapi"
+ }
+ }
+ ]
+ }
+}
diff --git a/_schemas/1.0/tests/response/invalid/jsonapi/jsonapi_with_not_allowed_members.json b/_schemas/1.0/tests/response/invalid/jsonapi/jsonapi_with_not_allowed_members.json
new file mode 100644
index 000000000..7973e0ff5
--- /dev/null
+++ b/_schemas/1.0/tests/response/invalid/jsonapi/jsonapi_with_not_allowed_members.json
@@ -0,0 +1,16 @@
+{
+ "jsonapi": {
+ "version": "1.0",
+ "oups": "error"
+ },
+ "meta": {
+ "errors-present-in-document": [
+ {
+ "detail": "Unless otherwise noted, objects defined by this specification **MUST NOT** contain any additional members (here, \"oups\").",
+ "source": {
+ "pointer": "/jsonapi"
+ }
+ }
+ ]
+ }
+}
diff --git a/_schemas/1.0/tests/response/invalid/jsonapi/meta_is_not_valid.json b/_schemas/1.0/tests/response/invalid/jsonapi/meta_is_not_valid.json
new file mode 100644
index 000000000..d32d1ac31
--- /dev/null
+++ b/_schemas/1.0/tests/response/invalid/jsonapi/meta_is_not_valid.json
@@ -0,0 +1,18 @@
+{
+ "jsonapi": {
+ "version": "1.0",
+ "meta": {
+ "key+": "wrong"
+ }
+ },
+ "meta": {
+ "errors-present-in-document": [
+ {
+ "detail": "Member names **MUST** contain only allowed characters.",
+ "source": {
+ "pointer": "/jsonapi/meta"
+ }
+ }
+ ]
+ }
+}
diff --git a/_schemas/1.0/tests/response/invalid/jsonapi/not_an_object.json b/_schemas/1.0/tests/response/invalid/jsonapi/not_an_object.json
new file mode 100644
index 000000000..b4a8325d0
--- /dev/null
+++ b/_schemas/1.0/tests/response/invalid/jsonapi/not_an_object.json
@@ -0,0 +1,22 @@
+{
+ "jsonapi": [
+ {
+ "version": "1.0"
+ },
+ {
+ "meta": {
+ "key+": "wrong"
+ }
+ }
+ ],
+ "meta": {
+ "errors-present-in-document": [
+ {
+ "detail": "If present, the value of the jsonapi member **MUST** be an object (a \"jsonapi object\").",
+ "source": {
+ "pointer": "/jsonapi"
+ }
+ }
+ ]
+ }
+}
diff --git a/_schemas/1.0/tests/response/invalid/jsonapi/version_is_not_a_string.json b/_schemas/1.0/tests/response/invalid/jsonapi/version_is_not_a_string.json
new file mode 100644
index 000000000..d1384bee1
--- /dev/null
+++ b/_schemas/1.0/tests/response/invalid/jsonapi/version_is_not_a_string.json
@@ -0,0 +1,15 @@
+{
+ "jsonapi": {
+ "version": 1
+ },
+ "meta": {
+ "errors-present-in-document": [
+ {
+ "detail": "If present, the value of the version member **MUST** be a string.",
+ "source": {
+ "pointer": "/jsonapi/version"
+ }
+ }
+ ]
+ }
+}
diff --git a/_schemas/1.0/tests/response/invalid/links/link_href_must_be_a_string.json b/_schemas/1.0/tests/response/invalid/links/link_href_must_be_a_string.json
new file mode 100644
index 000000000..3d100fd99
--- /dev/null
+++ b/_schemas/1.0/tests/response/invalid/links/link_href_must_be_a_string.json
@@ -0,0 +1,17 @@
+{
+ "links": {
+ "self": {
+ "href": 400
+ }
+ },
+ "meta": {
+ "errors-present-in-document": [
+ {
+ "detail": "The href member **MUST** be a string containing the link’s URL.",
+ "source": {
+ "pointer": "/links/self/href"
+ }
+ }
+ ]
+ }
+}
diff --git a/_schemas/1.0/tests/response/invalid/links/link_must_be_string_or_object.json b/_schemas/1.0/tests/response/invalid/links/link_must_be_string_or_object.json
new file mode 100644
index 000000000..74de60312
--- /dev/null
+++ b/_schemas/1.0/tests/response/invalid/links/link_must_be_string_or_object.json
@@ -0,0 +1,15 @@
+{
+ "links": {
+ "self": 400
+ },
+ "meta": {
+ "errors-present-in-document": [
+ {
+ "detail": "A link **MUST** be represented as either: a string containing the link's URL or a link object.",
+ "source": {
+ "pointer": "/links/self"
+ }
+ }
+ ]
+ }
+}
diff --git a/_schemas/1.0/tests/response/invalid/links/link_must_be_valid_uri.json b/_schemas/1.0/tests/response/invalid/links/link_must_be_valid_uri.json
new file mode 100644
index 000000000..c7ded4838
--- /dev/null
+++ b/_schemas/1.0/tests/response/invalid/links/link_must_be_valid_uri.json
@@ -0,0 +1,15 @@
+{
+ "links": {
+ "self": "wrong"
+ },
+ "meta": {
+ "errors-present-in-document": [
+ {
+ "detail": "If represented as a string, a link **MUST** be a string containing a valid URI.",
+ "source": {
+ "pointer": "/links/self"
+ }
+ }
+ ]
+ }
+}
diff --git a/_schemas/1.0/tests/response/invalid/links/links_must_be_an_object.json b/_schemas/1.0/tests/response/invalid/links/links_must_be_an_object.json
new file mode 100644
index 000000000..4e053c3fc
--- /dev/null
+++ b/_schemas/1.0/tests/response/invalid/links/links_must_be_an_object.json
@@ -0,0 +1,13 @@
+{
+ "links": "not valid",
+ "meta": {
+ "errors-present-in-document": [
+ {
+ "detail": "The value of each links member **MUST** be an object (a \"links object\").",
+ "source": {
+ "pointer": "/links"
+ }
+ }
+ ]
+ }
+}
diff --git a/_schemas/1.0/tests/response/invalid/meta/meta_must_be_an_object.json b/_schemas/1.0/tests/response/invalid/meta/meta_must_be_an_object.json
new file mode 100644
index 000000000..d73e2969c
--- /dev/null
+++ b/_schemas/1.0/tests/response/invalid/meta/meta_must_be_an_object.json
@@ -0,0 +1,14 @@
+{
+ "meta": [
+ {
+ "errors-present-in-document": [
+ {
+ "detail": "The value of each meta member **MUST** be an object (a \"meta object\").",
+ "source": {
+ "pointer": "/meta"
+ }
+ }
+ ]
+ }
+ ]
+}
diff --git a/_schemas/1.0/tests/response/invalid/meta/meta_must_have_valid_members.json b/_schemas/1.0/tests/response/invalid/meta/meta_must_have_valid_members.json
new file mode 100644
index 000000000..b6793f63f
--- /dev/null
+++ b/_schemas/1.0/tests/response/invalid/meta/meta_must_have_valid_members.json
@@ -0,0 +1,13 @@
+{
+ "meta": {
+ "key+": "not valid",
+ "errors-present-in-document": [
+ {
+ "detail": "Member names **MUST** contain only allowed characters.",
+ "source": {
+ "pointer": "/meta"
+ }
+ }
+ ]
+ }
+}
diff --git a/_schemas/1.0/tests/response/invalid/relationships/link_name_not_allowed.json b/_schemas/1.0/tests/response/invalid/relationships/link_name_not_allowed.json
new file mode 100644
index 000000000..4c7807d41
--- /dev/null
+++ b/_schemas/1.0/tests/response/invalid/relationships/link_name_not_allowed.json
@@ -0,0 +1,32 @@
+{
+ "data": {
+ "type": "article",
+ "id": "1",
+ "attributes": {
+ "title": "JSON:API, a specification for building APIs in JSON"
+ },
+ "relationships": {
+ "author": {
+ "links": {
+ "self": "http://example.com/articles/1/relationships/author",
+ "related": "http://example.com/articles/1/author",
+ "wrong": "http://www.example.com/not/allowed"
+ },
+ "data": {
+ "type": "people",
+ "id": "9"
+ }
+ }
+ }
+ },
+ "meta": {
+ "errors-present-in-document": [
+ {
+ "detail": "Unless otherwise noted, objects defined by this specification **MUST NOT** contain any additional members (here, \"wrong\").",
+ "source": {
+ "pointer": "/data/relationships/author/links"
+ }
+ }
+ ]
+ }
+}
diff --git a/_schemas/1.0/tests/response/invalid/relationships/linkage_must_be_object.json b/_schemas/1.0/tests/response/invalid/relationships/linkage_must_be_object.json
new file mode 100644
index 000000000..2b5a6a639
--- /dev/null
+++ b/_schemas/1.0/tests/response/invalid/relationships/linkage_must_be_object.json
@@ -0,0 +1,24 @@
+{
+ "data": {
+ "type": "article",
+ "id": "1",
+ "attributes": {
+ "title": "JSON:API, a specification for building APIs in JSON"
+ },
+ "relationships": {
+ "author": {
+ "data": "too bad"
+ }
+ }
+ },
+ "meta": {
+ "errors-present-in-document": [
+ {
+ "detail": "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 for non-empty to-many relationships.",
+ "source": {
+ "pointer": "/data/relationships/author/data"
+ }
+ }
+ ]
+ }
+}
diff --git a/_schemas/1.0/tests/response/invalid/relationships/links_not_valid.json b/_schemas/1.0/tests/response/invalid/relationships/links_not_valid.json
new file mode 100644
index 000000000..fb19780d6
--- /dev/null
+++ b/_schemas/1.0/tests/response/invalid/relationships/links_not_valid.json
@@ -0,0 +1,28 @@
+{
+ "data": {
+ "type": "article",
+ "id": "1",
+ "attributes": {
+ "title": "JSON:API, a specification for building APIs in JSON"
+ },
+ "relationships": {
+ "author": {
+ "links": "not valid",
+ "data": {
+ "type": "people",
+ "id": "9"
+ }
+ }
+ }
+ },
+ "meta": {
+ "errors-present-in-document": [
+ {
+ "detail": "The value of each links member **MUST** be an object (a \"links object\").",
+ "source": {
+ "pointer": "/data/relationships/author/links"
+ }
+ }
+ ]
+ }
+}
diff --git a/_schemas/1.0/tests/response/invalid/relationships/meta_not_valid.json b/_schemas/1.0/tests/response/invalid/relationships/meta_not_valid.json
new file mode 100644
index 000000000..692eb741a
--- /dev/null
+++ b/_schemas/1.0/tests/response/invalid/relationships/meta_not_valid.json
@@ -0,0 +1,30 @@
+{
+ "data": {
+ "type": "article",
+ "id": "1",
+ "attributes": {
+ "title": "JSON:API, a specification for building APIs in JSON"
+ },
+ "relationships": {
+ "author": {
+ "data": {
+ "type": "people",
+ "id": "9"
+ },
+ "meta": {
+ "no+": "not valid"
+ }
+ }
+ }
+ },
+ "meta": {
+ "errors-present-in-document": [
+ {
+ "detail": "Member names **MUST** contain only allowed characters.",
+ "source": {
+ "pointer": "/data/relationships/author/meta"
+ }
+ }
+ ]
+ }
+}
diff --git a/_schemas/1.0/tests/response/invalid/relationships/relationship_must_not_be_empty.json b/_schemas/1.0/tests/response/invalid/relationships/relationship_must_not_be_empty.json
new file mode 100644
index 000000000..c57005956
--- /dev/null
+++ b/_schemas/1.0/tests/response/invalid/relationships/relationship_must_not_be_empty.json
@@ -0,0 +1,22 @@
+{
+ "data": {
+ "type": "article",
+ "id": "1",
+ "attributes": {
+ "title": "JSON:API, a specification for building APIs in JSON"
+ },
+ "relationships": {
+ "author": {}
+ }
+ },
+ "meta": {
+ "errors-present-in-document": [
+ {
+ "detail": "A \"relationship object\" **MUST** contain at least one of the following: links, data, meta.",
+ "source": {
+ "pointer": "/data/relationships/author"
+ }
+ }
+ ]
+ }
+}
diff --git a/_schemas/1.0/tests/response/invalid/relationships/relationship_must_not_be_named_id.json b/_schemas/1.0/tests/response/invalid/relationships/relationship_must_not_be_named_id.json
new file mode 100644
index 000000000..c9eaca7e1
--- /dev/null
+++ b/_schemas/1.0/tests/response/invalid/relationships/relationship_must_not_be_named_id.json
@@ -0,0 +1,27 @@
+{
+ "data": {
+ "type": "article",
+ "id": "1",
+ "attributes": {
+ "title": "JSON:API, a specification for building APIs in JSON"
+ },
+ "relationships": {
+ "id": {
+ "data": {
+ "type": "people",
+ "id": "9"
+ }
+ }
+ }
+ },
+ "meta": {
+ "errors-present-in-document": [
+ {
+ "detail": "A relationship **CAN NOT** be named type or id.",
+ "source": {
+ "pointer": "/data/relationships"
+ }
+ }
+ ]
+ }
+}
diff --git a/_schemas/1.0/tests/response/invalid/relationships/relationship_must_not_be_named_type.json b/_schemas/1.0/tests/response/invalid/relationships/relationship_must_not_be_named_type.json
new file mode 100644
index 000000000..3a6bf8bf1
--- /dev/null
+++ b/_schemas/1.0/tests/response/invalid/relationships/relationship_must_not_be_named_type.json
@@ -0,0 +1,27 @@
+{
+ "data": {
+ "type": "article",
+ "id": "1",
+ "attributes": {
+ "title": "JSON:API, a specification for building APIs in JSON"
+ },
+ "relationships": {
+ "type": {
+ "data": {
+ "type": "people",
+ "id": "9"
+ }
+ }
+ }
+ },
+ "meta": {
+ "errors-present-in-document": [
+ {
+ "detail": "A relationship **CAN NOT** be named type or id.",
+ "source": {
+ "pointer": "/data/relationships"
+ }
+ }
+ ]
+ }
+}
diff --git a/_schemas/1.0/tests/response/invalid/relationships/relationship_must_not_have_additional_properties.json b/_schemas/1.0/tests/response/invalid/relationships/relationship_must_not_have_additional_properties.json
new file mode 100644
index 000000000..98d6a6046
--- /dev/null
+++ b/_schemas/1.0/tests/response/invalid/relationships/relationship_must_not_have_additional_properties.json
@@ -0,0 +1,28 @@
+{
+ "data": {
+ "type": "article",
+ "id": "1",
+ "attributes": {
+ "title": "JSON:API, a specification for building APIs in JSON"
+ },
+ "relationships": {
+ "author": {
+ "data": {
+ "type": "people",
+ "id": "9"
+ },
+ "wrong": "not allowed"
+ }
+ }
+ },
+ "meta": {
+ "errors-present-in-document": [
+ {
+ "detail": "Unless otherwise noted, objects defined by this specification **MUST NOT** contain any additional members (here, \"wrong\").",
+ "source": {
+ "pointer": "/data/relationships/author"
+ }
+ }
+ ]
+ }
+}
diff --git a/_schemas/1.0/tests/response/invalid/relationships/relationship_name_is_not_valid.json b/_schemas/1.0/tests/response/invalid/relationships/relationship_name_is_not_valid.json
new file mode 100644
index 000000000..b01702701
--- /dev/null
+++ b/_schemas/1.0/tests/response/invalid/relationships/relationship_name_is_not_valid.json
@@ -0,0 +1,27 @@
+{
+ "data": {
+ "type": "article",
+ "id": "1",
+ "attributes": {
+ "title": "JSON:API, a specification for building APIs in JSON"
+ },
+ "relationships": {
+ "notValid+": {
+ "data": {
+ "type": "people",
+ "id": "9"
+ }
+ }
+ }
+ },
+ "meta": {
+ "errors-present-in-document": [
+ {
+ "detail": "Member names **MUST** contain only allowed characters.",
+ "source": {
+ "pointer": "/data/relationships"
+ }
+ }
+ ]
+ }
+}
diff --git a/_schemas/1.0/tests/response/invalid/relationships/relationships_is_not_an_object.json b/_schemas/1.0/tests/response/invalid/relationships/relationships_is_not_an_object.json
new file mode 100644
index 000000000..6f93278bb
--- /dev/null
+++ b/_schemas/1.0/tests/response/invalid/relationships/relationships_is_not_an_object.json
@@ -0,0 +1,20 @@
+{
+ "data": {
+ "type": "article",
+ "id": "1",
+ "attributes": {
+ "title": "JSON:API, a specification for building APIs in JSON"
+ },
+ "relationships": "very bad"
+ },
+ "meta": {
+ "errors-present-in-document": [
+ {
+ "detail": "The value of the relationships key **MUST** be an object (a \"relationships object\").",
+ "source": {
+ "pointer": "/data/relationships"
+ }
+ }
+ ]
+ }
+}
diff --git a/_schemas/1.0/tests/response/invalid/relationships/to_many_linkage_not_valid.json b/_schemas/1.0/tests/response/invalid/relationships/to_many_linkage_not_valid.json
new file mode 100644
index 000000000..5a8e4ff1f
--- /dev/null
+++ b/_schemas/1.0/tests/response/invalid/relationships/to_many_linkage_not_valid.json
@@ -0,0 +1,30 @@
+{
+ "data": {
+ "type": "article",
+ "id": "1",
+ "attributes": {
+ "title": "JSON:API, a specification for building APIs in JSON"
+ },
+ "relationships": {
+ "author": {
+ "data": [
+ {
+ "type": "people",
+ "id": "9",
+ "bad": "not valid"
+ }
+ ]
+ }
+ }
+ },
+ "meta": {
+ "errors-present-in-document": [
+ {
+ "detail": "Unless otherwise noted, objects defined by this specification **MUST NOT** contain any additional members (here, \"bad\").",
+ "source": {
+ "pointer": "/data/relationships/author/data/0"
+ }
+ }
+ ]
+ }
+}
diff --git a/_schemas/1.0/tests/response/invalid/relationships/to_one_linkage_not_valid.json b/_schemas/1.0/tests/response/invalid/relationships/to_one_linkage_not_valid.json
new file mode 100644
index 000000000..40974bb5e
--- /dev/null
+++ b/_schemas/1.0/tests/response/invalid/relationships/to_one_linkage_not_valid.json
@@ -0,0 +1,28 @@
+{
+ "data": {
+ "type": "article",
+ "id": "1",
+ "attributes": {
+ "title": "JSON:API, a specification for building APIs in JSON"
+ },
+ "relationships": {
+ "author": {
+ "data": {
+ "type": "people",
+ "id": "9",
+ "bad": "not valid"
+ }
+ }
+ }
+ },
+ "meta": {
+ "errors-present-in-document": [
+ {
+ "detail": "Unless otherwise noted, objects defined by this specification **MUST NOT** contain any additional members (here, \"bad\").",
+ "source": {
+ "pointer": "/data/relationships/author/data"
+ }
+ }
+ ]
+ }
+}
diff --git a/_schemas/1.0/tests/response/invalid/resource/id_must_be_string.json b/_schemas/1.0/tests/response/invalid/resource/id_must_be_string.json
new file mode 100644
index 000000000..6ac2a3cb6
--- /dev/null
+++ b/_schemas/1.0/tests/response/invalid/resource/id_must_be_string.json
@@ -0,0 +1,19 @@
+{
+ "data": {
+ "type": "article",
+ "id": 2,
+ "attributes": {
+ "title": "JSON:API, a specification for building APIs in JSON"
+ }
+ },
+ "meta": {
+ "errors-present-in-document": [
+ {
+ "detail": "A resource's id member **MUST** be a string.",
+ "source": {
+ "pointer": "/data/id"
+ }
+ }
+ ]
+ }
+}
diff --git a/_schemas/1.0/tests/response/invalid/resource/relationship_named_id.json b/_schemas/1.0/tests/response/invalid/resource/relationship_named_id.json
new file mode 100644
index 000000000..785ace67b
--- /dev/null
+++ b/_schemas/1.0/tests/response/invalid/resource/relationship_named_id.json
@@ -0,0 +1,27 @@
+{
+ "data": {
+ "type": "article",
+ "id": "1",
+ "attributes": {
+ "title": "JSON:API, a specification for building APIs in JSON"
+ },
+ "relationships": {
+ "id": {
+ "data": {
+ "type": "wrong",
+ "id": "9"
+ }
+ }
+ }
+ },
+ "meta": {
+ "errors-present-in-document": [
+ {
+ "detail": "A relationship **CAN NOT** be named type or id.",
+ "source": {
+ "pointer": "/data/relationships"
+ }
+ }
+ ]
+ }
+}
diff --git a/_schemas/1.0/tests/response/invalid/resource/relationship_named_type.json b/_schemas/1.0/tests/response/invalid/resource/relationship_named_type.json
new file mode 100644
index 000000000..eb0594ed2
--- /dev/null
+++ b/_schemas/1.0/tests/response/invalid/resource/relationship_named_type.json
@@ -0,0 +1,27 @@
+{
+ "data": {
+ "type": "article",
+ "id": "1",
+ "attributes": {
+ "title": "JSON:API, a specification for building APIs in JSON"
+ },
+ "relationships": {
+ "type": {
+ "data": {
+ "type": "wrong",
+ "id": "9"
+ }
+ }
+ }
+ },
+ "meta": {
+ "errors-present-in-document": [
+ {
+ "detail": "A relationship **CAN NOT** be named type or id.",
+ "source": {
+ "pointer": "/data/relationships"
+ }
+ }
+ ]
+ }
+}
diff --git a/_schemas/1.0/tests/response/invalid/resource/resource_must_have_id_member.json b/_schemas/1.0/tests/response/invalid/resource/resource_must_have_id_member.json
new file mode 100644
index 000000000..37c5513dc
--- /dev/null
+++ b/_schemas/1.0/tests/response/invalid/resource/resource_must_have_id_member.json
@@ -0,0 +1,18 @@
+{
+ "data": {
+ "type": "article",
+ "attributes": {
+ "title": "JSON:API, a specification for building APIs in JSON"
+ }
+ },
+ "meta": {
+ "errors-present-in-document": [
+ {
+ "detail": "A resource object **MUST** contain at least the following top-level members: id, type.",
+ "source": {
+ "pointer": "/data"
+ }
+ }
+ ]
+ }
+}
diff --git a/_schemas/1.0/tests/response/invalid/resource/resource_must_have_type_member.json b/_schemas/1.0/tests/response/invalid/resource/resource_must_have_type_member.json
new file mode 100644
index 000000000..e932801c2
--- /dev/null
+++ b/_schemas/1.0/tests/response/invalid/resource/resource_must_have_type_member.json
@@ -0,0 +1,18 @@
+{
+ "data": {
+ "id": "3",
+ "attributes": {
+ "title": "JSON:API, a specification for building APIs in JSON"
+ }
+ },
+ "meta": {
+ "errors-present-in-document": [
+ {
+ "detail": "A resource object **MUST** contain at least the following top-level members: id, type.",
+ "source": {
+ "pointer": "/data"
+ }
+ }
+ ]
+ }
+}
diff --git a/_schemas/1.0/tests/response/invalid/resource/type_must_be_string.json b/_schemas/1.0/tests/response/invalid/resource/type_must_be_string.json
new file mode 100644
index 000000000..2cd27e12f
--- /dev/null
+++ b/_schemas/1.0/tests/response/invalid/resource/type_must_be_string.json
@@ -0,0 +1,19 @@
+{
+ "data": {
+ "type": 400,
+ "id": "1",
+ "attributes": {
+ "title": "JSON:API, a specification for building APIs in JSON"
+ }
+ },
+ "meta": {
+ "errors-present-in-document": [
+ {
+ "detail": "A resource's type member **MUST** be a string.",
+ "source": {
+ "pointer": "/data/type"
+ }
+ }
+ ]
+ }
+}
diff --git a/_schemas/1.0/tests/response/invalid/resource/type_must_not_be_empty.json b/_schemas/1.0/tests/response/invalid/resource/type_must_not_be_empty.json
new file mode 100644
index 000000000..73c2f3759
--- /dev/null
+++ b/_schemas/1.0/tests/response/invalid/resource/type_must_not_be_empty.json
@@ -0,0 +1,19 @@
+{
+ "data": {
+ "type": "",
+ "id": "1",
+ "attributes": {
+ "title": "JSON:API, a specification for building APIs in JSON"
+ }
+ },
+ "meta": {
+ "errors-present-in-document": [
+ {
+ "detail": "The values of type members **MUST** contain only allowed characters.",
+ "source": {
+ "pointer": "/data/type"
+ }
+ }
+ ]
+ }
+}
diff --git a/_schemas/1.0/tests/response/invalid/resource/type_value_is_not_valid.json b/_schemas/1.0/tests/response/invalid/resource/type_value_is_not_valid.json
new file mode 100644
index 000000000..57ed09e41
--- /dev/null
+++ b/_schemas/1.0/tests/response/invalid/resource/type_value_is_not_valid.json
@@ -0,0 +1,19 @@
+{
+ "data": {
+ "type": "test+1",
+ "id": "1",
+ "attributes": {
+ "title": "JSON:API, a specification for building APIs in JSON"
+ }
+ },
+ "meta": {
+ "errors-present-in-document": [
+ {
+ "detail": "The values of type members **MUST** contain only allowed characters.",
+ "source": {
+ "pointer": "/data/type"
+ }
+ }
+ ]
+ }
+}
diff --git a/_schemas/1.0/tests/response/invalid/resource/with_additional_properties.json b/_schemas/1.0/tests/response/invalid/resource/with_additional_properties.json
new file mode 100644
index 000000000..989491efd
--- /dev/null
+++ b/_schemas/1.0/tests/response/invalid/resource/with_additional_properties.json
@@ -0,0 +1,20 @@
+{
+ "data": {
+ "type": "article",
+ "id": "1",
+ "attributes": {
+ "title": "JSON:API, a specification for building APIs in JSON"
+ },
+ "bad": "property"
+ },
+ "meta": {
+ "errors-present-in-document": [
+ {
+ "detail": "Unless otherwise noted, objects defined by this specification **MUST NOT** contain any additional members (here, \"bad\").",
+ "source": {
+ "pointer": "/data"
+ }
+ }
+ ]
+ }
+}
diff --git a/_schemas/1.0/tests/response/invalid/resource_collection/resource_included_twice.json b/_schemas/1.0/tests/response/invalid/resource_collection/resource_included_twice.json
new file mode 100644
index 000000000..00e42fb4c
--- /dev/null
+++ b/_schemas/1.0/tests/response/invalid/resource_collection/resource_included_twice.json
@@ -0,0 +1,32 @@
+{
+ "data": [
+ {
+ "type": "people",
+ "id": "9",
+ "attributes": {
+ "firstName": "Dan",
+ "lastName": "Gebhardt",
+ "twitter": "dgeb"
+ }
+ },
+ {
+ "type": "people",
+ "id": "9",
+ "attributes": {
+ "firstName": "Dan",
+ "lastName": "Gebhardt",
+ "twitter": "dgeb"
+ }
+ }
+ ],
+ "meta": {
+ "errors-present-in-document": [
+ {
+ "detail": "A document **MUST NOT** include more than one resource object for each \"type\" and \"id\" pair.",
+ "source": {
+ "pointer": "/data"
+ }
+ }
+ ]
+ }
+}
diff --git a/_schemas/1.0/tests/response/invalid/resource_identifier/id_must_be_string.json b/_schemas/1.0/tests/response/invalid/resource_identifier/id_must_be_string.json
new file mode 100644
index 000000000..a6fc5f18f
--- /dev/null
+++ b/_schemas/1.0/tests/response/invalid/resource_identifier/id_must_be_string.json
@@ -0,0 +1,16 @@
+{
+ "data": {
+ "type": "article",
+ "id": 2
+ },
+ "meta": {
+ "errors-present-in-document": [
+ {
+ "detail": "A resource's id member **MUST** be a string.",
+ "source": {
+ "pointer": "/data/id"
+ }
+ }
+ ]
+ }
+}
diff --git a/_schemas/1.0/tests/response/invalid/resource_identifier/resource_must_have_id_member.json b/_schemas/1.0/tests/response/invalid/resource_identifier/resource_must_have_id_member.json
new file mode 100644
index 000000000..64a47dc47
--- /dev/null
+++ b/_schemas/1.0/tests/response/invalid/resource_identifier/resource_must_have_id_member.json
@@ -0,0 +1,18 @@
+{
+ "data": {
+ "type": "article",
+ "meta": {
+ "something": "wrong"
+ }
+ },
+ "meta": {
+ "errors-present-in-document": [
+ {
+ "detail": "A resource object **MUST** contain at least the following top-level members: id, type.",
+ "source": {
+ "pointer": "/data"
+ }
+ }
+ ]
+ }
+}
diff --git a/_schemas/1.0/tests/response/invalid/resource_identifier/resource_must_have_type_member.json b/_schemas/1.0/tests/response/invalid/resource_identifier/resource_must_have_type_member.json
new file mode 100644
index 000000000..5765ada04
--- /dev/null
+++ b/_schemas/1.0/tests/response/invalid/resource_identifier/resource_must_have_type_member.json
@@ -0,0 +1,18 @@
+{
+ "data": {
+ "id": "3",
+ "meta": {
+ "something": "bad"
+ }
+ },
+ "meta": {
+ "errors-present-in-document": [
+ {
+ "detail": "A resource object **MUST** contain at least the following top-level members: id, type.",
+ "source": {
+ "pointer": "/data"
+ }
+ }
+ ]
+ }
+}
diff --git a/_schemas/1.0/tests/response/invalid/resource_identifier/type_must_be_string.json b/_schemas/1.0/tests/response/invalid/resource_identifier/type_must_be_string.json
new file mode 100644
index 000000000..158404b04
--- /dev/null
+++ b/_schemas/1.0/tests/response/invalid/resource_identifier/type_must_be_string.json
@@ -0,0 +1,16 @@
+{
+ "data": {
+ "type": 400,
+ "id": "1"
+ },
+ "meta": {
+ "errors-present-in-document": [
+ {
+ "detail": "A resource's type member **MUST** be a string.",
+ "source": {
+ "pointer": "/data/type"
+ }
+ }
+ ]
+ }
+}
diff --git a/_schemas/1.0/tests/response/invalid/resource_identifier/type_must_not_be_empty.json b/_schemas/1.0/tests/response/invalid/resource_identifier/type_must_not_be_empty.json
new file mode 100644
index 000000000..e979f2c9e
--- /dev/null
+++ b/_schemas/1.0/tests/response/invalid/resource_identifier/type_must_not_be_empty.json
@@ -0,0 +1,16 @@
+{
+ "data": {
+ "type": "",
+ "id": "1"
+ },
+ "meta": {
+ "errors-present-in-document": [
+ {
+ "detail": "The values of type members **MUST** contain only allowed characters.",
+ "source": {
+ "pointer": "/data/type"
+ }
+ }
+ ]
+ }
+}
diff --git a/_schemas/1.0/tests/response/invalid/resource_identifier/type_value_is_not_valid.json b/_schemas/1.0/tests/response/invalid/resource_identifier/type_value_is_not_valid.json
new file mode 100644
index 000000000..9640a3417
--- /dev/null
+++ b/_schemas/1.0/tests/response/invalid/resource_identifier/type_value_is_not_valid.json
@@ -0,0 +1,16 @@
+{
+ "data": {
+ "type": "test+1",
+ "id": "1"
+ },
+ "meta": {
+ "errors-present-in-document": [
+ {
+ "detail": "The values of type members **MUST** contain only allowed characters.",
+ "source": {
+ "pointer": "/data/type"
+ }
+ }
+ ]
+ }
+}
diff --git a/_schemas/1.0/tests/response/invalid/resource_identifier/with_additional_properties.json b/_schemas/1.0/tests/response/invalid/resource_identifier/with_additional_properties.json
new file mode 100644
index 000000000..f0936810d
--- /dev/null
+++ b/_schemas/1.0/tests/response/invalid/resource_identifier/with_additional_properties.json
@@ -0,0 +1,17 @@
+{
+ "data": {
+ "type": "article",
+ "id": "1",
+ "bad": "property"
+ },
+ "meta": {
+ "errors-present-in-document": [
+ {
+ "detail": "Unless otherwise noted, objects defined by this specification **MUST NOT** contain any additional members (here, \"bad\").",
+ "source": {
+ "pointer": "/data"
+ }
+ }
+ ]
+ }
+}
diff --git a/_schemas/1.0/tests/response/invalid/top-level/data_and_errors_must_not_coexist.json b/_schemas/1.0/tests/response/invalid/top-level/data_and_errors_must_not_coexist.json
new file mode 100644
index 000000000..e52351d77
--- /dev/null
+++ b/_schemas/1.0/tests/response/invalid/top-level/data_and_errors_must_not_coexist.json
@@ -0,0 +1,22 @@
+{
+ "data": {
+ "type": "article",
+ "id": "1"
+ },
+ "errors": [
+ {
+ "status": "400",
+ "title": "Oh no!"
+ }
+ ],
+ "meta": {
+ "errors-present-in-document": [
+ {
+ "detail": "The members data and errors **MUST NOT** coexist in the same document.",
+ "source": {
+ "pointer": "/"
+ }
+ }
+ ]
+ }
+}
diff --git a/_schemas/1.0/tests/response/invalid/top-level/included_must_not_be_alone.json b/_schemas/1.0/tests/response/invalid/top-level/included_must_not_be_alone.json
new file mode 100644
index 000000000..6b43bc63a
--- /dev/null
+++ b/_schemas/1.0/tests/response/invalid/top-level/included_must_not_be_alone.json
@@ -0,0 +1,21 @@
+{
+ "included": [
+ {
+ "type": "article",
+ "id": "1",
+ "attributes": {
+ "somthing": "to know"
+ }
+ }
+ ],
+ "meta": {
+ "errors-present-in-document": [
+ {
+ "detail": "If a document does not contain a top-level data key, the included member **MUST NOT** be present either.",
+ "source": {
+ "pointer": "/"
+ }
+ }
+ ]
+ }
+}
diff --git a/_schemas/1.0/tests/response/invalid/top-level/invalid_root.json b/_schemas/1.0/tests/response/invalid/top-level/invalid_root.json
new file mode 100644
index 000000000..4a89e049f
--- /dev/null
+++ b/_schemas/1.0/tests/response/invalid/top-level/invalid_root.json
@@ -0,0 +1,3 @@
+{
+ "not": "valid"
+}
diff --git a/_schemas/1.0/tests/response/invalid/top-level/links_must_not_have_additional_properties.json b/_schemas/1.0/tests/response/invalid/top-level/links_must_not_have_additional_properties.json
new file mode 100644
index 000000000..9fa017d38
--- /dev/null
+++ b/_schemas/1.0/tests/response/invalid/top-level/links_must_not_have_additional_properties.json
@@ -0,0 +1,15 @@
+{
+ "links": {
+ "wrong": "http://www.example.com"
+ },
+ "meta": {
+ "errors-present-in-document": [
+ {
+ "detail": "Unless otherwise noted, objects defined by this specification **MUST NOT** contain any additional members (here, \"wrong\").",
+ "source": {
+ "pointer": "/links"
+ }
+ }
+ ]
+ }
+}
diff --git a/_schemas/1.0/tests/response/invalid/top-level/no_mandatory_top_level_members.json b/_schemas/1.0/tests/response/invalid/top-level/no_mandatory_top_level_members.json
new file mode 100644
index 000000000..881304d88
--- /dev/null
+++ b/_schemas/1.0/tests/response/invalid/top-level/no_mandatory_top_level_members.json
@@ -0,0 +1,18 @@
+{
+ "jsonapi": {
+ "version": "1.0",
+ "meta": {
+ "errors-present-in-document": [
+ {
+ "detail": "A document **MUST** contain at least one of the following top-level members: data, errors or meta.",
+ "source": {
+ "pointer": "/"
+ }
+ }
+ ]
+ }
+ },
+ "links": {
+ "self": "http://www.example.com"
+ }
+}
diff --git a/_schemas/1.0/tests/response/invalid/top-level/with_additional_properties.json b/_schemas/1.0/tests/response/invalid/top-level/with_additional_properties.json
new file mode 100644
index 000000000..b86a860b8
--- /dev/null
+++ b/_schemas/1.0/tests/response/invalid/top-level/with_additional_properties.json
@@ -0,0 +1,13 @@
+{
+ "something": "wrong",
+ "meta": {
+ "errors-present-in-document": [
+ {
+ "detail": "Unless otherwise noted, objects defined by this specification **MUST NOT** contain any additional members (here, \"something\").",
+ "source": {
+ "pointer": "/"
+ }
+ }
+ ]
+ }
+}
diff --git a/_schemas/1.0/tests/response/valid/with_failure/errors_and_meta.json b/_schemas/1.0/tests/response/valid/with_failure/errors_and_meta.json
new file mode 100644
index 000000000..21c49764c
--- /dev/null
+++ b/_schemas/1.0/tests/response/valid/with_failure/errors_and_meta.json
@@ -0,0 +1,31 @@
+{
+ "errors": [
+ {
+ "id": "1",
+ "links": {
+ "about": "http://www.example.com/errors/1"
+ },
+ "status": "400",
+ "code": "0x002",
+ "title": "human-readable summary of the problem",
+ "source": {
+ "pointer": "/data/id"
+ }
+ },
+ {
+ "id": "2",
+ "links": {
+ "about": "http://www.example.com/errors/2"
+ },
+ "status": "400",
+ "code": "0x008",
+ "title": "human-readable summary of the problem",
+ "source": {
+ "parameter": "include"
+ }
+ }
+ ],
+ "meta": {
+ "anything": "valid"
+ }
+}
diff --git a/_schemas/1.0/tests/response/valid/with_failure/only_errors/one_error.json b/_schemas/1.0/tests/response/valid/with_failure/only_errors/one_error.json
new file mode 100644
index 000000000..652c38b80
--- /dev/null
+++ b/_schemas/1.0/tests/response/valid/with_failure/only_errors/one_error.json
@@ -0,0 +1,16 @@
+{
+ "errors": [
+ {
+ "id": "1",
+ "links": {
+ "about": "http://www.example.com/errors/1"
+ },
+ "status": "400",
+ "code": "0x002",
+ "title": "human-readable summary of the problem",
+ "source": {
+ "pointer": "/data/id"
+ }
+ }
+ ]
+}
diff --git a/_schemas/1.0/tests/response/valid/with_success/complete.json b/_schemas/1.0/tests/response/valid/with_success/complete.json
new file mode 100644
index 000000000..a4fcb6017
--- /dev/null
+++ b/_schemas/1.0/tests/response/valid/with_success/complete.json
@@ -0,0 +1,87 @@
+{
+ "links": {
+ "self": "http://example.com/articles",
+ "first": "http://example.com/articles?page%5Bnumber%5D=1&page%5Bsize%5D=25",
+ "last": {
+ "href": "http://example.com/articles?page%5Bnumber%5D=1&page%5Bsize%5D=25"
+ },
+ "next": null,
+ "prev": null
+ },
+ "data": [
+ {
+ "type": "article",
+ "id": "1",
+ "attributes": {
+ "title": "JSON:API, a specification for building APIs in JSON"
+ },
+ "relationships": {
+ "author": {
+ "links": {
+ "self": "http://example.com/articles/1/relationships/author",
+ "related": "http://example.com/articles/1/author"
+ },
+ "data": {
+ "type": "people",
+ "id": "9"
+ },
+ "meta": {
+ "nothing": "else"
+ }
+ }
+ },
+ "links": {
+ "self": "http://example.com/articles/1"
+ },
+ "meta": {
+ "resource": "is valid"
+ }
+ },
+ {
+ "type": "article",
+ "id": "2",
+ "attributes": {
+ "title": "second"
+ },
+ "relationships": {
+ "author": {
+ "links": {
+ "self": "http://example.com/articles/2/relationships/author",
+ "related": "http://example.com/articles/2/author"
+ },
+ "data": {
+ "type": "people",
+ "id": "9"
+ },
+ "meta": {
+ "nothing": "else"
+ }
+ }
+ },
+ "links": {
+ "self": "http://example.com/articles/2"
+ },
+ "meta": {
+ "resource": "is valid"
+ }
+ }
+ ],
+ "meta": {
+ "something": "ok"
+ },
+ "jsonapi": {
+ "version": "1.0",
+ "meta": {
+ "anything": "right"
+ }
+ },
+ "included": [
+ {
+ "type": "people",
+ "id": "9",
+ "attributes": {
+ "name": "John Doe"
+ }
+ }
+ ]
+}
diff --git a/_schemas/1.0/tests/response/valid/with_success/data_and_included/single_resource.json b/_schemas/1.0/tests/response/valid/with_success/data_and_included/single_resource.json
new file mode 100644
index 000000000..8e74285eb
--- /dev/null
+++ b/_schemas/1.0/tests/response/valid/with_success/data_and_included/single_resource.json
@@ -0,0 +1,93 @@
+{
+ "links": {
+ "self": "http://example.com/articles/1"
+ },
+ "data": {
+ "type": "articles",
+ "id": "1",
+ "attributes": {
+ "title": "JSON:API, a specification for building APIs in JSON"
+ },
+ "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"
+ }
+ ]
+ }
+ },
+ "links": {
+ "self": "http://example.com/articles/1"
+ }
+ },
+ "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": "Second"
+ },
+ "relationships": {
+ "author": {
+ "data": {
+ "type": "people",
+ "id": "9"
+ }
+ }
+ },
+ "links": {
+ "self": "http://example.com/comments/12"
+ }
+ }
+ ]
+}
diff --git a/_schemas/1.0/tests/response/valid/with_success/data_and_meta.json b/_schemas/1.0/tests/response/valid/with_success/data_and_meta.json
new file mode 100644
index 000000000..876733b27
--- /dev/null
+++ b/_schemas/1.0/tests/response/valid/with_success/data_and_meta.json
@@ -0,0 +1,12 @@
+{
+ "data": {
+ "type": "article",
+ "id": "1",
+ "attributes": {
+ "title": "JSON:API, a specification for building APIs in JSON"
+ }
+ },
+ "meta": {
+ "anything": "valid"
+ }
+}
diff --git a/_schemas/1.0/tests/response/valid/with_success/data_is_null.json b/_schemas/1.0/tests/response/valid/with_success/data_is_null.json
new file mode 100644
index 000000000..3356af5ff
--- /dev/null
+++ b/_schemas/1.0/tests/response/valid/with_success/data_is_null.json
@@ -0,0 +1,3 @@
+{
+ "data": null
+}
diff --git a/_schemas/1.0/tests/response/valid/with_success/linkage/empty_to_many.json b/_schemas/1.0/tests/response/valid/with_success/linkage/empty_to_many.json
new file mode 100644
index 000000000..c202bd0ff
--- /dev/null
+++ b/_schemas/1.0/tests/response/valid/with_success/linkage/empty_to_many.json
@@ -0,0 +1,14 @@
+{
+ "data": {
+ "type": "article",
+ "id": "1",
+ "attributes": {
+ "title": "JSON:API, a specification for building APIs in JSON"
+ },
+ "relationships": {
+ "comments": {
+ "data": []
+ }
+ }
+ }
+}
diff --git a/_schemas/1.0/tests/response/valid/with_success/linkage/empty_to_one.json b/_schemas/1.0/tests/response/valid/with_success/linkage/empty_to_one.json
new file mode 100644
index 000000000..029f01579
--- /dev/null
+++ b/_schemas/1.0/tests/response/valid/with_success/linkage/empty_to_one.json
@@ -0,0 +1,14 @@
+{
+ "data": {
+ "type": "article",
+ "id": "1",
+ "attributes": {
+ "title": "JSON:API, a specification for building APIs in JSON"
+ },
+ "relationships": {
+ "author": {
+ "data": null
+ }
+ }
+ }
+}
diff --git a/_schemas/1.0/tests/response/valid/with_success/linkage/to_many.json b/_schemas/1.0/tests/response/valid/with_success/linkage/to_many.json
new file mode 100644
index 000000000..254bd6f86
--- /dev/null
+++ b/_schemas/1.0/tests/response/valid/with_success/linkage/to_many.json
@@ -0,0 +1,34 @@
+{
+ "data": {
+ "type": "article",
+ "id": "1",
+ "attributes": {
+ "title": "JSON:API, a specification for building APIs in JSON"
+ },
+ "relationships": {
+ "comments": {
+ "links": {
+ "self": "http://example.com/articles/1/relationships/comments",
+ "related": "http://example.com/articles/1/comments",
+ "first": "http://example.com/articles/1/comments?page%5Bnumber%5D=1&page%5Bsize%5D=25",
+ "last": "http://example.com/articles/1/comments?page%5Bnumber%5D=1&page%5Bsize%5D=25",
+ "prev": null,
+ "next": null
+ },
+ "data": [
+ {
+ "type": "comment",
+ "id": "12"
+ },
+ {
+ "type": "comment",
+ "id": "15",
+ "meta": {
+ "something": "ok"
+ }
+ }
+ ]
+ }
+ }
+ }
+}
diff --git a/_schemas/1.0/tests/response/valid/with_success/linkage/to_one.json b/_schemas/1.0/tests/response/valid/with_success/linkage/to_one.json
new file mode 100644
index 000000000..931514c4e
--- /dev/null
+++ b/_schemas/1.0/tests/response/valid/with_success/linkage/to_one.json
@@ -0,0 +1,20 @@
+{
+ "data": {
+ "type": "article",
+ "id": "1",
+ "attributes": {
+ "title": "JSON:API, a specification for building APIs in JSON"
+ },
+ "relationships": {
+ "comments": {
+ "data": {
+ "type": "people",
+ "id": "9",
+ "meta": {
+ "something": "ok"
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/_schemas/1.0/tests/response/valid/with_success/only_data/empty_resource_collection.json b/_schemas/1.0/tests/response/valid/with_success/only_data/empty_resource_collection.json
new file mode 100644
index 000000000..268c73f0e
--- /dev/null
+++ b/_schemas/1.0/tests/response/valid/with_success/only_data/empty_resource_collection.json
@@ -0,0 +1,3 @@
+{
+ "data": []
+}
diff --git a/_schemas/1.0/tests/response/valid/with_success/only_data/no_resource_null.json b/_schemas/1.0/tests/response/valid/with_success/only_data/no_resource_null.json
new file mode 100644
index 000000000..3356af5ff
--- /dev/null
+++ b/_schemas/1.0/tests/response/valid/with_success/only_data/no_resource_null.json
@@ -0,0 +1,3 @@
+{
+ "data": null
+}
diff --git a/_schemas/1.0/tests/response/valid/with_success/only_data/parallel_relationships.json b/_schemas/1.0/tests/response/valid/with_success/only_data/parallel_relationships.json
new file mode 100644
index 000000000..146f87f46
--- /dev/null
+++ b/_schemas/1.0/tests/response/valid/with_success/only_data/parallel_relationships.json
@@ -0,0 +1,33 @@
+{
+ "data": {
+ "type": "article",
+ "id": "1",
+ "attributes": {
+ "title": "JSON:API, a specification for building APIs in JSON"
+ },
+ "relationships": {
+ "author": {
+ "data": [
+ {
+ "type": "people",
+ "id": "9"
+ },
+ {
+ "type": "people",
+ "id": "9"
+ }
+ ]
+ }
+ }
+ },
+ "meta": {
+ "errors-present-in-document": [
+ {
+ "detail": "A document **MUST NOT** include more than one resource object for each \"type\" and \"id\" pair.",
+ "source": {
+ "pointer": "/data/relationships/author/data"
+ }
+ }
+ ]
+ }
+}
diff --git a/_schemas/1.0/tests/response/valid/with_success/only_data/resource_collection.json b/_schemas/1.0/tests/response/valid/with_success/only_data/resource_collection.json
new file mode 100644
index 000000000..9a5953955
--- /dev/null
+++ b/_schemas/1.0/tests/response/valid/with_success/only_data/resource_collection.json
@@ -0,0 +1,28 @@
+{
+ "data": [
+ {
+ "type": "article",
+ "id": "1",
+ "attributes": {
+ "something": true,
+ "title": "first article"
+ }
+ },
+ {
+ "type": "article",
+ "id": "2",
+ "attributes": {
+ "something": true,
+ "title": "second article"
+ }
+ },
+ {
+ "type": "article",
+ "id": "3",
+ "attributes": {
+ "something": false,
+ "title": "third article"
+ }
+ }
+ ]
+}
diff --git a/_schemas/1.0/tests/response/valid/with_success/only_data/resource_identifier_collection.json b/_schemas/1.0/tests/response/valid/with_success/only_data/resource_identifier_collection.json
new file mode 100644
index 000000000..8fa3edff3
--- /dev/null
+++ b/_schemas/1.0/tests/response/valid/with_success/only_data/resource_identifier_collection.json
@@ -0,0 +1,16 @@
+{
+ "data": [
+ {
+ "type": "article",
+ "id": "1"
+ },
+ {
+ "type": "article",
+ "id": "2"
+ },
+ {
+ "type": "article",
+ "id": "3"
+ }
+ ]
+}
diff --git a/_schemas/1.0/tests/response/valid/with_success/only_data/single_resource.json b/_schemas/1.0/tests/response/valid/with_success/only_data/single_resource.json
new file mode 100644
index 000000000..e2dc7db4c
--- /dev/null
+++ b/_schemas/1.0/tests/response/valid/with_success/only_data/single_resource.json
@@ -0,0 +1,16 @@
+{
+ "data": {
+ "type": "article",
+ "id": "1",
+ "attributes": {
+ "title": "JSON:API, a specification for building APIs in JSON"
+ },
+ "relationships": {
+ "toMany": {
+ "links": {
+ "self": "http://example.com/something/to-many"
+ }
+ }
+ }
+ }
+}
diff --git a/_schemas/1.0/tests/response/valid/with_success/only_data/single_resource_identifier.json b/_schemas/1.0/tests/response/valid/with_success/only_data/single_resource_identifier.json
new file mode 100644
index 000000000..54f60a2ba
--- /dev/null
+++ b/_schemas/1.0/tests/response/valid/with_success/only_data/single_resource_identifier.json
@@ -0,0 +1,6 @@
+{
+ "data": {
+ "type": "article",
+ "id": "1"
+ }
+}
diff --git a/_schemas/1.0/tests/response/valid/with_success/only_data/single_resource_with_empty_attributes.json b/_schemas/1.0/tests/response/valid/with_success/only_data/single_resource_with_empty_attributes.json
new file mode 100644
index 000000000..661865dd1
--- /dev/null
+++ b/_schemas/1.0/tests/response/valid/with_success/only_data/single_resource_with_empty_attributes.json
@@ -0,0 +1,7 @@
+{
+ "data": {
+ "type": "article",
+ "id": "1",
+ "attributes": {}
+ }
+}
diff --git a/_schemas/1.0/tests/response/valid/with_success/only_meta.json b/_schemas/1.0/tests/response/valid/with_success/only_meta.json
new file mode 100644
index 000000000..af6e064a0
--- /dev/null
+++ b/_schemas/1.0/tests/response/valid/with_success/only_meta.json
@@ -0,0 +1,9 @@
+{
+ "meta": {
+ "anything": "valid",
+ "count": 3,
+ "object": {
+ "attr": "value"
+ }
+ }
+}
diff --git a/_schemas/1.0/tests/response/valid/with_success/only_meta/empty_meta.json b/_schemas/1.0/tests/response/valid/with_success/only_meta/empty_meta.json
new file mode 100644
index 000000000..6da7422b1
--- /dev/null
+++ b/_schemas/1.0/tests/response/valid/with_success/only_meta/empty_meta.json
@@ -0,0 +1,3 @@
+{
+ "meta": {}
+}
diff --git a/_schemas/1.0/tests/response/valid/with_success/only_meta/meta_with_members.json b/_schemas/1.0/tests/response/valid/with_success/only_meta/meta_with_members.json
new file mode 100644
index 000000000..af6e064a0
--- /dev/null
+++ b/_schemas/1.0/tests/response/valid/with_success/only_meta/meta_with_members.json
@@ -0,0 +1,9 @@
+{
+ "meta": {
+ "anything": "valid",
+ "count": 3,
+ "object": {
+ "attr": "value"
+ }
+ }
+}
diff --git a/_schemas/scripts/validator.js b/_schemas/scripts/validator.js
new file mode 100644
index 000000000..539b749e3
--- /dev/null
+++ b/_schemas/scripts/validator.js
@@ -0,0 +1,317 @@
+// Import the modules
+const Ajv2020 = require("ajv/dist/2020");
+const addFormats = require("ajv-formats");
+const readdirp = require("readdirp");
+const npath = require("path");
+const { compareVersions } = require("compare-versions");
+
+/**
+ * Retrieve the value of the given option.
+ * @param {String} name - The name of the option to retrieve
+ * @param {*} defaultValue - The default value
+ * @returns {*}
+ */
+const getArgValue = function (name, defaultValue) {
+ const index = process.argv.indexOf(name);
+ let value;
+
+ if (index > -1) {
+ // Retrieve the value after --custom
+ value = process.argv[index + 1];
+ }
+
+ return (value || defaultValue);
+};
+
+// Define root dir
+const rootDir = "./_schemas";
+
+// Define object that will record the full path of all files tested
+const files = {};
+
+// Check for options
+const options = {
+ verbose: process.argv.indexOf("--verbose") > -1,
+ requiredVersion: getArgValue("-v"),
+ requiredFile: getArgValue("-f"),
+
+ onlyOneVersion: function () {
+ return typeof this.requiredVersion !== "undefined";
+ },
+ onlyForVersion: function (version) {
+ return this.onlyOneVersion() && this.requiredVersion === version;
+ },
+ singleTest: function() {
+ return (typeof this.requiredFile !== "undefined");
+ }
+};
+
+// Main function
+(async function () {
+ try {
+ // List all the available versions of the spec
+ for await (const entry of readdirp(rootDir, { depth: 0, entryType: "directories" })) {
+ const version = entry.path;
+ if (isNaN(version)) {
+ continue;
+ }
+
+ // For this version, ...
+
+ // ... prepare record object
+ files[version] = new Array();
+
+ // ... if not required to test this version, register all version-specific test files
+ if (options.onlyOneVersion() && !options.onlyForVersion(version)) {
+ await readTestDir(version);
+ continue;
+ }
+
+ // ... log some infos
+ console.log("");
+ console.log("JSON:API spec version : %s", version);
+ console.log("");
+
+ // ... create ajv instance
+ const ajv = ajvFactory();
+
+ // ... add schemas to ajv instance
+ await addSchemas(ajv, version);
+
+ // ... test all files
+ const { ok, ko } = await handleTests(ajv, version);
+
+ // ... log the results
+ if (options.verbose) {
+ console.log("");
+ }
+ console.log("Tests done : %d, success : %d, errors : %d", ko.length + ok.length, ok.length, ko.length);
+ if (ko.length != 0) {
+ ko.forEach(item => {
+ console.log("");
+ console.log(item.relativePath + " should be " + (item.shouldBeValid ? "valid" : "invalid") + " but is not.");
+ console.log(" spec version : " + item.version);
+ console.log(" absolute path : " + item.fullPath);
+ console.log(" errors : ");
+ console.log(item.errors.map(err => " " + err.instancePath + " : " + err.message).join("\n"));
+ });
+
+ }
+
+ // ... define the exit code
+ process.exitCode = (ko.length != 0) ? 1 : 0;
+ }
+ } catch (err) {
+ console.log(err);
+ process.exitCode = 2;
+ }
+})()
+
+/**
+ * Generate new instance of Ajv validator.
+ * @returns {Ajv2020} - An instance of Ajv validator
+ */
+const ajvFactory = function () {
+ // ... create ajv instance
+ const ajv = new Ajv2020();
+
+ // ...add formats to ajv validator
+ addFormats(ajv);
+
+ return ajv;
+};
+
+/**
+ * Load all version-related schemas and register them with the instance of the Ajv validator.
+ * @param {Ajv2020} ajv - An instance of Ajv validator
+ * @param {String} version - The version of the JSON:API spec that will be validated
+ * @return {void}
+ */
+const addSchemas = async function (ajv, version) {
+ for await (const entry of readdirp(`${rootDir}/${version}/`, { depth: 0, fileFilter: "*.json" })) {
+ const { path, fullPath } = entry;
+
+ // Load schema file
+ const schema = require(fullPath);
+
+ // Build the schema key to be used
+ const t = path.split(".").shift().split("_");
+ t.shift();
+ const t2 = t.join("_");
+ const key = t2 === "" ? "response" : "request_" + t2;
+
+ // Add schema to the Ajv validator instance
+ ajv.addSchema(schema, key);
+
+ if (options.verbose) {
+ console.log("Add schema : %s => %s", key, fullPath);
+ }
+ }
+ if (options.verbose) {
+ console.log("");
+ }
+};
+
+/**
+ * Check all the test files for the given version of the JSON:API spec.
+ * @param {Ajv2020} ajv - An instance of Ajv validator
+ * @param {String} version - The version of the JSON:API spec that will be validated
+ * @return {{ok: String[], ko: String[]}} - The results of the tests.
+ */
+const handleTests = async function (ajv, version) {
+ // Define some constants
+ const results = {
+ ok: new Array(),
+ ko: new Array()
+ };
+
+ if (options.singleTest()) {
+ await runSingleTest(ajv, version, results);
+ } else {
+ await runAllTests(ajv, version, results);
+ }
+
+ return results;
+};
+
+/**
+ * Check all the test files for the given version of the JSON:API spec.
+ * @param {Ajv2020} ajv - An instance of Ajv validator
+ * @param {String} version - The version of the JSON:API spec that will be validated
+ * @param {{ok: String[], ko: String[]}} results - The results of the tests.
+ * @return {void}
+ */
+const runAllTests = async function (ajv, version, results) {
+ // Build a list of test files from previous versions
+ const tmpFiles = new Array();
+ for (const v in files) {
+ if (compareVersions(version, v) === 1) {
+ tmpFiles.push(...files[v]);
+ }
+ }
+
+ // Validate each file of previous versions
+ tmpFiles.forEach(item => {
+ runTest(ajv, version, item.relativePath, item.fullPath, results);
+ });
+
+ // Iterate through version-specifiques test files
+ await readTestDir(version, (path, fullPath) => {
+ // Validate test file
+ runTest(ajv, version, path, fullPath, results);
+ })
+};
+
+/**
+ * Run a single test for the given version of the JSON:API spec and the test file that was passed in the options.
+ * @param {Ajv2020} ajv - An instance of Ajv validator
+ * @param {String} version - The version of the JSON:API spec that will be validated
+ * @param {{ok: String[], ko: String[]}} results - The result of the test.
+ * @return {void}
+ */
+const runSingleTest = async function (ajv, version, results) {
+ // Retrieve relative and full path of the file to test
+ const baseDir = __dirname.replaceAll(/[\/\\]/g, npath.sep).split(npath.sep);
+ baseDir.pop();
+ const filePath = options.requiredFile.replaceAll(/[\/\\]/g, npath.sep).split(npath.sep);
+ if (filePath[0] === "_schemas") {
+ filePath.shift();
+ }
+ const fullPath = [...baseDir, ...filePath].join(npath.sep);
+
+ filePath.shift();
+ filePath.shift();
+ const path = filePath.join(npath.sep);
+
+ // Validate test file
+ runTest(ajv, version, path, fullPath, results);
+};
+
+/**
+ * Check a single test files for the given version of the JSON:API spec.
+ * @param {Ajv2020} ajv - An instance of Ajv validator
+ * @param {String} version - The version of the JSON:API spec that will be validated
+ * @param {String} relativePath - The relative path of the test file that will be validated
+ * @param {String} fullPath - The absolute path of the test file that will be validated
+ * @param {{ok: String[], ko: String[]}} results - The results of the test.
+ * @return {void}
+ */
+const runTest = function (ajv, version, relativePath, fullPath, results) {
+ // Validate test file
+ const result = doTest(ajv, version, relativePath, fullPath);
+
+ // Handle validation result
+ if (result.ok) {
+ results.ok.push(result);
+ } else {
+ results.ko.push(result);
+ }
+};
+
+
+/**
+ * Load the list of test files for the given version of the JSON:API spec.
+ * @param {String} version - The version of the JSON:API spec that will be validated
+ * @param {Function} callback - A callback function to execute for each test file with path (relative path of test file) and fullPath (absolute path of test file) as arguments.
+ * @return {void}
+ */
+const readTestDir = async function (version, callback) {
+ // Iterate through version-specifiques test files
+ for await (const entry of readdirp(`${rootDir}/${version}/tests/`, { fileFilter: "*.json" })) {
+ const { path, fullPath } = entry;
+
+ if (callback) {
+ callback(path, fullPath);
+ }
+ // Record file path
+ files[version].push({
+ relativePath: path,
+ fullPath: fullPath
+ });
+ }
+};
+
+/**
+ * validate a test file for the given version of the JSON:API spec.
+ * @param {Ajv2020} ajv - An instance of Ajv validator
+ * @param {String} version - The version of the JSON:API spec that will be validated
+ * @param {String} relativePath - The relative path of the file to validate.
+ * @param {String} fullPath - The absolute path of the file to validate.
+ * @return {{version: String, relativePath: String, fullPath: String, ok : Boolean, errors: Object}} - The result of the validation.
+ */
+const doTest = function (ajv, version, relativePath, fullPath) {
+ if (options.verbose) {
+ console.log("testing : %s : %s", version, fullPath);
+ }
+
+ // Load file
+ const data = require(fullPath);
+
+ // Build the schema key to be used
+ const re = new RegExp("(.*)\\" + npath.sep + "(in)?valid\\" + npath.sep);
+ const matches = relativePath.match(re);
+ const valid_data = typeof matches[2] === "undefined";
+
+ const t = matches[1].split(npath.sep);
+ const key = [
+ t.shift(),
+ ...t.reverse()
+ ];
+
+ // Generate validating function
+ const validate = ajv.getSchema(key.join("_"));
+
+ // Validate file
+ const valid = validate(data);
+ const ok = (valid && valid_data) || (!valid && !valid_data);
+
+ // Return result of validation
+ return {
+ version: version,
+ relativePath: relativePath,
+ fullPath: fullPath,
+ shouldBeValid: valid_data,
+ ok: ok,
+ errors: !ok ? validate.errors : undefined
+ };
+};
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 000000000..f95b1fe3b
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,117 @@
+{
+ "name": "json-api",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "devDependencies": {
+ "ajv": "^8.12.0",
+ "ajv-formats": "^2.1.1",
+ "compare-versions": "^6.1.0",
+ "readdirp": "^3.6.0"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "8.12.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
+ "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
+ "dev": true,
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "json-schema-traverse": "^1.0.0",
+ "require-from-string": "^2.0.2",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ajv-formats": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz",
+ "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
+ "dev": true,
+ "dependencies": {
+ "ajv": "^8.0.0"
+ },
+ "peerDependencies": {
+ "ajv": "^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "ajv": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/compare-versions": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.0.tgz",
+ "integrity": "sha512-LNZQXhqUvqUTotpZ00qLSaify3b4VFD588aRr8MKFw4CMUr98ytzCW5wDH5qx/DEY5kCDXcbcRuCqL0szEf2tg==",
+ "dev": true
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
+ "dev": true
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/punycode": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz",
+ "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dev": true,
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
+ "node_modules/require-from-string": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 000000000..c5d527681
--- /dev/null
+++ b/package.json
@@ -0,0 +1,11 @@
+{
+ "devDependencies": {
+ "ajv": "^8.12.0",
+ "ajv-formats": "^2.1.1",
+ "compare-versions": "^6.1.0",
+ "readdirp": "^3.6.0"
+ },
+ "scripts": {
+ "test-schema": "node -i _schemas/scripts/validator.js"
+ }
+}
\ No newline at end of file
diff --git a/schema b/schema
deleted file mode 100644
index 56a404c2e..000000000
--- a/schema
+++ /dev/null
@@ -1,397 +0,0 @@
-{
- "$schema": "http://json-schema.org/draft-06/schema#",
- "title": "JSON:API Schema",
- "description": "This is a schema for responses in the JSON:API format. For more, see http://jsonapi.org",
- "oneOf": [
- {
- "$ref": "#/definitions/success"
- },
- {
- "$ref": "#/definitions/failure"
- },
- {
- "$ref": "#/definitions/info"
- }
- ],
-
- "definitions": {
- "success": {
- "type": "object",
- "required": [
- "data"
- ],
- "properties": {
- "data": {
- "$ref": "#/definitions/data"
- },
- "included": {
- "description": "To reduce the number of HTTP requests, servers **MAY** allow responses that include related resources along with the requested primary resources. Such responses are called \"compound documents\".",
- "type": "array",
- "items": {
- "$ref": "#/definitions/resource"
- },
- "uniqueItems": true
- },
- "meta": {
- "$ref": "#/definitions/meta"
- },
- "links": {
- "description": "Link members related to the primary data.",
- "allOf": [
- {
- "$ref": "#/definitions/links"
- },
- {
- "$ref": "#/definitions/pagination"
- }
- ]
- },
- "jsonapi": {
- "$ref": "#/definitions/jsonapi"
- }
- },
- "additionalProperties": false
- },
- "failure": {
- "type": "object",
- "required": [
- "errors"
- ],
- "properties": {
- "errors": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/error"
- },
- "uniqueItems": true
- },
- "meta": {
- "$ref": "#/definitions/meta"
- },
- "jsonapi": {
- "$ref": "#/definitions/jsonapi"
- },
- "links": {
- "$ref": "#/definitions/links"
- }
- },
- "additionalProperties": false
- },
- "info": {
- "type": "object",
- "required": [
- "meta"
- ],
- "properties": {
- "meta": {
- "$ref": "#/definitions/meta"
- },
- "links": {
- "$ref": "#/definitions/links"
- },
- "jsonapi": {
- "$ref": "#/definitions/jsonapi"
- }
- },
- "additionalProperties": false
- },
-
- "meta": {
- "description": "Non-standard meta-information that can not be represented as an attribute or relationship.",
- "type": "object",
- "additionalProperties": true
- },
- "data": {
- "description": "The document's \"primary data\" is a representation of the resource or collection of resources targeted by a request.",
- "oneOf": [
- {
- "$ref": "#/definitions/resource"
- },
- {
- "description": "An array of resource objects, an array of resource identifier objects, or an empty array ([]), for requests that target resource collections.",
- "type": "array",
- "items": {
- "$ref": "#/definitions/resource"
- },
- "uniqueItems": true
- },
- {
- "description": "null if the request is one that might correspond to a single resource, but doesn't currently.",
- "type": "null"
- }
- ]
- },
- "resource": {
- "description": "\"Resource objects\" appear in a JSON:API document to represent resources.",
- "type": "object",
- "required": [
- "type",
- "id"
- ],
- "properties": {
- "type": {
- "type": "string"
- },
- "id": {
- "type": "string"
- },
- "attributes": {
- "$ref": "#/definitions/attributes"
- },
- "relationships": {
- "$ref": "#/definitions/relationships"
- },
- "links": {
- "$ref": "#/definitions/links"
- },
- "meta": {
- "$ref": "#/definitions/meta"
- }
- },
- "additionalProperties": false
- },
- "relationshipLinks": {
- "description": "A resource object **MAY** contain references to other resource objects (\"relationships\"). Relationships may be to-one or to-many. Relationships can be specified by including a member in a resource's links object.",
- "type": "object",
- "properties": {
- "self": {
- "description": "A `self` member, whose value is a URL for the relationship itself (a \"relationship URL\"). This URL allows the client to directly manipulate the relationship. For example, it would allow a client to remove an `author` from an `article` without deleting the people resource itself.",
- "$ref": "#/definitions/link"
- },
- "related": {
- "$ref": "#/definitions/link"
- }
- },
- "additionalProperties": true
- },
- "links": {
- "type": "object",
- "additionalProperties": {
- "$ref": "#/definitions/link"
- }
- },
- "link": {
- "description": "A link **MUST** be represented as either: a string containing the link's URL or a link object.",
- "oneOf": [
- {
- "description": "A string containing the link's URL.",
- "type": "string",
- "format": "uri-reference"
- },
- {
- "type": "object",
- "required": [
- "href"
- ],
- "properties": {
- "href": {
- "description": "A string containing the link's URL.",
- "type": "string",
- "format": "uri-reference"
- },
- "meta": {
- "$ref": "#/definitions/meta"
- }
- }
- }
- ]
- },
-
- "attributes": {
- "description": "Members of the attributes object (\"attributes\") represent information about the resource object in which it's defined.",
- "type": "object",
- "patternProperties": {
- "^[a-zA-Z0-9](?:[-\\w]*[a-zA-Z0-9])?$": {
- "description": "Attributes may contain any valid JSON value."
- }
- },
- "not": {
- "anyOf": [
- {"required": ["relationships"]},
- {"required": ["links"]},
- {"required": ["id"]},
- {"required": ["type"]}
- ]
- },
- "additionalProperties": false
- },
-
- "relationships": {
- "description": "Members of the relationships object (\"relationships\") represent references from the resource object in which it's defined to other resource objects.",
- "type": "object",
- "patternProperties": {
- "^[a-zA-Z0-9](?:[-\\w]*[a-zA-Z0-9])?$": {
- "properties": {
- "links": {
- "$ref": "#/definitions/relationshipLinks"
- },
- "data": {
- "description": "Member, whose value represents \"resource linkage\".",
- "oneOf": [
- {
- "$ref": "#/definitions/relationshipToOne"
- },
- {
- "$ref": "#/definitions/relationshipToMany"
- }
- ]
- },
- "meta": {
- "$ref": "#/definitions/meta"
- }
- },
- "anyOf": [
- {"required": ["data"]},
- {"required": ["meta"]},
- {"required": ["links"]}
- ],
- "not": {
- "anyOf": [
- {"required": ["id"]},
- {"required": ["type"]}
- ]
- },
- "additionalProperties": false
- }
- },
- "additionalProperties": false
- },
- "relationshipToOne": {
- "description": "References to other resource objects in a to-one (\"relationship\"). Relationships can be specified by including a member in a resource's links object.",
- "anyOf": [
- {
- "$ref": "#/definitions/empty"
- },
- {
- "$ref": "#/definitions/linkage"
- }
- ]
- },
- "relationshipToMany": {
- "description": "An array of objects each containing \"type\" and \"id\" members for to-many relationships.",
- "type": "array",
- "items": {
- "$ref": "#/definitions/linkage"
- },
- "uniqueItems": true
- },
- "empty": {
- "description": "Describes an empty to-one relationship.",
- "type": "null"
- },
- "linkage": {
- "description": "The \"type\" and \"id\" to non-empty members.",
- "type": "object",
- "required": [
- "type",
- "id"
- ],
- "properties": {
- "type": {
- "type": "string"
- },
- "id": {
- "type": "string"
- },
- "meta": {
- "$ref": "#/definitions/meta"
- }
- },
- "additionalProperties": false
- },
- "pagination": {
- "type": "object",
- "properties": {
- "first": {
- "description": "The first page of data",
- "oneOf": [
- { "$ref": "#/definitions/link" },
- { "type": "null" }
- ]
- },
- "last": {
- "description": "The last page of data",
- "oneOf": [
- { "$ref": "#/definitions/link" },
- { "type": "null" }
- ]
- },
- "prev": {
- "description": "The previous page of data",
- "oneOf": [
- { "$ref": "#/definitions/link" },
- { "type": "null" }
- ]
- },
- "next": {
- "description": "The next page of data",
- "oneOf": [
- { "$ref": "#/definitions/link" },
- { "type": "null" }
- ]
- }
- }
- },
-
- "jsonapi": {
- "description": "An object describing the server's implementation",
- "type": "object",
- "properties": {
- "version": {
- "type": "string"
- },
- "meta": {
- "$ref": "#/definitions/meta"
- }
- },
- "additionalProperties": false
- },
-
- "error": {
- "type": "object",
- "properties": {
- "id": {
- "description": "A unique identifier for this particular occurrence of the problem.",
- "type": "string"
- },
- "links": {
- "$ref": "#/definitions/links"
- },
- "status": {
- "description": "The HTTP status code applicable to this problem, expressed as a string value.",
- "type": "string"
- },
- "code": {
- "description": "An application-specific error code, expressed as a string value.",
- "type": "string"
- },
- "title": {
- "description": "A short, human-readable summary of the problem. It **SHOULD NOT** change from occurrence to occurrence of the problem, except for purposes of localization.",
- "type": "string"
- },
- "detail": {
- "description": "A human-readable explanation specific to this occurrence of the problem.",
- "type": "string"
- },
- "source": {
- "type": "object",
- "properties": {
- "pointer": {
- "description": "A JSON Pointer [RFC6901] to the associated entity in the request document [e.g. \"/data\" for a primary data object, or \"/data/attributes/title\" for a specific attribute].",
- "type": "string"
- },
- "parameter": {
- "description": "A string indicating which query parameter caused the error.",
- "type": "string"
- }
- }
- },
- "meta": {
- "$ref": "#/definitions/meta"
- }
- },
- "additionalProperties": false
- }
- }
-}
-
From 6f85ee971b71ec34e58bbcb14858015a655aeaa7 Mon Sep 17 00:00:00 2001
From: Guille Gonzalez
Date: Tue, 18 Jun 2024 19:47:00 +0200
Subject: [PATCH 16/34] Add Datadog/swift-jsonapi to the implementations
section (#1748)
---
implementations/index.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/implementations/index.md b/implementations/index.md
index 3939939a4..f650295d3 100644
--- a/implementations/index.md
+++ b/implementations/index.md
@@ -76,6 +76,7 @@ isomorphic ActiveRecord clone that issues JSON:API requests instead of SQL and i
* [Japx](https://github.com/infinum/Japx) is lightweight JSON:API parser that flattens complex JSON:API structure and turns it into simple JSON and vice versa. It works by transferring Dictionary to Dictionary, so you can use Codable, Unbox, Wrap, ObjectMapper or any other object mapping tool that you prefer. It supports Objective-C as well.
* [mattpolzin / JSONAPI](https://github.com/mattpolzin/JSONAPI) is a Swift Codable library with heavy emphasis on type-safety. It is platform agnostic so it can be used client- and server-side.
* [IzzyParser](https://github.com/undabot/izzyparser-ios) is a lightweight library for serializing and deserializing JSON:API objects.
+* [Datadog/swift-jsonapi](https://github.com/Datadog/swift-jsonapi) is a Swift library that provides macros to simplify the encoding and decoding of JSON:API responses.
### Ruby
@@ -156,6 +157,7 @@ and writing of JSON:API documents.
### Swift
* [aonawale / JSONAPISerializer](https://github.com/aonawale/JSONAPISerializer) is a server side swift framework agnostic library that implements JSON:API v1.0.
* [mattpolzin / JSONAPI](https://github.com/mattpolzin/JSONAPI) is a Swift Codable library with heavy emphasis on type-safety. It is platform agnostic so it can be used client- and server-side.
+* [Datadog/swift-jsonapi](https://github.com/Datadog/swift-jsonapi) is a Swift library that provides macros to simplify the encoding and decoding of JSON:API responses.
### PHP
From 2fd0b559a4544039ea15f4807392af42aae91169 Mon Sep 17 00:00:00 2001
From: Caio Tarifa
Date: Tue, 18 Jun 2024 14:50:49 -0300
Subject: [PATCH 17/34] feat: add `fetchja` library on JavaScript
implementations (#1747)
---
implementations/index.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/implementations/index.md b/implementations/index.md
index f650295d3..2beec077b 100644
--- a/implementations/index.md
+++ b/implementations/index.md
@@ -54,6 +54,7 @@ isomorphic ActiveRecord clone that issues JSON:API requests instead of SQL and i
* [mobx-async-store](https://github.com/artemis-ag/mobx-async-store) - A Mobx-based store for async data fetching and state management for the JSON:API specification.
* [spraypaint](https://github.com/graphiti-api/spraypaint.js) - JS Client for Graphiti similar to ActiveRecord. Written in Typescript but works in plain old ES5 as well. This library is isomorphic - use it from the browser, or from the server with NodeJS.
* [json-api-models](https://github.com/tobyzerner/json-api-models) - A lightweight layer for working with JSON:API data.
+* [fetchja](https://github.com/caiotarifa/fetchja) - A super simple, modern, and lightweight library for dealing with JSON:API (Kitsu-like, but using Fetch API instead of Axios).
### Typescript
* [ts-angular-jsonapi](https://github.com/reyesoft/ts-angular-jsonapi) A JSON:API library developed for AngularJS in Typescript
From 77bc3cbbaf5cf04f25cb592dade51e0c830cde95 Mon Sep 17 00:00:00 2001
From: Jeldrik Hanschke
Date: Thu, 15 Aug 2024 22:28:05 +0200
Subject: [PATCH 18/34] top-level links object is related to the document in
some cases (#1753)
* top-level links object is related to the document in some cases
The top-level `links` object is related to the whole document in 2 out of 4 cases: `self` and `describedby` links are related to the document. Not only to its primary data. Even more they are useful even if a document does not have primary data.
A document could not have primary data if 1) being an error response, 2) a response to a successful mutation, and 3) having an extension applied. Top-level links, especially `self`, are commonly used in those cases. And intended to be used.
This PR improves the wording of the specification to avoid confusion. It was motivated by this thread in the discussion forum: https://jsonapi.org/format/
* fix typo
---
_format/1.2/index.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/_format/1.2/index.md b/_format/1.2/index.md
index 653d9dab0..17a5a986d 100644
--- a/_format/1.2/index.md
+++ b/_format/1.2/index.md
@@ -303,7 +303,7 @@ 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.
+* `links`: a [links object][links] related to the document as a whole.
* `included`: an array of [resource objects] that are related to the primary
data and/or each other ("included resources").
From 5361a05650f4b9ef4cec36188289a13c2a7ed46b Mon Sep 17 00:00:00 2001
From: Spenser Hale
Date: Tue, 17 Sep 2024 09:43:52 -0700
Subject: [PATCH 19/34] Auditing implementations links (#1741)
Removing implementations and tools that no longer exist or archived
Javascript
- backbone-jsonapi
Node.js
- transformalizer
Ruby
- jsonapi-simple_client
- jsonapi-record
Elixir
- JsonApiClient
Playground
- jsonapi-validator
- JSON API Playground
Updating Links:
JavaScript
- JSONAPI Suite
---
implementations/index.md | 16 +++-------------
1 file changed, 3 insertions(+), 13 deletions(-)
diff --git a/implementations/index.md b/implementations/index.md
index 2beec077b..9eb32e247 100644
--- a/implementations/index.md
+++ b/implementations/index.md
@@ -16,7 +16,6 @@ assembled to vet them.
### JavaScript
* [ember-data](https://github.com/emberjs/data) is one of the original exemplar implementations. There is now an [official adapter](https://emberjs.com/api/ember-data/release/classes/DS.JSONAPIAdapter) to support json-api.
-* [backbone-jsonapi](https://github.com/guillaumervls/backbone-jsonapi) is a Backbone adapter for JSON:API. Supports fetching Models & Collections from a JSON:API source.
* [backbone-relational-jsonapi](https://github.com/xbill82/backbone-relational-jsonapi) is a parsing layer for Backbone.Relational. Entities specified in JSON:API are automatically parsed to be injected into Backbone.Relational relations.
* [orbit.js](https://github.com/orbitjs/orbit.js) is a standalone library for
coordinating access to data sources and keeping their contents synchronized.
@@ -42,8 +41,8 @@ assembled to vet them.
* [Sarala JSON API data formatter](https://github.com/milroyfraser/sarala-json-api-data-formatter) is a simple and fluent framework agnostic javascript library to transform standard JSON:API responses to simple JSON objects and vice versa.
* [Sarala](https://github.com/milroyfraser/sarala) is a javascript package which gives you a [Laravel Eloquent](https://laravel.com/docs/5.6/eloquent) like syntax to perform CRUD operations against an JSON:API built according to [JSON:API specification](http://jsonapi.org/format/).
* [jsonapi-client](https://github.com/itsfadnis/jsonapi-client) A convenient module to consume a jsonapi service
-* [JSORM](https://jsonapi-suite.github.io/jsonapi_suite/js/home) is an
-isomorphic ActiveRecord clone that issues JSON:API requests instead of SQL and is part of the larger [JSONAPI Suite](https://jsonapi-suite.github.io/jsonapi_suite).
+* [JSORM](https://github.com/jsonapi-suite/jsorm) is an
+isomorphic ActiveRecord clone that issues JSON:API requests instead of SQL and is part of the larger [JSONAPI Suite](https://github.com/jsonapi-suite/).
* [jsonapi-vuex](https://github.com/mrichar1/jsonapi-vuex) A module for interacting with a jsonapi service using a Vuex store, restructuring/normalizing records to make life easier.
* [heather-js](https://github.com/bitex-la/heather-js) A library for parsing JSONAPI into objects from ES6 classes.
* [@hyral/core](https://github.com/SyneticNL/Hyral) - An advanced, documented, easily extendable and lightweight (JSON:)API abstraction library with ORM-like CRUD support, automatic relationships handling and support for multiple (different) backends.
@@ -86,8 +85,6 @@ isomorphic ActiveRecord clone that issues JSON:API requests instead of SQL and i
* [Munson](https://github.com/coryodaniel/munson) is a ruby JSONAPI client that can act as an ORM or integrate with your models via fine-grained agnosticism. Easy to configure and customize. Includes a chainable/customizable query builder, attributes API and dirty tracking.
* [json-api-vanilla](https://github.com/trainline/json-api-vanilla) a reference-aware ruby library for JSONAPI deserialization that doesn't require setting up classes.
* [SimpleJSONAPIClient](https://github.com/amcaplan/simple_jsonapi_client) gives you lower-level control for API operations, while your models and their relationships maintain a neat, ActiveRecord-inspired interface.
-* [jsonapi-simple_client](https://github.com/InspireNL/jsonapi-simple_client) a client to interact with a Server API that implements the JSON:API spec.
-* [jsonapi-record](https://github.com/InspireNL/jsonapi-record) a client framework for interacting JSON:API Spec compliant APIs in Ruby.
* [jsonapi-unwrapper](https://github.com/Sonberg/jsonapi-unwrapper) a simple and lightweight library to deserialize JSON:API payloads.
### PHP
@@ -149,10 +146,6 @@ and writing of JSON:API documents.
* [json-api-doc](https://github.com/noplay/json-api-doc) JSON:API parser returning a simple Python dictionary
* [json-api-smart](https://github.com/NilssonPL/json-api-smart) JSON:API with an ORM interface
-### Elixir
-
-* [JsonApiClient](https://github.com/Decisiv/json_api_client) JSON:API Client Library For Elixir
-
## Server libraries
### Swift
@@ -209,7 +202,6 @@ and writing of JSON:API documents.
* [json-api-ify](https://github.com/kutlerskaggs/json-api-ify) serialize the **** out of your data. json:api v1.0 complaint.
* [bookshelf-jsonapi-params](https://github.com/scoutforpets/bookshelf-jsonapi-params) automatically apply JSON:API filtering, pagination, sparse fieldsets, includes, and sorting to your Bookshelf.js queries.
* [Lux](https://github.com/postlight/lux) is a MVC style Node.js framework for building lightning fast JSON:APIs.
-* [transformalizer](https://github.com/GaiamTV/transformalizer) a bare bones node module for transforming raw data into JSON:API compliant payloads that makes no assumption regarding the shape of your data and sdks used, supports the full v1.0 specification, and supports dynamic transformations, links, and meta at all levels of a document.
* [jsonapi-mock](https://github.com/Thomas-X/jsonapi-mock) A [json-server](https://github.com/typicode/json-server) inspired jsonapi mock server. Setup a jsonapi mock server in almost no time, uses lowdb.
* [DenaliJS](http://denalijs.org) A layered-conventions framework for building ambitious APIs. Includes a powerful addon system, best-in-class developer experience, and extensive documentation.
* [jsonapi-fractal](https://github.com/andersondanilo/jsonapi-fractal) JSON:API Serializer inspired by Fractal (PHP)
@@ -238,7 +230,7 @@ the moment.
* [JSONAPI::Resources](https://github.com/cerebris/jsonapi-resources) provides a complete framework for developing a JSON:API server. It is designed to work with Rails, and provides routes, controllers, and serializers.
* [JSONAPI::Utils](https://github.com/b2beauty/jsonapi-utils) works on top of [JSONAPI::Resources](https://github.com/cerebris/jsonapi-resources) taking advantage of its resource-driven style and bringing a Rails way to build modern APIs with no or less learning curve.
* [Caprese](https://github.com/nicklandgrebe/caprese) An opinionated Rails library for creating JSON:API servers that lets you focus on customizing the behavior of your endpoints rather than the dirty work of setting them up. Leverages the power of [ActiveModel::Serializer](https://github.com/rails-api/active_model_serializers).
- * [JSONAPI Suite](https://jsonapi-suite.github.io/jsonapi_suite)
+ * [JSONAPI Suite](https://github.com/jsonapi-suite)
facilitates a server capable of deep querying and nested writes. Works
with any ORM or datastore; comes with integration test helpers and
automatic swagger documentation.
@@ -349,9 +341,7 @@ includes related resources.
### Playground
* [json-api-document-viewer](https://tadast.github.io/json-api-document-viewer) the flat json:api structure is a good way to express complex relationships between objects. However the same flatness makes it difficult for humans to "parse" these relationships. This tool visualises object relationships by visually nesting them.
-* [jsonapi-validator](https://jsonapi-validator.herokuapp.com) is a playground service for quick prototyping and validating JSON responses with jsonapi.org specification.
* [corroborate](http://corroborate.arenpatel.com/) JSON:API request/response payload validator. It warns when there is a specification violation and also informs when a recommendation has not been followed.
-* [JSON API Playground](http://jsonapiplayground.reyesoft.com/) Fake online JSON:API server for testing and prototyping. It's great for tutorials, faking a server, sharing code examples, etc.
### Ruby
From 0c29063ffd141e821565a0762313af5311234a38 Mon Sep 17 00:00:00 2001
From: Jeldrik Hanschke
Date: Tue, 17 Sep 2024 18:53:35 +0200
Subject: [PATCH 20/34] backporting the clarification of top-level links object
to v1.1 (#1754)
The clarification was merged to v1.2 (upcoming) in #1753. This backports the changes to v1.1.
---
_format/1.1/index.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/_format/1.1/index.md b/_format/1.1/index.md
index 5271037ad..36c813c6e 100644
--- a/_format/1.1/index.md
+++ b/_format/1.1/index.md
@@ -302,7 +302,7 @@ 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.
+* `links`: a [links object][links] related to the document as a whole.
* `included`: an array of [resource objects] that are related to the primary
data and/or each other ("included resources").
From 1aa4b2f4dc2fea6937aa0cfbe1eac9f450775621 Mon Sep 17 00:00:00 2001
From: "s.mareychev"
Date: Mon, 7 Oct 2024 16:42:20 +0400
Subject: [PATCH 21/34] Add jsonapi-simple JAVA package to implementations
---
implementations/index.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/implementations/index.md b/implementations/index.md
index 9eb32e247..28bd5a27c 100644
--- a/implementations/index.md
+++ b/implementations/index.md
@@ -298,6 +298,7 @@ includes related resources.
* [jsonapi-rvp](https://github.com/xlate/jsonapi-rvp) utilizes the platform capabilities available in the Jakarta EE environment (formerly Java EE) to expose JPA entities via JSON:API. Features include query and entity validation (using Bean Validators), pagination, use of surrogate keys as record identifiers, support for LEFT outer join conditions, and a simple JavaScript client.
* [JSON:API object converter](https://github.com/MieskeB/json-api-spring-boot) converts normal Java objects to JSON:API standard with the use of annotations in the viewmodels (dtos).
* [Dashjoin Low Code Development Platform](https://github.com/dashjoin/platform) connects to relational, document, and graph databases and makes them available via JSON:API. The platform offers powerful access control mechanisms, a query editor, and graphical layout designers.
+* [jsonapi-simple](https://github.com/devslm/jsonapi-simple) is a simple implementation of the JSON:API specification (only required output fields). This library implements only top-level fields: data, links, errors and meta (using spring boot dependencies) without any relationships, includes and others. It implemented also filtering, sorting and sparse fieldset. [See the documentation](https://devslm.github.io/jsonapi-simple/) for full details.
### Scala
* [scala-jsonapi](https://github.com/scala-jsonapi/scala-jsonapi) A Scala library for producing JSON output (and deserializing JSON input) based on JSON:API specification.
From c563ae3bea1610e56e39f21fab42f0bea047d4c0 Mon Sep 17 00:00:00 2001
From: Jeldrik Hanschke
Date: Tue, 22 Oct 2024 16:40:29 +0200
Subject: [PATCH 22/34] media type parameters should be delimited by an
immediately preceding semicolony
---
_format/1.2/index.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/_format/1.2/index.md b/_format/1.2/index.md
index 17a5a986d..b34ade3ec 100644
--- a/_format/1.2/index.md
+++ b/_format/1.2/index.md
@@ -50,7 +50,7 @@ The JSON:API specification supports two media type parameters: `ext` and
> 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
+`Content-Type: text/html;charset="utf-8"`, the media type is `text/html` and
`charset` is a parameter.
### Extensions
@@ -121,7 +121,7 @@ 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"
+Content-Type: application/vnd.api+json;ext="https://jsonapi.org/ext/version"
// ...
{
@@ -170,7 +170,7 @@ 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"
+Content-Type: application/vnd.api+json;profile="https://example.com/resource-timestamps"
// ...
{
From 353ef57f262c06e3cbd49065739ac6e009ce713c Mon Sep 17 00:00:00 2001
From: Jeldrik Hanschke
Date: Tue, 22 Oct 2024 16:43:27 +0200
Subject: [PATCH 23/34] Backport changes to v1.1
---
_format/1.1/index.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/_format/1.1/index.md b/_format/1.1/index.md
index 36c813c6e..14eaa4f6f 100644
--- a/_format/1.1/index.md
+++ b/_format/1.1/index.md
@@ -49,7 +49,7 @@ The JSON:API specification supports two media type parameters: `ext` and
> 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
+`Content-Type: text/html;charset="utf-8"`, the media type is `text/html` and
`charset` is a parameter.
### Extensions
@@ -120,7 +120,7 @@ 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"
+Content-Type: application/vnd.api+json;ext="https://jsonapi.org/ext/version"
// ...
{
@@ -169,7 +169,7 @@ 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"
+Content-Type: application/vnd.api+json;profile="https://example.com/resource-timestamps"
// ...
{
From ddcec33556a2bca7c93fbcbc823cff5dd42e2b7a Mon Sep 17 00:00:00 2001
From: Jeldrik Hanschke
Date: Thu, 31 Oct 2024 08:43:14 +0100
Subject: [PATCH 24/34] Clarify combination of JSON:API with other standars
JSON:API can and should be combined with other standards. But there has been some confusion about it in the past. E.g. people tried using it for authentication concerns such as login instead of established standards such as OAuth 2.0. Others assumed an API implementing JSON:API cannot support file upload. There seems to be a need clarifying it in the specification to avoid such confusion going forward.
---
_format/1.2/index.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/_format/1.2/index.md b/_format/1.2/index.md
index b34ade3ec..708d42606 100644
--- a/_format/1.2/index.md
+++ b/_format/1.2/index.md
@@ -13,6 +13,8 @@ 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 can and should be combined with additional standards addressing other needs of HTTP APIs such as [OAuth 2.0](https://datatracker.ietf.org/doc/html/rfc6749) for authorization and [Open API](https://spec.openapis.org/oas/latest.html) for documentation. A server supporting JSON:API can support other API standards such as [OData](https://www.odata.org/) or [GraphQL](https://graphql.org/) on the same or other endpoints.
+
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.
From 93d316a8f8d8a189c44e8621205bda7d6acd2d07 Mon Sep 17 00:00:00 2001
From: Jeldrik Hanschke
Date: Thu, 31 Oct 2024 10:01:14 +0100
Subject: [PATCH 25/34] Update _format/1.2/index.md
Co-authored-by: Lode Claassen
---
_format/1.2/index.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/_format/1.2/index.md b/_format/1.2/index.md
index 708d42606..99ee5abf7 100644
--- a/_format/1.2/index.md
+++ b/_format/1.2/index.md
@@ -13,7 +13,7 @@ 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 can and should be combined with additional standards addressing other needs of HTTP APIs such as [OAuth 2.0](https://datatracker.ietf.org/doc/html/rfc6749) for authorization and [Open API](https://spec.openapis.org/oas/latest.html) for documentation. A server supporting JSON:API can support other API standards such as [OData](https://www.odata.org/) or [GraphQL](https://graphql.org/) on the same or other endpoints.
+For additional needs of HTTP APIs, like authentication, documentation, file uploads, and more, JSON:API can and should be combined with additional standards such as [OAuth 2.0](https://datatracker.ietf.org/doc/html/rfc6749) for authorization and [Open API](https://spec.openapis.org/oas/latest.html) for documentation. It is allowed for a server supporting JSON:API to also support other API standards on the same or other endpoints, such as [OData](https://www.odata.org/) or [GraphQL](https://graphql.org/).
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))
From 5f9c8c78f15904c5d794ae485eb0e3c29276b020 Mon Sep 17 00:00:00 2001
From: Jeldrik Hanschke
Date: Tue, 5 Nov 2024 22:13:30 +0100
Subject: [PATCH 26/34] Update _format/1.2/index.md
Co-authored-by: Dan Gebhardt
---
_format/1.2/index.md | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/_format/1.2/index.md b/_format/1.2/index.md
index 99ee5abf7..723639d44 100644
--- a/_format/1.2/index.md
+++ b/_format/1.2/index.md
@@ -13,7 +13,11 @@ 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.
-For additional needs of HTTP APIs, like authentication, documentation, file uploads, and more, JSON:API can and should be combined with additional standards such as [OAuth 2.0](https://datatracker.ietf.org/doc/html/rfc6749) for authorization and [Open API](https://spec.openapis.org/oas/latest.html) for documentation. It is allowed for a server supporting JSON:API to also support other API standards on the same or other endpoints, such as [OData](https://www.odata.org/) or [GraphQL](https://graphql.org/).
+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.
+
+> For additional needs of HTTP APIs, like authentication, documentation, file uploads, and more, JSON:API can and should be combined with additional standards such as [OAuth 2.0](https://datatracker.ietf.org/doc/html/rfc6749) for authorization and [Open API](https://spec.openapis.org/oas/latest.html) for documentation. It is allowed for a server supporting JSON:API to also support other API standards on the same or other endpoints, such as [OData](https://www.odata.org/) or [GraphQL](https://graphql.org/).
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))
From cf1494a86efed01ceece7ee3575291aac993e674 Mon Sep 17 00:00:00 2001
From: Jeldrik Hanschke
Date: Tue, 5 Nov 2024 22:14:46 +0100
Subject: [PATCH 27/34] remove duplicated sentence
---
_format/1.2/index.md | 4 ----
1 file changed, 4 deletions(-)
diff --git a/_format/1.2/index.md b/_format/1.2/index.md
index 723639d44..9c9074980 100644
--- a/_format/1.2/index.md
+++ b/_format/1.2/index.md
@@ -19,10 +19,6 @@ JSON:API requires use of the JSON:API media type
> For additional needs of HTTP APIs, like authentication, documentation, file uploads, and more, JSON:API can and should be combined with additional standards such as [OAuth 2.0](https://datatracker.ietf.org/doc/html/rfc6749) for authorization and [Open API](https://spec.openapis.org/oas/latest.html) for documentation. It is allowed for a server supporting JSON:API to also support other API standards on the same or other endpoints, such as [OData](https://www.odata.org/) or [GraphQL](https://graphql.org/).
-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
From 8138e5e99f6e74076e8155a0ecc9086a42b0f696 Mon Sep 17 00:00:00 2001
From: Jeldrik Hanschke
Date: Tue, 5 Nov 2024 22:15:51 +0100
Subject: [PATCH 28/34] remove accidentally added white spaces
---
_format/1.2/index.md | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/_format/1.2/index.md b/_format/1.2/index.md
index 9c9074980..eba70acaf 100644
--- a/_format/1.2/index.md
+++ b/_format/1.2/index.md
@@ -1,5 +1,4 @@
----
-version: 1.2
+---version: 1.2
status: draft
---
@@ -14,8 +13,8 @@ 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.
+([`application/vnd.api+json`](http://www.iana.org/assignments/media-types/application/vnd.api+json))
+for exchanging data.
> For additional needs of HTTP APIs, like authentication, documentation, file uploads, and more, JSON:API can and should be combined with additional standards such as [OAuth 2.0](https://datatracker.ietf.org/doc/html/rfc6749) for authorization and [Open API](https://spec.openapis.org/oas/latest.html) for documentation. It is allowed for a server supporting JSON:API to also support other API standards on the same or other endpoints, such as [OData](https://www.odata.org/) or [GraphQL](https://graphql.org/).
From 80c8fc6581b0dabdcfae5957733930429d67eaca Mon Sep 17 00:00:00 2001
From: Jeldrik Hanschke
Date: Tue, 5 Nov 2024 22:16:21 +0100
Subject: [PATCH 29/34] fix accidentally removed line break
---
_format/1.2/index.md | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/_format/1.2/index.md b/_format/1.2/index.md
index eba70acaf..1f385eeee 100644
--- a/_format/1.2/index.md
+++ b/_format/1.2/index.md
@@ -1,4 +1,5 @@
----version: 1.2
+---
+version: 1.2
status: draft
---
From d2a8086b760a311223ec1f739ad4a51e0e43e8ba Mon Sep 17 00:00:00 2001
From: Jeldrik Hanschke
Date: Tue, 5 Nov 2024 22:20:34 +0100
Subject: [PATCH 30/34] break lines after 80 chars for consitency
---
_format/1.2/index.md | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/_format/1.2/index.md b/_format/1.2/index.md
index 1f385eeee..8288fffdd 100644
--- a/_format/1.2/index.md
+++ b/_format/1.2/index.md
@@ -17,7 +17,13 @@ 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.
-> For additional needs of HTTP APIs, like authentication, documentation, file uploads, and more, JSON:API can and should be combined with additional standards such as [OAuth 2.0](https://datatracker.ietf.org/doc/html/rfc6749) for authorization and [Open API](https://spec.openapis.org/oas/latest.html) for documentation. It is allowed for a server supporting JSON:API to also support other API standards on the same or other endpoints, such as [OData](https://www.odata.org/) or [GraphQL](https://graphql.org/).
+> For additional needs of HTTP APIs, like authentication, documentation, file
+> uploads, and more, JSON:API can and should be combined with additional
+> standards such as [OAuth 2.0](https://datatracker.ietf.org/doc/html/rfc6749)
+> for authorization and [Open API](https://spec.openapis.org/oas/latest.html)
+> for documentation. It is allowed for a server supporting JSON:API to also
+> support other API standards on the same or other endpoints, such as
+> [OData](https://www.odata.org/) or [GraphQL](https://graphql.org/).
## Semantics
From 77bec41695dbeb6ff3fc58dddf72ad3a5bcd89ba Mon Sep 17 00:00:00 2001
From: Jeldrik Hanschke
Date: Thu, 7 Nov 2024 07:18:07 +0100
Subject: [PATCH 31/34] add example for multiple extensions applied
---
_format/1.2/index.md | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/_format/1.2/index.md b/_format/1.2/index.md
index 8288fffdd..ccb0194ab 100644
--- a/_format/1.2/index.md
+++ b/_format/1.2/index.md
@@ -93,6 +93,13 @@ values of the `ext` and `profile` parameters **MUST** equal a space-separated
> specification requires that parameter values be surrounded by quotation marks
> (U+0022 QUOTATION MARK, "\"").
+In the following example, two extensions and one profile have been applied:
+
+```http
+HTTP/1.1 200 OK
+Content-Type: application/vnd.api+json;ext="https://example.com/version https://example.com/bulk";profile="https://example.com/resource-timestamps"
+```
+
#### Rules for Extensions
An extension **MAY** impose additional processing rules or further restrictions
From 34b97a6237621c435c964f587933142e94c984a0 Mon Sep 17 00:00:00 2001
From: Kurt McKee
Date: Wed, 20 Nov 2024 08:10:39 -0600
Subject: [PATCH 32/34] Fix some typos
---
_format/1.1/index.md | 8 ++++----
_format/1.2/index.md | 8 ++++----
_layouts/page.html | 2 +-
examples/index.md | 4 ++--
implementations/index.md | 2 +-
recommendations/index.md | 2 +-
6 files changed, 13 insertions(+), 13 deletions(-)
diff --git a/_format/1.1/index.md b/_format/1.1/index.md
index 14eaa4f6f..0371bc107 100644
--- a/_format/1.1/index.md
+++ b/_format/1.1/index.md
@@ -2155,10 +2155,10 @@ 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**
+ particular occurrence of the problem. When dereferenced, 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
+ particular error is an instance of. This URI **SHOULD** be dereferenceable 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.
@@ -2210,7 +2210,7 @@ parsing algorithm. The resulting value might not be a string.
> 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),
+use [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.
@@ -2247,7 +2247,7 @@ request as equivalent to one in which the square brackets were percent-encoded.
[profiles]: #profiles
[error details]: #errors
[error object]: #error-objects
-[error objects]: #errror-objects
+[error objects]: #error-objects
[member names]: #document-member-names
[pagination]: #fetching-pagination
[query parameter family]: #query-parameters-families
diff --git a/_format/1.2/index.md b/_format/1.2/index.md
index ccb0194ab..681ad033e 100644
--- a/_format/1.2/index.md
+++ b/_format/1.2/index.md
@@ -2171,10 +2171,10 @@ 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**
+ particular occurrence of the problem. When dereferenced, 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
+ particular error is an instance of. This URI **SHOULD** be dereferenceable 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.
@@ -2226,7 +2226,7 @@ parsing algorithm. The resulting value might not be a string.
> 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),
+use [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.
@@ -2263,7 +2263,7 @@ request as equivalent to one in which the square brackets were percent-encoded.
[profiles]: #profiles
[error details]: #errors
[error object]: #error-objects
-[error objects]: #errror-objects
+[error objects]: #error-objects
[member names]: #document-member-names
[pagination]: #fetching-pagination
[query parameter family]: #query-parameters-families
diff --git a/_layouts/page.html b/_layouts/page.html
index 6555a979c..acd1e512c 100644
--- a/_layouts/page.html
+++ b/_layouts/page.html
@@ -47,7 +47,7 @@ A specification for building APIs in JSON