diff --git a/tools/spectral/ipa/__tests__/createMethodRequestBodyIsRequestSuffixedObject.test.js b/tools/spectral/ipa/__tests__/createMethodRequestBodyIsRequestSuffixedObject.test.js new file mode 100644 index 0000000000..86a1cd5fbc --- /dev/null +++ b/tools/spectral/ipa/__tests__/createMethodRequestBodyIsRequestSuffixedObject.test.js @@ -0,0 +1,221 @@ +import testRule from './__helpers__/testRule'; +import { DiagnosticSeverity } from '@stoplight/types'; + +const componentSchemas = { + schemas: { + SchemaRequest: { + type: 'object', + }, + Schema: { + type: 'object', + }, + }, +}; +testRule('xgen-IPA-106-create-method-request-body-is-request-suffixed-object', [ + { + name: 'valid methods', + document: { + components: componentSchemas, + paths: { + '/resource': { + post: { + requestBody: { + content: { + 'application/vnd.atlas.2023-01-01+json': { + schema: { + $ref: '#/components/schemas/SchemaRequest', + }, + }, + 'application/vnd.atlas.2024-01-01+json': { + schema: { + $ref: '#/components/schemas/SchemaRequest', + }, + }, + }, + }, + }, + }, + '/resource/{id}': { + post: { + requestBody: { + content: { + 'application/vnd.atlas.2023-01-01+json': { + schema: { + $ref: '#/components/schemas/SchemaRequest', + }, + }, + 'application/vnd.atlas.2024-01-01+json': { + schema: { + $ref: '#/components/schemas/SchemaRequest', + }, + }, + }, + }, + }, + }, + '/resource/{id}:customMethod': { + post: { + requestBody: { + content: { + 'application/vnd.atlas.2023-01-01+json': { + schema: { + $ref: '#/components/schemas/Schema', + }, + }, + 'application/vnd.atlas.2024-01-01+json': { + schema: { + $ref: '#/components/schemas/SchemaRequest', + }, + }, + }, + }, + }, + }, + }, + }, + errors: [], + }, + { + name: 'invalid methods', + document: { + components: componentSchemas, + paths: { + '/resource': { + post: { + requestBody: { + content: { + 'application/vnd.atlas.2023-01-01+json': { + schema: { + $ref: '#/components/schemas/Schema', + }, + }, + }, + }, + }, + }, + '/resource2': { + post: { + requestBody: { + content: { + 'application/vnd.atlas.2023-01-01+json': { + schema: { + $ref: '#/components/schemas/Schema', + }, + }, + 'application/vnd.atlas.2024-01-01+json': { + schema: { + $ref: '#/components/schemas/Schema', + }, + }, + }, + }, + }, + }, + '/resource3': { + post: { + requestBody: { + content: { + 'application/vnd.atlas.2023-01-01+json': { + schema: { + type: 'object', + }, + }, + }, + }, + }, + }, + }, + }, + errors: [ + { + code: 'xgen-IPA-106-create-method-request-body-is-request-suffixed-object', + message: 'The response body schema must reference a schema with a Request suffix. http://go/ipa/106', + path: ['paths', '/resource', 'post', 'requestBody', 'content', 'application/vnd.atlas.2023-01-01+json'], + severity: DiagnosticSeverity.Warning, + }, + { + code: 'xgen-IPA-106-create-method-request-body-is-request-suffixed-object', + message: 'The response body schema must reference a schema with a Request suffix. http://go/ipa/106', + path: ['paths', '/resource2', 'post', 'requestBody', 'content', 'application/vnd.atlas.2023-01-01+json'], + severity: DiagnosticSeverity.Warning, + }, + { + code: 'xgen-IPA-106-create-method-request-body-is-request-suffixed-object', + message: 'The response body schema must reference a schema with a Request suffix. http://go/ipa/106', + path: ['paths', '/resource2', 'post', 'requestBody', 'content', 'application/vnd.atlas.2024-01-01+json'], + severity: DiagnosticSeverity.Warning, + }, + { + code: 'xgen-IPA-106-create-method-request-body-is-request-suffixed-object', + message: 'The response body schema is defined inline and must reference a predefined schema. http://go/ipa/106', + path: ['paths', '/resource3', 'post', 'requestBody', 'content', 'application/vnd.atlas.2023-01-01+json'], + severity: DiagnosticSeverity.Warning, + }, + ], + }, + { + name: 'invalid method with exception', + document: { + components: componentSchemas, + paths: { + '/resource': { + post: { + requestBody: { + content: { + 'application/vnd.atlas.2023-01-01+json': { + schema: { + $ref: '#/components/schemas/Schema', + }, + 'x-xgen-IPA-exception': { + 'xgen-IPA-106-create-method-request-body-is-request-suffixed-object': 'reason', + }, + }, + }, + }, + }, + }, + '/resource2': { + post: { + requestBody: { + content: { + 'application/vnd.atlas.2023-01-01+json': { + schema: { + $ref: '#/components/schemas/Schema', + }, + 'x-xgen-IPA-exception': { + 'xgen-IPA-106-create-method-request-body-is-request-suffixed-object': 'reason', + }, + }, + 'application/vnd.atlas.2024-01-01+json': { + schema: { + $ref: '#/components/schemas/Schema', + }, + 'x-xgen-IPA-exception': { + 'xgen-IPA-106-create-method-request-body-is-request-suffixed-object': 'reason', + }, + }, + }, + }, + }, + }, + '/resource3': { + post: { + requestBody: { + content: { + 'application/vnd.atlas.2023-01-01+json': { + schema: { + type: 'object', + }, + 'x-xgen-IPA-exception': { + 'xgen-IPA-106-create-method-request-body-is-request-suffixed-object': 'reason', + }, + }, + }, + }, + }, + }, + }, + }, + errors: [], + }, +]); diff --git a/tools/spectral/ipa/ipa-spectral.yaml b/tools/spectral/ipa/ipa-spectral.yaml index 4b1b04fd73..d9ee29a7e6 100644 --- a/tools/spectral/ipa/ipa-spectral.yaml +++ b/tools/spectral/ipa/ipa-spectral.yaml @@ -5,6 +5,7 @@ extends: - ./rulesets/IPA-109.yaml - ./rulesets/IPA-113.yaml - ./rulesets/IPA-123.yaml + - ./rulesets/IPA-106.yaml overrides: - files: diff --git a/tools/spectral/ipa/rulesets/IPA-106.yaml b/tools/spectral/ipa/rulesets/IPA-106.yaml new file mode 100644 index 0000000000..b8e695bc50 --- /dev/null +++ b/tools/spectral/ipa/rulesets/IPA-106.yaml @@ -0,0 +1,15 @@ +# IPA-104: Create +# http://go/ipa/106 + +functions: + - createMethodRequestBodyIsRequestSuffixedObject + +rules: + xgen-IPA-106-create-method-request-body-is-request-suffixed-object: + description: 'The Create method request should be a Request suffixed object. http://go/ipa/106' + message: '{{error}} http://go/ipa/106' + severity: warn + given: '$.paths[*].post.requestBody.content' + then: + field: '@key' + function: 'createMethodRequestBodyIsRequestSuffixedObject' diff --git a/tools/spectral/ipa/rulesets/README.md b/tools/spectral/ipa/rulesets/README.md index db001ccf4f..43f9ae370a 100644 --- a/tools/spectral/ipa/rulesets/README.md +++ b/tools/spectral/ipa/rulesets/README.md @@ -34,6 +34,14 @@ For rule definitions, see [IPA-104.yaml](https://github.com/mongodb/openapi/blob | xgen-IPA-104-get-method-returns-single-resource | The purpose of the get method is to return data from a single resource. http://go/ipa/104 | warn | | xgen-IPA-104-get-method-response-code-is-200 | The Get method must return a 200 OK response. http://go/ipa/104 | warn | +### IPA-106 + +For rule definitions, see [IPA-106.yaml](https://github.com/mongodb/openapi/blob/main/tools/spectral/ipa/rulesets/IPA-106.yaml). + +| Rule Name | Description | Severity | +| ------------------------------------------------------------------ | -------------------------------------------------------------------------------- | -------- | +| xgen-IPA-106-create-method-request-body-is-request-suffixed-object | The Create method request should be a Request suffixed object. http://go/ipa/106 | warn | + ### IPA-109 For rule definitions, see [IPA-109.yaml](https://github.com/mongodb/openapi/blob/main/tools/spectral/ipa/rulesets/IPA-109.yaml). diff --git a/tools/spectral/ipa/rulesets/functions/createMethodRequestBodyIsRequestSuffixedObject.js b/tools/spectral/ipa/rulesets/functions/createMethodRequestBodyIsRequestSuffixedObject.js new file mode 100644 index 0000000000..4f3944a4c9 --- /dev/null +++ b/tools/spectral/ipa/rulesets/functions/createMethodRequestBodyIsRequestSuffixedObject.js @@ -0,0 +1,37 @@ +import { hasException } from './utils/exceptions.js'; +import { collectAdoption, collectAndReturnViolation, collectException } from './utils/collectionUtils.js'; +import { isCustomMethod } from './utils/resourceEvaluation.js'; +import { resolveObject } from './utils/componentUtils.js'; + +const RULE_NAME = 'xgen-IPA-106-create-method-request-body-is-request-suffixed-object'; +const ERROR_MESSAGE_SCHEMA_NAME = 'The response body schema must reference a schema with a Request suffix.'; +const ERROR_MESSAGE_SCHEMA_REF = 'The response body schema is defined inline and must reference a predefined schema.'; + +export default (input, _, { path, documentInventory }) => { + const oas = documentInventory.unresolved; + const resourcePath = path[1]; + + if (isCustomMethod(resourcePath)) { + return; + } + + const contentPerMediaType = resolveObject(oas, path); + + if (hasException(contentPerMediaType, RULE_NAME)) { + collectException(contentPerMediaType, RULE_NAME, path); + return; + } + + if (contentPerMediaType.schema) { + const schema = contentPerMediaType.schema; + if (!schema.$ref) { + return collectAndReturnViolation(path, RULE_NAME, ERROR_MESSAGE_SCHEMA_REF); + } + + if (!schema.$ref.endsWith('Request')) { + return collectAndReturnViolation(path, RULE_NAME, ERROR_MESSAGE_SCHEMA_NAME); + } + } + + collectAdoption(path, RULE_NAME); +};