Skip to content

Commit

Permalink
Feat: Add Spectral rules for JSON:API Document Structure - Resource O…
Browse files Browse the repository at this point in the history
…bjects

This commit introduces a new set of Spectral rules specifically tailored for validating JSON:API Resource Objects within OpenAPI documents. Key highlights include:

- Creation of Spectral rules to ensure compliance with JSON:API standards, focusing on Resource Objects structures, object properties and proper types.
- Resource Objects can be provided as a single object or an array of objects. These rule validations tailor for both scenarios and ensures it is following the JSON:API Specifications v1.0.
- `id` member has an exception for when a `Resource Object` is newly created. The rules are tailored for this specific scenario, which is typically a HTPP POST, otherwise an `id` member is required.

These additions significantly improve our capability to automatically validate and ensure the consistency of API responses with the JSON:API standard.
  • Loading branch information
ezenity committed Dec 12, 2023
1 parent 8cc8c21 commit e7fb080
Showing 1 changed file with 342 additions and 0 deletions.
342 changes: 342 additions & 0 deletions rules/jsonapi-document-structure-resource-objects.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,342 @@
// Document Structure - Resource Objects - https://jsonapi.org/format/#document-resource-objects

// All rules in the file MUST have corresponding tests

import { enumeration, length, truthy } from '@stoplight/spectral-functions';

export default {
documentationUrl: 'https://jsonapi.org/format/#document-resource-objects',
rules: {

/**
* Ensures that a `Resource Object` is of type `object` or `array`
*/
'document-structure-resource-objects-type': {
description: 'A `Resource Object` MUST be of type `object` or `array`',
message: `{{path}} - {{description}}`,
severity: 'error',
// given: "$..[?(@.get || @.delete || @.put || @.patch || @.post)]..[?(@.responses || @.requestBody)]..content['application/vnd.api+json'].schema.properties.data.properties",
given: "$..[?(@property == 'get' || @property == 'delete' || @property == 'put' || @property == 'patch' || @property == 'post')]..[?(@property == 'responses' || @property == 'requestBody')]..content['application/vnd.api+json'].schema.properties.data",
then: {
field: 'type',
function: enumeration,
functionOptions: {
values: [
'object',
'array'
]
}
}
},

/**
* Checks the presence of 'id' and 'type' within a single `Resource Object`.
*/
'document-structure-resource-objects-single-structure': {
description: 'A single `Resource Object` MUST only contain the specified members',
message: `{{path}} - {{description}}`,
severity: 'error',
given: "$..[?(@property == 'get' || @property == 'delete' || @property == 'put' || @property == 'patch' || @property == 'post')]..[?(@property == 'responses' || @property == 'requestBody')]..content['application/vnd.api+json'].schema.properties.data.properties",
then: {
field: '@key',
function: enumeration,
functionOptions: {
values: [
'id',
'type',
'attributes',
'relationships',
'links',
'meta'
]
}
}
},

/**
* Ensures that the single `Resource Object` contains an appropriate number of members.
* This rule checks that a single `Resource Object` has a minmum of two members and a
* maximum of 6 members. This will even include for when an `id` member is not required
* Exception because when creating a new resource, typically the `attributes` and/or
* `relationships` objects are provided to supply the data needed to create the resource.
*/
'document-structure-resource-objects-single-structure-length': {
description: 'A single `Resource Object` MAY contain between two or six specified members',
message: `{{path}} - {{description}}`,
severity: 'error',
given: "$..[?(@property == 'get' || @property == 'delete' || @property == 'put' || @property == 'patch' || @property == 'post')]..[?(@property == 'responses' || @property == 'requestBody')]..content['application/vnd.api+json'].schema.properties.data",
then: {
field: 'properties',
function: length,
functionOptions: {
min: 2,
max: 6
}
}
},

/**
* Ensures that a single `Resource Object` has a `type` member.
*/
'document-structure-resource-objects-single-type-required': {
description: 'A single `Resource Object` MUST contain `type` member',
message: `{{path}} - {{description}}`,
severity: 'error',
given: "$..[?(@property == 'get' || @property == 'delete' || @property == 'put' || @property == 'patch' || @property == 'post')]..[?(@property == 'responses' || @property == 'requestBody')]..content['application/vnd.api+json'].schema.properties.data.properties",
then: {
field: 'type',
function: truthy
}
},

/**
* Ensures that a single `Resource Object` has a `id` member for the specific
* targeted HTTP methods: GET, DELETE, PUT, PATCH.
*
* HTTP method 'POST' will not have an `id` member. When a client is sending a request
* to create a new resource on the server, the `id` of the resource is not yet known, as
* it is usually generated by the server. In such cases, the `Resource Object` in the
* request body will not include an `id` member. This is common in `POST` requests where
* a new resource is being added.
*/
'document-structure-resource-objects-single-id-required': {
description: 'A single `Resource Object` MUST contain `id` member for HTTP methods: GET, DELETE, PUT, PATCH',
message: `{{path}} - {{description}}`,
severity: 'error',
given: "$..[?(@property == 'get' || @property == 'delete' || @property == 'put' || @property == 'patch')]..[?(@property == 'responses' || @property == 'requestBody')]..content['application/vnd.api+json'].schema.properties.data.properties",
then: {
field: 'id',
function: truthy
}
},

/**
* Verifies that the `id` member in each single `Resource Object` is of type `string`.
* This rule is crucial for maintaining consistency in `Resource Object` indentifiers.
*/
'document-structure-resource-objects-single-id-type': {
description: '`id` member in a single `Resource Object` MUST be of type `string`',
message: `{{path}} - {{description}}`,
severity: 'error',
given: "$..[?(@property == 'get' || @property == 'delete' || @property == 'put' || @property == 'patch' || @property == 'post')]..[?(@property == 'responses' || @property == 'requestBody')]..content['application/vnd.api+json'].schema.properties.data",
then: {
field: 'type',
function: enumeration,
functionOptions: {
values: [
'string'
]
}
}
},

/**
* Verifies that the `type` member in each single `Resource Object` is of type `string`.
* This rule is to assist with ensuring a single `Resource Object` share a common
* attributes and relationships.
*/
'document-structure-resource-objects-single-type-type': {
description: '`type` member in a single `Resource Object` MUST be of type `string`',
message: `{{path}} - {{description}}`,
severity: 'error',
given: "$..[?(@property == 'get' || @property == 'delete' || @property == 'put' || @property == 'patch' || @property == 'post')]..[?(@property == 'responses' || @property == 'requestBody')]..content['application/vnd.api+json'].schema.properties.data.properties.type",
then: {
field: 'type',
function: enumeration,
functionOptions: {
values: [
'string'
]
}
}
},

/**
* Verifies that the `attributes` member in each single `Resource Object` is of type `object`.
* This rule is crucial for maintaining consistency in representing some of the resource's data.
*/
'document-structure-resource-objects-single-attributes-type': {
description: '`attributes` member in a single `Resource Object` MUST be of type `object`',
message: `{{path}} - {{description}}`,
severity: 'error',
given: "$..[?(@property == 'get' || @property == 'delete' || @property == 'put' || @property == 'patch' || @property == 'post')]..[?(@property == 'responses' || @property == 'requestBody')]..content['application/vnd.api+json'].schema.properties.data.properties.attributes",
then: {
field: 'type',
function: enumeration,
functionOptions: {
values: [
'object'
]
}
}
},

/**
* Checks the presence of 'id' and 'type' within an array of resource objects.
*/
'document-structure-resource-objects-array-required-fields': {
description: 'An array of `Resource Objects` MUST contain `id` and `type` members',
message: `{{path}} - {{description}}`,
severity: 'error',
given: "$..[?(@property == 'get' || @property == 'delete' || @property == 'put' || @property == 'patch' || @property == 'post')]..[?(@property == 'responses' || @property == 'requestBody')]..content['application/vnd.api+json'].schema.properties.data",
then: {
field: '@key',
function: enumeration,
functionOptions: {
values: [
'id',
'type',
'attributes',
'relationships',
'links',
'meta'
]
}
}
},

/**
* Checks the presence of 'id' and 'type' within an array of `Resource Objects`.
*/
'document-structure-resource-objects-array-structure': {
description: 'An array of `Resource Objects` MUST only contain the specified members',
message: `{{path}} - {{description}}`,
severity: 'error',
given: "$..[?(@property == 'get' || @property == 'delete' || @property == 'put' || @property == 'patch' || @property == 'post')]..[?(@property == 'responses' || @property == 'requestBody')]..content['application/vnd.api+json'].schema.properties.data.items.properties",
then: {
field: '@key',
function: enumeration,
functionOptions: {
values: [
'id',
'type',
'attributes',
'relationships',
'links',
'meta'
]
}
}
},

/**
* Ensures that the an array of `Resource Objects` contains an appropriate number of members.
* This rule checks that an array of `Resource Objects` has a minmum of two members and a
* maximum of 6 members. This will even include for when an `id` member is not required
* Exception because when creating a new resource, typically the `attributes` and/or
* `relationships` objects are provided to supply the data needed to create the resource.
*/
'document-structure-resource-objects-array-structure-length': {
description: 'An array of `Resource Objects` MAY contain between two or six specified members',
message: `{{path}} - {{description}}`,
severity: 'error',
given: "$..[?(@property == 'get' || @property == 'delete' || @property == 'put' || @property == 'patch' || @property == 'post')]..[?(@property == 'responses' || @property == 'requestBody')]..content['application/vnd.api+json'].schema.properties.data.items",
then: {
field: 'properties',
function: length,
functionOptions: {
min: 2,
max: 6
}
}
},

/**
* Ensures that an array of `Resource Objects` has a `type` member.
*/
'document-structure-resource-objects-array-type-required': {
description: 'An array of `Resource Objects` MUST contain `type` member',
message: `{{path}} - {{description}}`,
severity: 'error',
given: "$..[?(@property == 'get' || @property == 'delete' || @property == 'put' || @property == 'patch' || @property == 'post')]..[?(@property == 'responses' || @property == 'requestBody')]..content['application/vnd.api+json'].schema.properties.data.items.properties",
then: {
field: 'type',
function: truthy
}
},

/**
* Ensures that an array of `Resource Objects` has a `id` member for the specific
* targeted HTTP methods: GET, DELETE, PUT, PATCH.
*
* HTTP method 'POST' will not have an `id` member. When a client is sending a request
* to create a new resource on the server, the `id` of the resource is not yet known, as
* it is usually generated by the server. In such cases, the `Resource Object` in the
* request body will not include an `id` member. This is common in `POST` requests where
* a new resource is being added.
*/
'document-structure-resource-objects-array-id-required': {
description: 'An array of `Resource Objects` MUST contain `id` member for HTTP methods: GET, DELETE, PUT, PATCH',
message: `{{path}} - {{description}}`,
severity: 'error',
given: "$..[?(@property == 'get' || @property == 'delete' || @property == 'put' || @property == 'patch')]..[?(@property == 'responses' || @property == 'requestBody')]..content['application/vnd.api+json'].schema.properties.data.items.properties",
then: {
field: 'id',
function: truthy
}
},

/**
* Verifies that the `id` member in an array of `Resource Objects` is of type `string`.
* This rule is crucial for maintaining consistency in `Resource Object` indentifiers.
*/
'document-structure-resource-objects-array-id-type': {
description: '`id` member in an array of `Resource Objects` MUST be of type `string`',
message: `{{path}} - {{description}}`,
severity: 'error',
given: "$..[?(@property == 'get' || @property == 'delete' || @property == 'put' || @property == 'patch' || @property == 'post')]..[?(@property == 'responses' || @property == 'requestBody')]..content['application/vnd.api+json'].schema.properties.data.items.properties.id",
then: {
field: 'type',
function: enumeration,
functionOptions: {
values: [
'string'
]
}
}
},

/**
* Verifies that the `type` member in an array of `Resource Objects` is of type `string`.
* This rule is to assist with ensuring aa array of `Resource Objects` share a common
* attributes and relationships.
*/
'document-structure-resource-objects-array-type-type': {
description: '`type` member in an array of `Resource Objects` MUST be of type `string`',
message: `{{path}} - {{description}}`,
severity: 'error',
given: "$..[?(@property == 'get' || @property == 'delete' || @property == 'put' || @property == 'patch' || @property == 'post')]..[?(@property == 'responses' || @property == 'requestBody')]..content['application/vnd.api+json'].schema.properties.data.items.properties.type",
then: {
field: 'type',
function: enumeration,
functionOptions: {
values: [
'string'
]
}
}
},

/**
* Verifies that the `attributes` member in an array of `Resource Objects` is of type `object`.
* This rule is crucial for maintaining consistency in representing some of the resource's data.
*/
'document-structure-resource-objects-array-attributes-type': {
description: '`attributes` member in an array of `Resource Objects` MUST be of type `object`',
message: `{{path}} - {{description}}`,
severity: 'error',
given: "$..[?(@property == 'get' || @property == 'delete' || @property == 'put' || @property == 'patch' || @property == 'post')]..[?(@property == 'responses' || @property == 'requestBody')]..content['application/vnd.api+json'].schema.properties.data.items.properties.attributes",
then: {
field: 'type',
function: enumeration,
functionOptions: {
values: [
'object'
]
}
}
}


}
};

0 comments on commit e7fb080

Please sign in to comment.