Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
@@ -0,0 +1,178 @@
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' },
],
},
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,
},
],
},
]);
1 change: 1 addition & 0 deletions tools/spectral/ipa/ipa-spectral.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ extends:
- ./rulesets/IPA-123.yaml
- ./rulesets/IPA-124.yaml
- ./rulesets/IPA-125.yaml
- ./rulesets/IPA-126.yaml

overrides:
- files:
Expand Down
49 changes: 49 additions & 0 deletions tools/spectral/ipa/rulesets/IPA-126.yaml
Original file line number Diff line number Diff line change
@@ -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'
23 changes: 23 additions & 0 deletions tools/spectral/ipa/rulesets/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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")




Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
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
const words = str.split(' ');

return words.every((wordGroup) => {
// For hyphenated words, check each part
if (wordGroup.includes('-')) {
const hyphenatedParts = wordGroup.split('-');
return hyphenatedParts.every((part) => {
if (ignoreList.includes(part)) return true;
if (grammaticalWords.includes(part)) return true;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (grammaticalWords.includes(part)) return true;

[nit] There are probably no compound words (I think that's what it's called 😋 ) using dashes with words like "the" or "a"

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's a good catch! Applied the suggestion, thanks!

// First character should be uppercase, rest lowercase, all alphabetical
return /^[A-Z][a-z]*$/.test(part);
});
}

// For regular words
if (ignoreList.includes(wordGroup)) return true;
if (grammaticalWords.includes(wordGroup)) return true;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Nit] These should be for words that are not the first word, e.g. The Name and User of the Thing is valid, but the User would be invalid.

Suggested change
if (grammaticalWords.includes(wordGroup)) return true;
if (index !== 0 && grammaticalWords.includes(wordGroup)) return true;

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense! Applied the suggestion, and added a unit test for it

return /^[A-Z][a-z]*$/.test(wordGroup);
});
}
Loading