diff --git a/tools/spectral/ipa/__tests__/IPA126TagNamesShouldUseTitleCase.test.js b/tools/spectral/ipa/__tests__/IPA126TagNamesShouldUseTitleCase.test.js new file mode 100644 index 0000000000..f2a1073170 --- /dev/null +++ b/tools/spectral/ipa/__tests__/IPA126TagNamesShouldUseTitleCase.test.js @@ -0,0 +1,186 @@ +import testRule from './__helpers__/testRule.js'; +import { DiagnosticSeverity } from '@stoplight/types'; + +testRule('xgen-IPA-126-tag-names-should-use-title-case', [ + { + name: 'valid Title Case tag names', + document: { + tags: [ + { name: 'User Management' }, + { name: 'Resource Groups' }, + { name: 'Atlas' }, + { name: 'User Profiles' }, + { name: 'Api' }, + { name: 'Users' }, + { name: 'Resources' }, + { name: 'Projects' }, + ], + }, + errors: [], + }, + { + name: 'invalid camelCase instead of Title Case', + document: { + tags: [{ name: 'userManagement' }], + }, + errors: [ + { + code: 'xgen-IPA-126-tag-names-should-use-title-case', + message: 'Tag name should use Title Case, found: "userManagement".', + path: ['tags', '0'], + severity: DiagnosticSeverity.Warning, + }, + ], + }, + { + name: 'invalid kebab-case instead of Title Case', + document: { + tags: [{ name: 'user-management' }], + }, + errors: [ + { + code: 'xgen-IPA-126-tag-names-should-use-title-case', + message: 'Tag name should use Title Case, found: "user-management".', + path: ['tags', '0'], + severity: DiagnosticSeverity.Warning, + }, + ], + }, + { + name: 'invalid snake_case instead of Title Case', + document: { + tags: [{ name: 'user_management' }], + }, + errors: [ + { + code: 'xgen-IPA-126-tag-names-should-use-title-case', + message: 'Tag name should use Title Case, found: "user_management".', + path: ['tags', '0'], + severity: DiagnosticSeverity.Warning, + }, + ], + }, + { + name: 'invalid all lowercase instead of Title Case', + document: { + tags: [{ name: 'user management' }], + }, + errors: [ + { + code: 'xgen-IPA-126-tag-names-should-use-title-case', + message: 'Tag name should use Title Case, found: "user management".', + path: ['tags', '0'], + severity: DiagnosticSeverity.Warning, + }, + ], + }, + { + name: 'invalid ALL UPPERCASE instead of Title Case', + document: { + tags: [{ name: 'USER MANAGEMENT' }], + }, + errors: [ + { + code: 'xgen-IPA-126-tag-names-should-use-title-case', + message: 'Tag name should use Title Case, found: "USER MANAGEMENT".', + path: ['tags', '0'], + severity: DiagnosticSeverity.Warning, + }, + ], + }, + { + name: 'mixed cases in multiple tags', + document: { + tags: [{ name: 'User Management' }, { name: 'resourceGroups' }, { name: 'API ENDPOINTS' }], + }, + errors: [ + { + code: 'xgen-IPA-126-tag-names-should-use-title-case', + message: 'Tag name should use Title Case, found: "resourceGroups".', + path: ['tags', '1'], + severity: DiagnosticSeverity.Warning, + }, + { + code: 'xgen-IPA-126-tag-names-should-use-title-case', + message: 'Tag name should use Title Case, found: "API ENDPOINTS".', + path: ['tags', '2'], + severity: DiagnosticSeverity.Warning, + }, + ], + }, + { + name: 'valid with exception', + document: { + tags: [ + { + name: 'legacy_tag', + 'x-xgen-IPA-exception': { + 'xgen-IPA-126-tag-names-should-use-title-case': 'Legacy tag that cannot be changed', + }, + }, + ], + }, + errors: [], + }, + { + name: 'invalid tag names', + document: { + tags: [ + { name: 'Api V1' }, + { name: 'Version 2 Resources' }, + { name: 'Push-Based Log Export' }, //valid + { name: 'AWS Clusters DNS' }, // valid + { name: 'Encryption at Rest using Customer Key Management' }, + { name: '-Test Tag' }, + { name: 'Test Tag-' }, + { name: 'Test Tag -Name' }, + { name: 'the Test Tag' }, + { name: 'A Test Tag' }, + ], + }, + errors: [ + { + code: 'xgen-IPA-126-tag-names-should-use-title-case', + message: 'Tag name should use Title Case, found: "Api V1".', + path: ['tags', '0'], + severity: DiagnosticSeverity.Warning, + }, + { + code: 'xgen-IPA-126-tag-names-should-use-title-case', + message: 'Tag name should use Title Case, found: "Version 2 Resources".', + path: ['tags', '1'], + severity: DiagnosticSeverity.Warning, + }, + { + code: 'xgen-IPA-126-tag-names-should-use-title-case', + message: 'Tag name should use Title Case, found: "Encryption at Rest using Customer Key Management".', + path: ['tags', '4'], + severity: DiagnosticSeverity.Warning, + }, + { + code: 'xgen-IPA-126-tag-names-should-use-title-case', + message: 'Tag name should use Title Case, found: "-Test Tag".', + path: ['tags', '5'], + severity: DiagnosticSeverity.Warning, + }, + { + code: 'xgen-IPA-126-tag-names-should-use-title-case', + message: 'Tag name should use Title Case, found: "Test Tag-".', + path: ['tags', '6'], + severity: DiagnosticSeverity.Warning, + }, + { + code: 'xgen-IPA-126-tag-names-should-use-title-case', + message: 'Tag name should use Title Case, found: "Test Tag -Name".', + path: ['tags', '7'], + severity: DiagnosticSeverity.Warning, + }, + { + code: 'xgen-IPA-126-tag-names-should-use-title-case', + message: 'Tag name should use Title Case, found: "the Test Tag".', + path: ['tags', '8'], + severity: DiagnosticSeverity.Warning, + }, + ], + }, +]); diff --git a/tools/spectral/ipa/ipa-spectral.yaml b/tools/spectral/ipa/ipa-spectral.yaml index ea85043bca..c52ef721a8 100644 --- a/tools/spectral/ipa/ipa-spectral.yaml +++ b/tools/spectral/ipa/ipa-spectral.yaml @@ -18,6 +18,7 @@ extends: - ./rulesets/IPA-123.yaml - ./rulesets/IPA-124.yaml - ./rulesets/IPA-125.yaml + - ./rulesets/IPA-126.yaml overrides: - files: diff --git a/tools/spectral/ipa/rulesets/IPA-126.yaml b/tools/spectral/ipa/rulesets/IPA-126.yaml new file mode 100644 index 0000000000..8691d99959 --- /dev/null +++ b/tools/spectral/ipa/rulesets/IPA-126.yaml @@ -0,0 +1,49 @@ +# IPA-126: Top-Level API Names +# http://go/ipa/126 + +functions: + - IPA126TagNamesShouldUseTitleCase +rules: + xgen-IPA-126-tag-names-should-use-title-case: + description: | + Tag names in the OpenAPI specification should use Title Case. + + ##### Implementation details + Rule checks for the following conditions: + - All tag names defined in the OpenAPI tags object should use Title Case + - Title Case means each word starts with an uppercase letter, and the rest are lowercase + - Certain abbreviations (like "API", "AWS", etc.) in the ignoreList are allowed to maintain their casing + - Grammatical words (like "and", "or", "the", etc.) are allowed to be all lowercase + + ##### Configuration + This rule includes two configuration options: + - `ignoreList`: Words that are allowed to maintain their specific casing (e.g., "API", "AWS", "DNS") + - `grammaticalWords`: Common words that can remain lowercase in titles (e.g., "and", "or", "the") + message: '{{error}} https://mdb.link/mongodb-atlas-openapi-validation#xgen-IPA-126-tag-names-should-use-title-case' + severity: warn + given: $.tags[?(@.name && @.name.length > 0)] + then: + function: 'IPA126TagNamesShouldUseTitleCase' + functionOptions: + ignoreList: + - 'AWS' + - 'DNS' + - 'API' + - 'IP' + - 'MongoDB' + - 'LDAP' + - 'GCP' + grammaticalWords: + - 'and' + - 'or' + - 'to' + - 'in' + - 'as' + - 'for' + - 'of' + - 'with' + - 'by' + - 'but' + - 'the' + - 'a' + - 'an' diff --git a/tools/spectral/ipa/rulesets/README.md b/tools/spectral/ipa/rulesets/README.md index 27e7c81fa2..68dac890c4 100644 --- a/tools/spectral/ipa/rulesets/README.md +++ b/tools/spectral/ipa/rulesets/README.md @@ -920,4 +920,27 @@ object types with clear discriminators. +### IPA-126 + +Rules are based on [http://go/ipa/IPA-126](http://go/ipa/IPA-126). + +#### xgen-IPA-126-tag-names-should-use-title-case + + ![warn](https://img.shields.io/badge/warning-yellow) +Tag names in the OpenAPI specification should use Title Case. + +##### Implementation details +Rule checks for the following conditions: + - All tag names defined in the OpenAPI tags object should use Title Case + - Title Case means each word starts with an uppercase letter, and the rest are lowercase + - Certain abbreviations (like "API", "AWS", etc.) in the ignoreList are allowed to maintain their casing + - Grammatical words (like "and", "or", "the", etc.) are allowed to be all lowercase + +##### Configuration +This rule includes two configuration options: + - `ignoreList`: Words that are allowed to maintain their specific casing (e.g., "API", "AWS", "DNS") + - `grammaticalWords`: Common words that can remain lowercase in titles (e.g., "and", "or", "the") + + + diff --git a/tools/spectral/ipa/rulesets/functions/IPA126TagNamesShouldUseTitleCase.js b/tools/spectral/ipa/rulesets/functions/IPA126TagNamesShouldUseTitleCase.js new file mode 100644 index 0000000000..6b17a9b415 --- /dev/null +++ b/tools/spectral/ipa/rulesets/functions/IPA126TagNamesShouldUseTitleCase.js @@ -0,0 +1,47 @@ +import { hasException } from './utils/exceptions.js'; +import { collectAdoption, collectAndReturnViolation, collectException } from './utils/collectionUtils.js'; + +const RULE_NAME = 'xgen-IPA-126-tag-names-should-use-title-case'; + +export default (input, { ignoreList, grammaticalWords }, { path }) => { + const tagName = input.name; + if (hasException(input, RULE_NAME)) { + collectException(input, RULE_NAME, path); + return; + } + + // Check if the tag name uses Title Case + if (!isTitleCase(tagName, ignoreList, grammaticalWords)) { + return collectAndReturnViolation(path, RULE_NAME, [ + { + path, + message: `Tag name should use Title Case, found: "${tagName}".`, + }, + ]); + } + + // Tag name uses Title Case + collectAdoption(path, RULE_NAME); +}; + +function isTitleCase(str, ignoreList, grammaticalWords) { + // Split by spaces to check each word/word-group + // First character should be uppercase, rest lowercase, all alphabetical + const words = str.split(' '); + + return words.every((wordGroup, index) => { + // For hyphenated words, check each part + if (wordGroup.includes('-')) { + const hyphenatedParts = wordGroup.split('-'); + return hyphenatedParts.every((part) => { + if (ignoreList.includes(part)) return true; + return /^[A-Z][a-z]*$/.test(part); + }); + } + + // For regular words + if (ignoreList.includes(wordGroup)) return true; + if (index !== 0 && grammaticalWords.includes(wordGroup)) return true; + return /^[A-Z][a-z]*$/.test(wordGroup); + }); +}