Skip to content

Commit f763ff9

Browse files
committed
CLOUDP-304929: camel case
1 parent 1487a6e commit f763ff9

File tree

3 files changed

+244
-0
lines changed

3 files changed

+244
-0
lines changed
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import testRule from './__helpers__/testRule';
2+
import { DiagnosticSeverity } from '@stoplight/types';
3+
4+
testRule('xgen-IPA-102-collection-identifier-camelCase', [
5+
{
6+
name: 'valid camelCase identifiers',
7+
document: {
8+
paths: {
9+
'/resources': {},
10+
'/users': {},
11+
'/resourceGroups': {},
12+
'/userProfiles': {}
13+
},
14+
},
15+
errors: [],
16+
},
17+
{
18+
name: 'valid camelCase with path parameters',
19+
document: {
20+
paths: {
21+
'/resourceGroups/{groupId}': {},
22+
'/users/{userId}/userProfiles': {},
23+
},
24+
},
25+
errors: [],
26+
},
27+
{
28+
name: 'valid camelCase custom methods',
29+
document: {
30+
paths: {
31+
'/resources:createResource': {},
32+
'/users/{userId}:activateUser': {},
33+
},
34+
},
35+
errors: [],
36+
},
37+
{
38+
name: 'invalid PascalCase instead of camelCase',
39+
document: {
40+
paths: {
41+
'/Resources': {},
42+
},
43+
},
44+
errors: [
45+
{
46+
code: 'xgen-IPA-102-collection-identifier-camelCase',
47+
message: "Collection identifiers must be in camelCase. Path segment 'Resources' in path '/Resources' is not in camelCase. http://go/ipa/102",
48+
path: ['paths'],
49+
severity: DiagnosticSeverity.Warning,
50+
},
51+
],
52+
},
53+
{
54+
name: 'invalid with snake_case instead of camelCase',
55+
document: {
56+
paths: {
57+
'/resource_groups': {},
58+
},
59+
},
60+
errors: [
61+
{
62+
code: 'xgen-IPA-102-collection-identifier-camelCase',
63+
message: "Collection identifiers must be in camelCase. Path segment 'resource_groups' in path '/resource_groups' is not in camelCase. http://go/ipa/102",
64+
path: ['paths'],
65+
severity: DiagnosticSeverity.Warning,
66+
},
67+
],
68+
},
69+
{
70+
name: 'invalid with kebab-case instead of camelCase',
71+
document: {
72+
paths: {
73+
'/resource-groups': {},
74+
},
75+
},
76+
errors: [
77+
{
78+
code: 'xgen-IPA-102-collection-identifier-camelCase',
79+
message: "Collection identifiers must be in camelCase. Path segment 'resource-groups' in path '/resource-groups' is not in camelCase. http://go/ipa/102",
80+
path: ['paths'],
81+
severity: DiagnosticSeverity.Warning,
82+
},
83+
],
84+
},
85+
{
86+
name: 'invalid custom method not in camelCase',
87+
document: {
88+
paths: {
89+
'/resources:CREATE_RESOURCE': {},
90+
},
91+
},
92+
errors: [
93+
{
94+
code: 'xgen-IPA-102-collection-identifier-camelCase',
95+
message: "Collection identifiers must be in camelCase. Custom method 'CREATE_RESOURCE' in path '/resources:CREATE_RESOURCE' is not in camelCase. http://go/ipa/102",
96+
path: ['paths'],
97+
severity: DiagnosticSeverity.Warning,
98+
},
99+
],
100+
},
101+
{
102+
name: 'invalid with consecutive uppercase letters',
103+
document: {
104+
paths: {
105+
'/resourcesAPI': {},
106+
},
107+
},
108+
errors: [
109+
{
110+
code: 'xgen-IPA-102-collection-identifier-camelCase',
111+
message: "Collection identifiers must be in camelCase. Path segment 'resourcesAPI' in path '/resourcesAPI' is not in camelCase. http://go/ipa/102",
112+
path: ['paths'],
113+
severity: DiagnosticSeverity.Warning,
114+
},
115+
],
116+
},
117+
{
118+
name: 'valid with path-level exception',
119+
document: {
120+
paths: {
121+
'/resource_groups': {
122+
'x-xgen-IPA-exception': {
123+
'xgen-IPA-102-collection-identifier-camelCase': 'Legacy API path that cannot be changed'
124+
}
125+
},
126+
},
127+
},
128+
errors: [],
129+
},
130+
]);
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { collectAdoption, collectAndReturnViolation, collectException } from './utils/collectionUtils.js';
2+
import { hasException } from './utils/exceptions.js';
3+
4+
const RULE_NAME = 'xgen-IPA-102-collection-identifier-camelCase';
5+
const ERROR_MESSAGE = 'Collection identifiers must be in camelCase.';
6+
7+
/**
8+
* Checks if a string is in camelCase format
9+
*
10+
* @param {string} str - The string to check
11+
* @returns {boolean} - True if the string is in camelCase, false otherwise
12+
*/
13+
function isCamelCase(str) {
14+
// Must start with lowercase letter
15+
if (!/^[a-z]/.test(str)) {
16+
return false;
17+
}
18+
19+
// Should not contain underscores or hyphens
20+
if (/[-_]/.test(str)) {
21+
return false;
22+
}
23+
24+
// Should not have consecutive uppercase letters (not typical in camelCase)
25+
if (/[A-Z]{2,}/.test(str)) {
26+
return false;
27+
}
28+
29+
return true;
30+
}
31+
32+
/**
33+
* Checks if collection identifiers in paths follow camelCase convention
34+
*
35+
* @param {object} input - The paths object from the OpenAPI spec
36+
* @param {object} _ - Unused
37+
* @param {object} context - The context object containing the path
38+
*/
39+
export default (input, _, { path }) => {
40+
const violations = [];
41+
42+
Object.keys(input).forEach(pathKey => {
43+
// Check for exception at the path level
44+
if (input[pathKey] && hasException(input[pathKey], RULE_NAME)) {
45+
collectException(input[pathKey], RULE_NAME, [...path, pathKey]);
46+
return;
47+
}
48+
49+
const pathSegments = pathKey.split('/').filter(segment => segment.length > 0);
50+
51+
pathSegments.forEach(segment => {
52+
// Skip path parameters
53+
if (segment.startsWith('{') && segment.endsWith('}')) {
54+
return;
55+
}
56+
57+
// Check for custom methods
58+
const parts = segment.split(':');
59+
const identifier = parts[0];
60+
61+
// Skip empty identifiers
62+
if (identifier.length === 0) {
63+
return;
64+
}
65+
66+
// Check if it's in camelCase
67+
if (!isCamelCase(identifier)) {
68+
violations.push({
69+
message: `${ERROR_MESSAGE} Path segment '${identifier}' in path '${pathKey}' is not in camelCase.`,
70+
path: [...path, pathKey]
71+
});
72+
}
73+
74+
// If there's a custom method, check that too
75+
if (parts.length > 1 && parts[1].length > 0) {
76+
const methodName = parts[1];
77+
if (!isCamelCase(methodName)) {
78+
violations.push({
79+
message: `${ERROR_MESSAGE} Custom method '${methodName}' in path '${pathKey}' is not in camelCase.`,
80+
path: [...path, pathKey]
81+
});
82+
}
83+
}
84+
});
85+
});
86+
87+
if (violations.length > 0) {
88+
return collectAndReturnViolation(path, RULE_NAME, violations);
89+
}
90+
91+
return collectAdoption(path, RULE_NAME);
92+
};

tools/spectral/ipa/rulesets/functions/utils/componentUtils.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,25 @@
1+
/**
2+
* Checks if a string is in camelCase format
3+
* @param {string} str - The string to check
4+
* @returns {boolean} - True if the string is in camelCase, false otherwise
5+
*/
6+
export function isCamelCase(str) {
7+
// Must start with lowercase letter
8+
if (!/^[a-z]/.test(str)) {
9+
return false;
10+
}
11+
// Should not contain underscores or hyphens
12+
if (/[-_]/.test(str)) {
13+
return false;
14+
}
15+
// Should not have consecutive uppercase letters (not typical in camelCase)
16+
if (/[A-Z]{2,}/.test(str)) {
17+
return false;
18+
}
19+
return true;
20+
}
21+
22+
123
/**
224
* Checks if a string belongs to a path parameter or a path parameter with a custom method.
325
*

0 commit comments

Comments
 (0)