diff --git a/tools/spectral/CONTRIBUTING.md b/tools/spectral/CONTRIBUTING.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tools/spectral/ipa/__tests__/deleteMethodResponseShouldNotHaveSchema.test.js b/tools/spectral/ipa/__tests__/deleteMethodResponseShouldNotHaveSchema.test.js new file mode 100644 index 0000000000..877d151c59 --- /dev/null +++ b/tools/spectral/ipa/__tests__/deleteMethodResponseShouldNotHaveSchema.test.js @@ -0,0 +1,98 @@ +import testRule from './__helpers__/testRule'; +import { DiagnosticSeverity } from '@stoplight/types'; + +testRule('xgen-IPA-108-delete-response-should-be-empty', [ + { + name: 'valid DELETE with void 204', + document: { + paths: { + '/resource/{id}': { + delete: { + responses: { + 204: {}, + }, + }, + }, + }, + }, + errors: [], + }, + { + name: 'valid DELETE with void 204 versioned', + document: { + paths: { + '/resource/{id}': { + delete: { + responses: { + 204: { + description: 'No Content', + content: { + 'application/vnd.atlas.2023-01-01+json': { + 'x-xgen-version': '2023-01-01', + }, + 'application/vnd.atlas.2023-03-01+json': { + 'x-xgen-version': '2023-01-01', + }, + }, + }, + }, + }, + }, + }, + }, + errors: [], + }, + { + name: 'invalid DELETE with non-void 204', + document: { + paths: { + '/resource/{id}': { + delete: { + responses: { + 204: { + content: { + 'application/vnd.atlas.2023-01-01+json': { + schema: { type: 'object' }, + }, + }, + }, + }, + }, + }, + }, + }, + errors: [ + { + code: 'xgen-IPA-108-delete-response-should-be-empty', + message: + 'Error found for application/vnd.atlas.2023-01-01+json: DELETE method should return an empty response. The response should not have a schema property. http://go/ipa/108', + path: ['paths', '/resource/{id}', 'delete', 'responses', '204'], + severity: DiagnosticSeverity.Warning, + }, + ], + }, + { + name: 'valid with exception', + document: { + paths: { + '/resource/{id}': { + delete: { + responses: { + 204: { + 'application/vnd.atlas.2023-01-01+json': { + 'x-xgen-IPA-exception': { + 'xgen-IPA-108-delete-response-should-be-empty': 'Legacy API', + }, + content: { + schema: { type: 'object' }, + }, + }, + }, + }, + }, + }, + }, + }, + errors: [], + }, +]); diff --git a/tools/spectral/ipa/ipa-spectral.yaml b/tools/spectral/ipa/ipa-spectral.yaml index d9ee29a7e6..0693a5a88e 100644 --- a/tools/spectral/ipa/ipa-spectral.yaml +++ b/tools/spectral/ipa/ipa-spectral.yaml @@ -6,6 +6,7 @@ extends: - ./rulesets/IPA-113.yaml - ./rulesets/IPA-123.yaml - ./rulesets/IPA-106.yaml + - ./rulesets/IPA-108.yaml overrides: - files: diff --git a/tools/spectral/ipa/rulesets/IPA-108.yaml b/tools/spectral/ipa/rulesets/IPA-108.yaml new file mode 100644 index 0000000000..7300f624b5 --- /dev/null +++ b/tools/spectral/ipa/rulesets/IPA-108.yaml @@ -0,0 +1,14 @@ +# IPA-108: Delete +# http://go/ipa/108 + +rules: + xgen-IPA-108-delete-response-should-be-empty: + description: Delete method response should not have schema reference to object. http://go/ipa/108 + message: '{{error}} http://go/ipa/108' + severity: warn + given: $.paths[*].delete.responses[204] + then: + function: deleteMethodResponseShouldNotHaveSchema + +functions: + - deleteMethodResponseShouldNotHaveSchema diff --git a/tools/spectral/ipa/rulesets/README.md b/tools/spectral/ipa/rulesets/README.md index 43f9ae370a..05df066027 100644 --- a/tools/spectral/ipa/rulesets/README.md +++ b/tools/spectral/ipa/rulesets/README.md @@ -42,6 +42,14 @@ For rule definitions, see [IPA-106.yaml](https://github.com/mongodb/openapi/blob | ------------------------------------------------------------------ | -------------------------------------------------------------------------------- | -------- | | 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-108 + +For rule definitions, see [IPA-108.yaml](https://github.com/mongodb/openapi/blob/main/tools/spectral/ipa/rulesets/IPA-108.yaml). + +| Rule Name | Description | Severity | +| -------------------------------------------- | ------------------------------------------------------------------------------------ | -------- | +| xgen-IPA-108-delete-response-should-be-empty | Delete method response should not have schema reference to object. http://go/ipa/108 | 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/deleteMethodResponseShouldNotHaveSchema.js b/tools/spectral/ipa/rulesets/functions/deleteMethodResponseShouldNotHaveSchema.js new file mode 100644 index 0000000000..cc1dcbbe3a --- /dev/null +++ b/tools/spectral/ipa/rulesets/functions/deleteMethodResponseShouldNotHaveSchema.js @@ -0,0 +1,58 @@ +import { hasException } from './utils/exceptions.js'; +import { + collectAdoption, + collectAndReturnViolation, + collectException, + handleInternalError, +} from './utils/collectionUtils.js'; + +const RULE_NAME = 'xgen-IPA-108-delete-response-should-be-empty'; +const ERROR_MESSAGE = 'DELETE method should return an empty response. The response should not have a schema property.'; + +/** + * Delete method should return an empty response + * @param {object} input - The delete operation object + * @param {object} _ - Unused + * @param {object} context - The context object containing the path + */ +export default (input, _, { path }) => { + // 1. Handle exception on OpenAPI schema + if (hasException(input, RULE_NAME)) { + collectException(input, RULE_NAME, path); + return; + } + + // 2. Validation + const errors = checkViolations(input, path); + if (errors.length > 0) { + return collectAndReturnViolation(path, RULE_NAME, errors); + } + + collectAdoption(path, RULE_NAME); +}; + +/** + * Check if the operation has validation issues + * @param {object} input - The object to vefify + * @param {object} jsonPathArray - The jsonPathArray covering location in the OpenAPI schema + * @return {Array} - errors array () + */ +function checkViolations(input, jsonPathArray) { + const errors = []; + try { + const successResponse = input; + if (successResponse.content) { + for (const contentType of Object.keys(successResponse.content)) { + if (successResponse.content[contentType] && successResponse.content[contentType].schema) { + errors.push({ + path: jsonPathArray, + message: `Error found for ${contentType}: ${ERROR_MESSAGE}`, + }); + } + } + } + } catch (e) { + handleInternalError(RULE_NAME, jsonPathArray, e); + } + return errors; +} diff --git a/tools/spectral/ipa/rulesets/functions/utils/collectionUtils.js b/tools/spectral/ipa/rulesets/functions/utils/collectionUtils.js index 653c632fc6..b02bef5ee0 100644 --- a/tools/spectral/ipa/rulesets/functions/utils/collectionUtils.js +++ b/tools/spectral/ipa/rulesets/functions/utils/collectionUtils.js @@ -46,3 +46,17 @@ export function collectException(object, ruleName, path) { collector.add(EntryType.EXCEPTION, path, ruleName, exceptionReason); } } + +/** + * Creates internal rule error entry for the collector in order to not fail validation process. + * @param {Array} jsonPathArray - The JSON path for the object where the rule exception occurred. + * @param {string} ruleName - The name of the rule that was adopted. + */ +export function handleInternalError(ruleName, jsonPathArray, error) { + return [ + { + path: jsonPathArray, + message: `${ruleName} Internal Rule Error: ${error} Please report issue in https://github.com/mongodb/openapi/issues`, + }, + ]; +}