Skip to content

Commit e096c19

Browse files
authored
CLOUDP-304930: IPA-102 pattern test (#498)
1 parent 3dabaed commit e096c19

File tree

4 files changed

+168
-6
lines changed

4 files changed

+168
-6
lines changed
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import testRule from './__helpers__/testRule';
2+
import { DiagnosticSeverity } from '@stoplight/types';
3+
4+
testRule('xgen-IPA-102-collection-identifier-pattern', [
5+
{
6+
name: 'valid collection identifiers',
7+
document: {
8+
paths: {
9+
'/resources': {},
10+
'/users': {},
11+
'/resourceGroups': {},
12+
'/api/v2/customers/payments': {},
13+
},
14+
},
15+
errors: [],
16+
},
17+
{
18+
name: 'valid with path parameters',
19+
document: {
20+
paths: {
21+
'/resources/{id}': {},
22+
'/users/{userId}/profiles': {},
23+
},
24+
},
25+
errors: [],
26+
},
27+
{
28+
name: 'valid with custom methods',
29+
document: {
30+
paths: {
31+
'/resources:create': {},
32+
'/users/{userId}:activate': {},
33+
},
34+
},
35+
errors: [],
36+
},
37+
{
38+
name: 'invalid starts with uppercase',
39+
document: {
40+
paths: {
41+
'/Resources': {},
42+
},
43+
},
44+
errors: [
45+
{
46+
code: 'xgen-IPA-102-collection-identifier-pattern',
47+
message:
48+
"Collection identifiers must begin with a lowercase letter and contain only ASCII letters and numbers (/[a-z][a-zA-Z0-9]*/). Path segment 'Resources' in path '/Resources' doesn't match the required pattern. http://go/ipa/102",
49+
path: ['paths', '/Resources'],
50+
severity: DiagnosticSeverity.Warning,
51+
},
52+
],
53+
},
54+
{
55+
name: 'invalid with special characters',
56+
document: {
57+
paths: {
58+
'/resource-groups': {},
59+
'/user_profiles': {},
60+
},
61+
},
62+
errors: [
63+
{
64+
code: 'xgen-IPA-102-collection-identifier-pattern',
65+
message:
66+
"Collection identifiers must begin with a lowercase letter and contain only ASCII letters and numbers (/[a-z][a-zA-Z0-9]*/). Path segment 'resource-groups' in path '/resource-groups' doesn't match the required pattern. http://go/ipa/102",
67+
path: ['paths', '/resource-groups'],
68+
severity: DiagnosticSeverity.Warning,
69+
},
70+
{
71+
code: 'xgen-IPA-102-collection-identifier-pattern',
72+
message:
73+
"Collection identifiers must begin with a lowercase letter and contain only ASCII letters and numbers (/[a-z][a-zA-Z0-9]*/). Path segment 'user_profiles' in path '/user_profiles' doesn't match the required pattern. http://go/ipa/102",
74+
path: ['paths', '/user_profiles'],
75+
severity: DiagnosticSeverity.Warning,
76+
},
77+
],
78+
},
79+
{
80+
name: 'valid with path-level exception',
81+
document: {
82+
paths: {
83+
'/resource-groups': {
84+
'x-xgen-IPA-exception': {
85+
'xgen-IPA-102-collection-identifier-pattern': 'Legacy API path that cannot be changed',
86+
},
87+
},
88+
},
89+
},
90+
errors: [],
91+
},
92+
]);
Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
# IPA-102: Resource Identifiers
22
# http://go/ipa/102
33

4-
functions:
5-
- eachPathAlternatesBetweenResourceNameAndPathParam
6-
74
rules:
85
xgen-IPA-102-path-alternate-resource-name-path-param:
96
description: 'Paths should alternate between resource names and path params. http://go/ipa/102'
@@ -13,3 +10,16 @@ rules:
1310
then:
1411
field: '@key'
1512
function: 'eachPathAlternatesBetweenResourceNameAndPathParam'
13+
14+
xgen-IPA-102-collection-identifier-pattern:
15+
description: Collection identifiers must begin with a lowercase letter and contain only ASCII letters and numbers. http://go/ipa/102
16+
message: '{{error}} http://go/ipa/102'
17+
severity: warn
18+
given: $.paths
19+
then:
20+
field: '@key'
21+
function: collectionIdentifierPattern
22+
23+
functions:
24+
- collectionIdentifierPattern
25+
- eachPathAlternatesBetweenResourceNameAndPathParam

tools/spectral/ipa/rulesets/README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,10 @@ For rule definitions, see [IPA-005.yaml](https://github.com/mongodb/openapi/blob
2020

2121
For rule definitions, see [IPA-102.yaml](https://github.com/mongodb/openapi/blob/main/tools/spectral/ipa/rulesets/IPA-102.yaml).
2222

23-
| Rule Name | Description | Severity |
24-
| ---------------------------------------------------- | -------------------------------------------------------------------------------- | -------- |
25-
| xgen-IPA-102-path-alternate-resource-name-path-param | Paths should alternate between resource names and path params. http://go/ipa/102 | error |
23+
| Rule Name | Description | Severity |
24+
| ---------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | -------- |
25+
| xgen-IPA-102-path-alternate-resource-name-path-param | Paths should alternate between resource names and path params. http://go/ipa/102 | error |
26+
| xgen-IPA-102-collection-identifier-pattern | Collection identifiers must begin with a lowercase letter and contain only ASCII letters and numbers. http://go/ipa/102 | warn |
2627

2728
### IPA-104
2829

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
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-pattern';
5+
const ERROR_MESSAGE =
6+
'Collection identifiers must begin with a lowercase letter and contain only ASCII letters and numbers (/[a-z][a-zA-Z0-9]*/).';
7+
const VALID_IDENTIFIER_PATTERN = /^[a-z][a-zA-Z0-9]*$/;
8+
9+
/**
10+
* Checks if collection identifiers in paths begin with a lowercase letter and contain only ASCII letters and numbers
11+
*
12+
* @param {object} input - The paths object from the OpenAPI spec
13+
* @param {object} _ - Unused
14+
* @param {object} context - The context object containing the path
15+
*/
16+
export default (input, _, { path, documentInventory }) => {
17+
const oas = documentInventory.resolved;
18+
const pathKey = input;
19+
20+
// Check for exception at the path level
21+
if (hasException(oas.paths[input], RULE_NAME)) {
22+
collectException(oas.paths[input], RULE_NAME, path);
23+
return;
24+
}
25+
26+
const violations = checkViolations(pathKey, path);
27+
if (violations.length > 0) {
28+
return collectAndReturnViolation(path, RULE_NAME, violations);
29+
}
30+
31+
return collectAdoption(path, RULE_NAME);
32+
};
33+
34+
function checkViolations(pathKey, path) {
35+
const violations = [];
36+
// Skip path parameters and custom methods
37+
const pathSegments = pathKey.split('/').filter((segment) => segment.length > 0);
38+
39+
pathSegments.forEach((segment) => {
40+
// Skip path parameters (those inside curly braces)
41+
if (segment.startsWith('{') && segment.endsWith('}')) {
42+
return;
43+
}
44+
45+
// Skip segments with custom methods (containing :)
46+
if (segment.includes(':')) {
47+
return;
48+
}
49+
50+
// Check the pattern
51+
if (!VALID_IDENTIFIER_PATTERN.test(segment)) {
52+
violations.push({
53+
message: `${ERROR_MESSAGE} Path segment '${segment}' in path '${pathKey}' doesn't match the required pattern.`,
54+
path: [...path, pathKey],
55+
});
56+
}
57+
});
58+
return violations;
59+
}

0 commit comments

Comments
 (0)