Skip to content

Commit 8f8c58a

Browse files
CLOUDP-306582: IPA-126: Top-Level API Names (#668)
1 parent 483c75f commit 8f8c58a

File tree

5 files changed

+306
-0
lines changed

5 files changed

+306
-0
lines changed
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
import testRule from './__helpers__/testRule.js';
2+
import { DiagnosticSeverity } from '@stoplight/types';
3+
4+
testRule('xgen-IPA-126-tag-names-should-use-title-case', [
5+
{
6+
name: 'valid Title Case tag names',
7+
document: {
8+
tags: [
9+
{ name: 'User Management' },
10+
{ name: 'Resource Groups' },
11+
{ name: 'Atlas' },
12+
{ name: 'User Profiles' },
13+
{ name: 'Api' },
14+
{ name: 'Users' },
15+
{ name: 'Resources' },
16+
{ name: 'Projects' },
17+
],
18+
},
19+
errors: [],
20+
},
21+
{
22+
name: 'invalid camelCase instead of Title Case',
23+
document: {
24+
tags: [{ name: 'userManagement' }],
25+
},
26+
errors: [
27+
{
28+
code: 'xgen-IPA-126-tag-names-should-use-title-case',
29+
message: 'Tag name should use Title Case, found: "userManagement".',
30+
path: ['tags', '0'],
31+
severity: DiagnosticSeverity.Warning,
32+
},
33+
],
34+
},
35+
{
36+
name: 'invalid kebab-case instead of Title Case',
37+
document: {
38+
tags: [{ name: 'user-management' }],
39+
},
40+
errors: [
41+
{
42+
code: 'xgen-IPA-126-tag-names-should-use-title-case',
43+
message: 'Tag name should use Title Case, found: "user-management".',
44+
path: ['tags', '0'],
45+
severity: DiagnosticSeverity.Warning,
46+
},
47+
],
48+
},
49+
{
50+
name: 'invalid snake_case instead of Title Case',
51+
document: {
52+
tags: [{ name: 'user_management' }],
53+
},
54+
errors: [
55+
{
56+
code: 'xgen-IPA-126-tag-names-should-use-title-case',
57+
message: 'Tag name should use Title Case, found: "user_management".',
58+
path: ['tags', '0'],
59+
severity: DiagnosticSeverity.Warning,
60+
},
61+
],
62+
},
63+
{
64+
name: 'invalid all lowercase instead of Title Case',
65+
document: {
66+
tags: [{ name: 'user management' }],
67+
},
68+
errors: [
69+
{
70+
code: 'xgen-IPA-126-tag-names-should-use-title-case',
71+
message: 'Tag name should use Title Case, found: "user management".',
72+
path: ['tags', '0'],
73+
severity: DiagnosticSeverity.Warning,
74+
},
75+
],
76+
},
77+
{
78+
name: 'invalid ALL UPPERCASE instead of Title Case',
79+
document: {
80+
tags: [{ name: 'USER MANAGEMENT' }],
81+
},
82+
errors: [
83+
{
84+
code: 'xgen-IPA-126-tag-names-should-use-title-case',
85+
message: 'Tag name should use Title Case, found: "USER MANAGEMENT".',
86+
path: ['tags', '0'],
87+
severity: DiagnosticSeverity.Warning,
88+
},
89+
],
90+
},
91+
{
92+
name: 'mixed cases in multiple tags',
93+
document: {
94+
tags: [{ name: 'User Management' }, { name: 'resourceGroups' }, { name: 'API ENDPOINTS' }],
95+
},
96+
errors: [
97+
{
98+
code: 'xgen-IPA-126-tag-names-should-use-title-case',
99+
message: 'Tag name should use Title Case, found: "resourceGroups".',
100+
path: ['tags', '1'],
101+
severity: DiagnosticSeverity.Warning,
102+
},
103+
{
104+
code: 'xgen-IPA-126-tag-names-should-use-title-case',
105+
message: 'Tag name should use Title Case, found: "API ENDPOINTS".',
106+
path: ['tags', '2'],
107+
severity: DiagnosticSeverity.Warning,
108+
},
109+
],
110+
},
111+
{
112+
name: 'valid with exception',
113+
document: {
114+
tags: [
115+
{
116+
name: 'legacy_tag',
117+
'x-xgen-IPA-exception': {
118+
'xgen-IPA-126-tag-names-should-use-title-case': 'Legacy tag that cannot be changed',
119+
},
120+
},
121+
],
122+
},
123+
errors: [],
124+
},
125+
{
126+
name: 'invalid tag names',
127+
document: {
128+
tags: [
129+
{ name: 'Api V1' },
130+
{ name: 'Version 2 Resources' },
131+
{ name: 'Push-Based Log Export' }, //valid
132+
{ name: 'AWS Clusters DNS' }, // valid
133+
{ name: 'Encryption at Rest using Customer Key Management' },
134+
{ name: '-Test Tag' },
135+
{ name: 'Test Tag-' },
136+
{ name: 'Test Tag -Name' },
137+
{ name: 'the Test Tag' },
138+
{ name: 'A Test Tag' },
139+
],
140+
},
141+
errors: [
142+
{
143+
code: 'xgen-IPA-126-tag-names-should-use-title-case',
144+
message: 'Tag name should use Title Case, found: "Api V1".',
145+
path: ['tags', '0'],
146+
severity: DiagnosticSeverity.Warning,
147+
},
148+
{
149+
code: 'xgen-IPA-126-tag-names-should-use-title-case',
150+
message: 'Tag name should use Title Case, found: "Version 2 Resources".',
151+
path: ['tags', '1'],
152+
severity: DiagnosticSeverity.Warning,
153+
},
154+
{
155+
code: 'xgen-IPA-126-tag-names-should-use-title-case',
156+
message: 'Tag name should use Title Case, found: "Encryption at Rest using Customer Key Management".',
157+
path: ['tags', '4'],
158+
severity: DiagnosticSeverity.Warning,
159+
},
160+
{
161+
code: 'xgen-IPA-126-tag-names-should-use-title-case',
162+
message: 'Tag name should use Title Case, found: "-Test Tag".',
163+
path: ['tags', '5'],
164+
severity: DiagnosticSeverity.Warning,
165+
},
166+
{
167+
code: 'xgen-IPA-126-tag-names-should-use-title-case',
168+
message: 'Tag name should use Title Case, found: "Test Tag-".',
169+
path: ['tags', '6'],
170+
severity: DiagnosticSeverity.Warning,
171+
},
172+
{
173+
code: 'xgen-IPA-126-tag-names-should-use-title-case',
174+
message: 'Tag name should use Title Case, found: "Test Tag -Name".',
175+
path: ['tags', '7'],
176+
severity: DiagnosticSeverity.Warning,
177+
},
178+
{
179+
code: 'xgen-IPA-126-tag-names-should-use-title-case',
180+
message: 'Tag name should use Title Case, found: "the Test Tag".',
181+
path: ['tags', '8'],
182+
severity: DiagnosticSeverity.Warning,
183+
},
184+
],
185+
},
186+
]);

tools/spectral/ipa/ipa-spectral.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ extends:
1818
- ./rulesets/IPA-123.yaml
1919
- ./rulesets/IPA-124.yaml
2020
- ./rulesets/IPA-125.yaml
21+
- ./rulesets/IPA-126.yaml
2122

2223
overrides:
2324
- files:
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# IPA-126: Top-Level API Names
2+
# http://go/ipa/126
3+
4+
functions:
5+
- IPA126TagNamesShouldUseTitleCase
6+
rules:
7+
xgen-IPA-126-tag-names-should-use-title-case:
8+
description: |
9+
Tag names in the OpenAPI specification should use Title Case.
10+
11+
##### Implementation details
12+
Rule checks for the following conditions:
13+
- All tag names defined in the OpenAPI tags object should use Title Case
14+
- Title Case means each word starts with an uppercase letter, and the rest are lowercase
15+
- Certain abbreviations (like "API", "AWS", etc.) in the ignoreList are allowed to maintain their casing
16+
- Grammatical words (like "and", "or", "the", etc.) are allowed to be all lowercase
17+
18+
##### Configuration
19+
This rule includes two configuration options:
20+
- `ignoreList`: Words that are allowed to maintain their specific casing (e.g., "API", "AWS", "DNS")
21+
- `grammaticalWords`: Common words that can remain lowercase in titles (e.g., "and", "or", "the")
22+
message: '{{error}} https://mdb.link/mongodb-atlas-openapi-validation#xgen-IPA-126-tag-names-should-use-title-case'
23+
severity: warn
24+
given: $.tags[?(@.name && @.name.length > 0)]
25+
then:
26+
function: 'IPA126TagNamesShouldUseTitleCase'
27+
functionOptions:
28+
ignoreList:
29+
- 'AWS'
30+
- 'DNS'
31+
- 'API'
32+
- 'IP'
33+
- 'MongoDB'
34+
- 'LDAP'
35+
- 'GCP'
36+
grammaticalWords:
37+
- 'and'
38+
- 'or'
39+
- 'to'
40+
- 'in'
41+
- 'as'
42+
- 'for'
43+
- 'of'
44+
- 'with'
45+
- 'by'
46+
- 'but'
47+
- 'the'
48+
- 'a'
49+
- 'an'

tools/spectral/ipa/rulesets/README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -920,4 +920,27 @@ object types with clear discriminators.
920920

921921

922922

923+
### IPA-126
924+
925+
Rules are based on [http://go/ipa/IPA-126](http://go/ipa/IPA-126).
926+
927+
#### xgen-IPA-126-tag-names-should-use-title-case
928+
929+
![warn](https://img.shields.io/badge/warning-yellow)
930+
Tag names in the OpenAPI specification should use Title Case.
931+
932+
##### Implementation details
933+
Rule checks for the following conditions:
934+
- All tag names defined in the OpenAPI tags object should use Title Case
935+
- Title Case means each word starts with an uppercase letter, and the rest are lowercase
936+
- Certain abbreviations (like "API", "AWS", etc.) in the ignoreList are allowed to maintain their casing
937+
- Grammatical words (like "and", "or", "the", etc.) are allowed to be all lowercase
938+
939+
##### Configuration
940+
This rule includes two configuration options:
941+
- `ignoreList`: Words that are allowed to maintain their specific casing (e.g., "API", "AWS", "DNS")
942+
- `grammaticalWords`: Common words that can remain lowercase in titles (e.g., "and", "or", "the")
943+
944+
945+
923946

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { hasException } from './utils/exceptions.js';
2+
import { collectAdoption, collectAndReturnViolation, collectException } from './utils/collectionUtils.js';
3+
4+
const RULE_NAME = 'xgen-IPA-126-tag-names-should-use-title-case';
5+
6+
export default (input, { ignoreList, grammaticalWords }, { path }) => {
7+
const tagName = input.name;
8+
if (hasException(input, RULE_NAME)) {
9+
collectException(input, RULE_NAME, path);
10+
return;
11+
}
12+
13+
// Check if the tag name uses Title Case
14+
if (!isTitleCase(tagName, ignoreList, grammaticalWords)) {
15+
return collectAndReturnViolation(path, RULE_NAME, [
16+
{
17+
path,
18+
message: `Tag name should use Title Case, found: "${tagName}".`,
19+
},
20+
]);
21+
}
22+
23+
// Tag name uses Title Case
24+
collectAdoption(path, RULE_NAME);
25+
};
26+
27+
function isTitleCase(str, ignoreList, grammaticalWords) {
28+
// Split by spaces to check each word/word-group
29+
// First character should be uppercase, rest lowercase, all alphabetical
30+
const words = str.split(' ');
31+
32+
return words.every((wordGroup, index) => {
33+
// For hyphenated words, check each part
34+
if (wordGroup.includes('-')) {
35+
const hyphenatedParts = wordGroup.split('-');
36+
return hyphenatedParts.every((part) => {
37+
if (ignoreList.includes(part)) return true;
38+
return /^[A-Z][a-z]*$/.test(part);
39+
});
40+
}
41+
42+
// For regular words
43+
if (ignoreList.includes(wordGroup)) return true;
44+
if (index !== 0 && grammaticalWords.includes(wordGroup)) return true;
45+
return /^[A-Z][a-z]*$/.test(wordGroup);
46+
});
47+
}

0 commit comments

Comments
 (0)