Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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.',
},
},
},
Expand Down Expand Up @@ -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,
},
],
Expand Down
4 changes: 4 additions & 0 deletions tools/spectral/ipa/ipa-spectral.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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'
3 changes: 2 additions & 1 deletion tools/spectral/ipa/rulesets/IPA-005.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion tools/spectral/ipa/rulesets/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down
Original file line number Diff line number Diff line change
@@ -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 }) => {
Expand All @@ -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;
Expand Down
Loading