diff --git a/tools/spectral/ipa/__tests__/IPA005ExceptionExtensionFormat.test.js b/tools/spectral/ipa/__tests__/IPA005ExceptionExtensionFormat.test.js index 9b2dd5c9c8..fda9121389 100644 --- a/tools/spectral/ipa/__tests__/IPA005ExceptionExtensionFormat.test.js +++ b/tools/spectral/ipa/__tests__/IPA005ExceptionExtensionFormat.test.js @@ -8,13 +8,19 @@ testRule('xgen-IPA-005-exception-extension-format', [ paths: { '/path': { 'x-xgen-IPA-exception': { - 'xgen-IPA-100-rule-name': 'Exception', + 'xgen-IPA-100-rule-name': 'Exception.', + }, + }, + '/path-short': { + 'x-xgen-IPA-exception': { + 'xgen-IPA-100': 'Exception.', }, }, '/nested': { post: { 'x-xgen-IPA-exception': { - 'xgen-IPA-100-rule-name': 'Exception', + 'xgen-IPA-100-rule-name': 'Exception.', + 'xgen-IPA-005': 'Short format exception.', }, }, }, @@ -46,31 +52,108 @@ testRule('xgen-IPA-005-exception-extension-format', [ 'xgen-IPA-100-rule-name': {}, }, }, + '/path5': { + 'x-xgen-IPA-exception': { + 'xgen-IPA-1001-rule-name': {}, + }, + }, + '/path6': { + 'x-xgen-IPA-exception': { + 'xgen-IPA-1001-rule_name': {}, + }, + }, + '/path7': { + 'x-xgen-IPA-exception': { + 'xgen-IPA_100_rule-name': {}, + }, + }, + '/path8': { + 'x-xgen-IPA-exception': { + 'xgen-IPA-100-': 'Exception.', + }, + }, + '/path9': { + 'x-xgen-IPA-exception': { + 'xgen-IPA-5': 'Exception.', + }, + }, + '/path10': { + 'x-xgen-IPA-exception': { + 'xgen-IPA-100': 'exception without uppercase.', + }, + }, + '/path11': { + 'x-xgen-IPA-exception': { + 'xgen-IPA-100': 'Exception without period', + }, + }, }, }, errors: [ { code: 'xgen-IPA-005-exception-extension-format', - message: 'IPA exceptions must have a valid rule name and a reason.', + message: 'IPA exceptions must have a valid key following xgen-IPA-XXX or xgen-IPA-XXX-{rule-name} format.', path: ['paths', '/path1', 'x-xgen-IPA-exception'], severity: DiagnosticSeverity.Error, }, { code: 'xgen-IPA-005-exception-extension-format', - message: 'IPA exceptions must have a valid rule name and a reason.', - path: ['paths', '/path2', 'x-xgen-IPA-exception', 'xgen-IPA-100-rule-name'], + message: 'IPA exceptions must have a non-empty reason that starts with uppercase and ends with a full stop.', + path: ['paths', '/path2', 'x-xgen-IPA-exception'], + severity: DiagnosticSeverity.Error, + }, + { + code: 'xgen-IPA-005-exception-extension-format', + message: 'IPA exceptions must have a valid key following xgen-IPA-XXX or xgen-IPA-XXX-{rule-name} format.', + path: ['paths', '/path3', 'x-xgen-IPA-exception'], + severity: DiagnosticSeverity.Error, + }, + { + code: 'xgen-IPA-005-exception-extension-format', + message: 'IPA exceptions must have a non-empty reason that starts with uppercase and ends with a full stop.', + path: ['paths', '/path4', 'x-xgen-IPA-exception'], + severity: DiagnosticSeverity.Error, + }, + { + code: 'xgen-IPA-005-exception-extension-format', + message: 'IPA exceptions must have a valid key following xgen-IPA-XXX or xgen-IPA-XXX-{rule-name} format.', + path: ['paths', '/path5', 'x-xgen-IPA-exception'], + severity: DiagnosticSeverity.Error, + }, + { + code: 'xgen-IPA-005-exception-extension-format', + message: 'IPA exceptions must have a valid key following xgen-IPA-XXX or xgen-IPA-XXX-{rule-name} format.', + path: ['paths', '/path6', 'x-xgen-IPA-exception'], + severity: DiagnosticSeverity.Error, + }, + { + code: 'xgen-IPA-005-exception-extension-format', + message: 'IPA exceptions must have a valid key following xgen-IPA-XXX or xgen-IPA-XXX-{rule-name} format.', + path: ['paths', '/path7', 'x-xgen-IPA-exception'], + severity: DiagnosticSeverity.Error, + }, + { + code: 'xgen-IPA-005-exception-extension-format', + message: 'IPA exceptions must have a valid key following xgen-IPA-XXX or xgen-IPA-XXX-{rule-name} format.', + path: ['paths', '/path8', 'x-xgen-IPA-exception'], + severity: DiagnosticSeverity.Error, + }, + { + code: 'xgen-IPA-005-exception-extension-format', + message: 'IPA exceptions must have a valid key following xgen-IPA-XXX or xgen-IPA-XXX-{rule-name} format.', + path: ['paths', '/path9', 'x-xgen-IPA-exception'], severity: DiagnosticSeverity.Error, }, { code: 'xgen-IPA-005-exception-extension-format', - message: 'IPA exceptions must have a valid rule name and a reason.', - path: ['paths', '/path3', 'x-xgen-IPA-exception', 'invalid-rule-name'], + message: 'IPA exceptions must have a non-empty reason that starts with uppercase and ends with a full stop.', + path: ['paths', '/path10', 'x-xgen-IPA-exception'], severity: DiagnosticSeverity.Error, }, { code: 'xgen-IPA-005-exception-extension-format', - message: 'IPA exceptions must have a valid rule name and a reason.', - path: ['paths', '/path4', 'x-xgen-IPA-exception', 'xgen-IPA-100-rule-name'], + message: 'IPA exceptions must have a non-empty reason that starts with uppercase and ends with a full stop.', + path: ['paths', '/path11', 'x-xgen-IPA-exception'], severity: DiagnosticSeverity.Error, }, ], diff --git a/tools/spectral/ipa/ipa-spectral.yaml b/tools/spectral/ipa/ipa-spectral.yaml index a46dedf224..2bad9e8f9e 100644 --- a/tools/spectral/ipa/ipa-spectral.yaml +++ b/tools/spectral/ipa/ipa-spectral.yaml @@ -214,3 +214,7 @@ overrides: - '**#/paths/~1api~1atlas~1v2~1groups~1%7BgroupId%7D~1customDBRoles~1roles~1%7BroleName%7D' rules: xgen-IPA-102-collection-identifier-camelCase: 'off' + - files: + - '**' # Matches all files + rules: + xgen-IPA-005-exception-extension-format: 'off' diff --git a/tools/spectral/ipa/rulesets/IPA-005.yaml b/tools/spectral/ipa/rulesets/IPA-005.yaml index 98eda6bf77..dbe38f99e4 100644 --- a/tools/spectral/ipa/rulesets/IPA-005.yaml +++ b/tools/spectral/ipa/rulesets/IPA-005.yaml @@ -12,7 +12,8 @@ rules: ##### Implementation details Rule checks for the following conditions: - Exception rule names must start with 'xgen-IPA-' prefix - - Each exception must include a non-empty reason as a string + - Exception rule names can be either short format (xgen-IPA-XXX) or full format (xgen-IPA-XXX-rule-name) + - Each exception must include a non-empty reason as a string that starts with uppercase and ends with a full stop - This rule itself does not allow exceptions message: '{{error}} https://mdb.link/mongodb-atlas-openapi-validation#xgen-IPA-005-exception-extension-format' severity: error diff --git a/tools/spectral/ipa/rulesets/README.md b/tools/spectral/ipa/rulesets/README.md index fc75253fb3..eea19fa3dc 100644 --- a/tools/spectral/ipa/rulesets/README.md +++ b/tools/spectral/ipa/rulesets/README.md @@ -20,7 +20,8 @@ IPA exception extensions must follow the correct format. ##### Implementation details Rule checks for the following conditions: - Exception rule names must start with 'xgen-IPA-' prefix - - Each exception must include a non-empty reason as a string + - Exception rule names can be either short format (xgen-IPA-XXX) or full format (xgen-IPA-XXX-rule-name) + - Each exception must include a non-empty reason as a string that starts with uppercase and ends with a full stop - This rule itself does not allow exceptions diff --git a/tools/spectral/ipa/rulesets/functions/IPA005ExceptionExtensionFormat.js b/tools/spectral/ipa/rulesets/functions/IPA005ExceptionExtensionFormat.js index ac7c64377f..0d320ca630 100644 --- a/tools/spectral/ipa/rulesets/functions/IPA005ExceptionExtensionFormat.js +++ b/tools/spectral/ipa/rulesets/functions/IPA005ExceptionExtensionFormat.js @@ -1,7 +1,10 @@ import { evaluateAndCollectAdoptionStatusWithoutExceptions, handleInternalError } from './utils/collectionUtils.js'; -const ERROR_MESSAGE = 'IPA exceptions must have a valid rule name and a reason.'; -const RULE_NAME_PREFIX = 'xgen-IPA-'; +const ERROR_MESSAGE_RULENAME_FORMAT = + 'IPA exceptions must have a valid key following xgen-IPA-XXX or xgen-IPA-XXX-{rule-name} format.'; +const ERROR_MESSAGE_REASON_FORMAT = + 'IPA exceptions must have a non-empty reason that starts with uppercase and ends with a full stop.'; +const RULE_NAME_PATTERN = /^xgen-IPA-\d{3}(?:-[a-z-]+)?$/; // Note: This rule does not allow exceptions export default (input, _, { path, rule }) => { @@ -10,21 +13,44 @@ export default (input, _, { path, rule }) => { return evaluateAndCollectAdoptionStatusWithoutExceptions(errors, ruleName, path); }; -function isValidException(ruleName, reason) { - return ruleName.startsWith(RULE_NAME_PREFIX) && typeof reason === 'string' && reason !== ''; +function isRuleNameValid(ruleName) { + return RULE_NAME_PATTERN.test(ruleName); +} + +function isReasonFormatValid(reason) { + if (reason === null || reason === undefined || typeof reason !== 'string' || reason === '') { + return false; + } + // Check if reason starts with uppercase letter + if (!/^[A-Z]/.test(reason)) { + return false; + } + + // Check if reason ends with a full stop + if (!reason.endsWith('.')) { + return false; + } + return true; } function checkViolationsAndReturnErrors(input, path, ruleName) { const errors = []; try { const exemptedRules = Object.keys(input); - exemptedRules.forEach((ruleName) => { - const reason = input[ruleName]; - if (!isValidException(ruleName, reason)) { + exemptedRules.forEach((key) => { + const reason = input[key]; + if (!isRuleNameValid(key)) { errors.push({ path: path.concat([ruleName]), - message: ERROR_MESSAGE, + message: ERROR_MESSAGE_RULENAME_FORMAT, }); + } else { + if (!isReasonFormatValid(reason)) { + errors.push({ + path: path.concat([ruleName]), + message: ERROR_MESSAGE_REASON_FORMAT, + }); + } } }); return errors;