Skip to content

Commit 1d13d8f

Browse files
feat(ipa):child path identifiers inherit parent path exceptions
1 parent 87b1063 commit 1d13d8f

7 files changed

+102
-5
lines changed

tools/spectral/ipa/__tests__/IPA102CollectionIdentifierCamelCase.test.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,4 +254,20 @@ testRule('xgen-IPA-102-collection-identifier-camelCase', [
254254
},
255255
],
256256
},
257+
{
258+
name: 'child paths inherit parent exceptions',
259+
document: {
260+
paths: {
261+
'/resource_groups': {
262+
'x-xgen-IPA-exception': {
263+
'xgen-IPA-102-collection-identifier-camelCase': 'Legacy API path that cannot be changed',
264+
},
265+
},
266+
'/resource_groups/{id}': {},
267+
'/resource_groups/{id}/User-Profiles': {},
268+
'/resource_groups/{id}/User-Profiles/{profileId}': {},
269+
},
270+
},
271+
errors: [],
272+
},
257273
]);

tools/spectral/ipa/__tests__/IPA102CollectionIdentifierPattern.test.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,4 +89,20 @@ testRule('xgen-IPA-102-collection-identifier-pattern', [
8989
},
9090
errors: [],
9191
},
92+
{
93+
name: 'child paths inherit parent exceptions',
94+
document: {
95+
paths: {
96+
'/resource-groups': {
97+
'x-xgen-IPA-exception': {
98+
'xgen-IPA-102-collection-identifier-pattern': 'Legacy API path that cannot be changed',
99+
},
100+
},
101+
'/resource-groups/{id}': {},
102+
'/resource-groups/{id}/sub_resources': {},
103+
'/resource-groups/{id}/sub_resources/{subId}': {},
104+
},
105+
},
106+
errors: [],
107+
},
92108
]);

tools/spectral/ipa/__tests__/IPA102EachPathAlternatesBetweenResourceNameAndPathParam.test.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,4 +152,19 @@ testRule('xgen-IPA-102-path-alternate-resource-name-path-param', [
152152
},
153153
errors: [],
154154
},
155+
{
156+
name: 'child paths inherit parent exceptions',
157+
document: {
158+
paths: {
159+
'/api/atlas/v2/resourceName1/resourceName2': {
160+
'x-xgen-IPA-exception': {
161+
'xgen-IPA-102-path-alternate-resource-name-path-param': 'parent exception reason',
162+
},
163+
},
164+
'/api/atlas/v2/resourceName1/resourceName2/child': {},
165+
'/api/atlas/v2/resourceName1/resourceName2/child/{id}': {},
166+
},
167+
},
168+
errors: [],
169+
},
155170
]);

tools/spectral/ipa/rulesets/functions/IPA102CollectionIdentifierCamelCase.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1-
import { evaluateAndCollectAdoptionStatus, handleInternalError } from './utils/collectionUtils.js';
1+
import {
2+
evaluateAndCollectAdoptionStatus,
3+
handleInternalError,
4+
} from './utils/collectionUtils.js';
25
import { isPathParam } from './utils/componentUtils.js';
36
import { casing } from '@stoplight/spectral-functions';
7+
import { findExceptionInPathHierarchy } from './utils/exceptions.js';
48

59
const RULE_NAME = 'xgen-IPA-102-collection-identifier-camelCase';
610
const ERROR_MESSAGE = 'Collection identifiers must be in camelCase.';
@@ -21,7 +25,11 @@ export default (input, options, { path, documentInventory }) => {
2125

2226
const violations = checkViolations(pathKey, path, ignoredValues);
2327

24-
return evaluateAndCollectAdoptionStatus(violations, RULE_NAME, oas.paths[input], path);
28+
// Check for exceptions in path hierarchy
29+
const pathWithException = findExceptionInPathHierarchy(oas, pathKey, RULE_NAME);
30+
const objectToCheck = pathWithException ? oas.paths[pathWithException] : oas.paths[input];
31+
32+
return evaluateAndCollectAdoptionStatus(violations, RULE_NAME, objectToCheck, path);
2533
};
2634

2735
function checkViolations(pathKey, path, ignoredValues = []) {

tools/spectral/ipa/rulesets/functions/IPA102CollectionIdentifierPattern.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { evaluateAndCollectAdoptionStatus } from './utils/collectionUtils.js';
2+
import { findExceptionInPathHierarchy } from './utils/exceptions.js';
23

34
const RULE_NAME = 'xgen-IPA-102-collection-identifier-pattern';
45
const ERROR_MESSAGE =
@@ -17,7 +18,11 @@ export default (input, _, { path, documentInventory }) => {
1718

1819
const violations = checkViolations(input, path);
1920

20-
return evaluateAndCollectAdoptionStatus(violations, RULE_NAME, oas.paths[input], path);
21+
// Check for exceptions in path hierarchy
22+
const pathWithException = findExceptionInPathHierarchy(oas, input, RULE_NAME);
23+
const objectToCheck = pathWithException ? oas.paths[pathWithException] : oas.paths[input];
24+
25+
return evaluateAndCollectAdoptionStatus(violations, RULE_NAME, objectToCheck, path);
2126
};
2227

2328
function checkViolations(pathKey, path) {

tools/spectral/ipa/rulesets/functions/IPA102EachPathAlternatesBetweenResourceNameAndPathParam.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import { isPathParam } from './utils/componentUtils.js';
2-
import { evaluateAndCollectAdoptionStatus, handleInternalError } from './utils/collectionUtils.js';
2+
import {
3+
evaluateAndCollectAdoptionStatus,
4+
handleInternalError,
5+
} from './utils/collectionUtils.js';
36
import { AUTH_PREFIX, UNAUTH_PREFIX } from './utils/resourceEvaluation.js';
7+
import { findExceptionInPathHierarchy } from './utils/exceptions.js';
48

59
const RULE_NAME = 'xgen-IPA-102-path-alternate-resource-name-path-param';
610
const ERROR_MESSAGE = 'API paths must alternate between resource name and path params.';
@@ -37,7 +41,11 @@ export default (input, _, { path, documentInventory }) => {
3741

3842
const errors = checkViolationsAndReturnErrors(suffixWithLeadingSlash, path);
3943

40-
return evaluateAndCollectAdoptionStatus(errors, RULE_NAME, oas.paths[input], path);
44+
// Check for exceptions in path hierarchy
45+
const pathWithException = findExceptionInPathHierarchy(oas, input, RULE_NAME);
46+
const objectToCheck = pathWithException ? oas.paths[pathWithException] : oas.paths[input];
47+
48+
return evaluateAndCollectAdoptionStatus(errors, RULE_NAME, objectToCheck, path);
4149
};
4250

4351
function checkViolationsAndReturnErrors(suffixWithLeadingSlash, path) {

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,32 @@ export function hasException(object, ruleName) {
1313
}
1414
return false;
1515
}
16+
17+
/**
18+
* Finds an exception in the path hierarchy of an OpenAPI Specification (OAS) document
19+
* for a specific rule name, starting from the current path and traversing up to parent paths.
20+
*
21+
* @param {object} oas - OpenAPI Specification document containing the paths.
22+
* @param {string} currentPath - Current path to check for exceptions.
23+
* @param {string} ruleName - Name of the rule for which the exception is being checked.
24+
* @return {string|null} Returns the path where the exception is found,
25+
* or null if no exception is found in the current path or its hierarchy.
26+
*/
27+
export function findExceptionInPathHierarchy(oas, currentPath, ruleName) {
28+
// Check current path first
29+
if (hasException(oas.paths[currentPath], ruleName)) {
30+
return currentPath;
31+
}
32+
33+
// Check parent paths by removing segments from the end
34+
const pathSegments = currentPath.split('/').filter(segment => segment !== '');
35+
36+
for (let i = pathSegments.length - 1; i > 0; i--) {
37+
const parentPath = '/' + pathSegments.slice(0, i).join('/');
38+
if (oas.paths[parentPath] && hasException(oas.paths[parentPath], ruleName)) {
39+
return parentPath;
40+
}
41+
}
42+
43+
return null;
44+
}

0 commit comments

Comments
 (0)