Skip to content

Commit 75f3934

Browse files
fix(ipa): child path identifiers inherit parent path exceptions (#896)
1 parent 89929a9 commit 75f3934

12 files changed

+356
-16
lines changed

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

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,4 +254,85 @@ 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+
},
273+
{
274+
name: 'child paths have exceptions along with parent exceptions',
275+
document: {
276+
paths: {
277+
'/resource_groups': {
278+
'x-xgen-IPA-exception': {
279+
'xgen-IPA-102-collection-identifier-camelCase': 'Legacy API path that cannot be changed',
280+
},
281+
},
282+
'/resource_groups/{id}': {
283+
'x-xgen-IPA-exception': {
284+
'xgen-IPA-102-collection-identifier-camelCase': 'Legacy API path that cannot be changed',
285+
},
286+
},
287+
'/resource_groups/{id}/User-Profiles': {
288+
'x-xgen-IPA-exception': {
289+
'xgen-IPA-102-collection-identifier-camelCase': 'Legacy API path that cannot be changed',
290+
},
291+
},
292+
'/resource_groups/{id}/User-Profiles/{profileId}': {
293+
'x-xgen-IPA-exception': {
294+
'xgen-IPA-102-collection-identifier-camelCase': 'Legacy API path that cannot be changed',
295+
},
296+
},
297+
},
298+
},
299+
errors: [
300+
{
301+
code: 'xgen-IPA-102-collection-identifier-camelCase',
302+
message:
303+
'This component adopts the rule and does not need an exception. Please remove the exception. https://mdb.link/mongodb-atlas-openapi-validation#xgen-IPA-102-collection-identifier-camelCase',
304+
path: [
305+
'paths',
306+
'/resource_groups/{id}',
307+
'x-xgen-IPA-exception',
308+
'xgen-IPA-102-collection-identifier-camelCase',
309+
],
310+
severity: DiagnosticSeverity.Error,
311+
},
312+
{
313+
code: 'xgen-IPA-102-collection-identifier-camelCase',
314+
message:
315+
'This component adopts the rule and does not need an exception. Please remove the exception. https://mdb.link/mongodb-atlas-openapi-validation#xgen-IPA-102-collection-identifier-camelCase',
316+
path: [
317+
'paths',
318+
'/resource_groups/{id}/User-Profiles',
319+
'x-xgen-IPA-exception',
320+
'xgen-IPA-102-collection-identifier-camelCase',
321+
],
322+
severity: DiagnosticSeverity.Error,
323+
},
324+
{
325+
code: 'xgen-IPA-102-collection-identifier-camelCase',
326+
message:
327+
'This component adopts the rule and does not need an exception. Please remove the exception. https://mdb.link/mongodb-atlas-openapi-validation#xgen-IPA-102-collection-identifier-camelCase',
328+
path: [
329+
'paths',
330+
'/resource_groups/{id}/User-Profiles/{profileId}',
331+
'x-xgen-IPA-exception',
332+
'xgen-IPA-102-collection-identifier-camelCase',
333+
],
334+
severity: DiagnosticSeverity.Error,
335+
},
336+
],
337+
},
257338
]);

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

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,4 +89,80 @@ 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+
},
108+
{
109+
name: 'child paths have exceptions along with parent exceptions',
110+
document: {
111+
paths: {
112+
'/resource-groups': {
113+
'x-xgen-IPA-exception': {
114+
'xgen-IPA-102-collection-identifier-pattern': 'Legacy API path that cannot be changed',
115+
},
116+
},
117+
'/resource-groups/{id}': {
118+
'x-xgen-IPA-exception': {
119+
'xgen-IPA-102-collection-identifier-pattern': 'Legacy API path that cannot be changed',
120+
},
121+
},
122+
'/resource-groups/{id}/sub_resources': {
123+
'x-xgen-IPA-exception': {
124+
'xgen-IPA-102-collection-identifier-pattern': 'Legacy API path that cannot be changed',
125+
},
126+
},
127+
'/resource-groups/{id}/sub_resources/{subId}': {
128+
'x-xgen-IPA-exception': {
129+
'xgen-IPA-102-collection-identifier-pattern': 'Legacy API path that cannot be changed',
130+
},
131+
},
132+
},
133+
},
134+
errors: [
135+
{
136+
code: 'xgen-IPA-102-collection-identifier-pattern',
137+
message:
138+
'This component adopts the rule and does not need an exception. Please remove the exception. https://mdb.link/mongodb-atlas-openapi-validation#xgen-IPA-102-collection-identifier-pattern',
139+
path: ['paths', '/resource-groups/{id}', 'x-xgen-IPA-exception', 'xgen-IPA-102-collection-identifier-pattern'],
140+
severity: DiagnosticSeverity.Error,
141+
},
142+
{
143+
code: 'xgen-IPA-102-collection-identifier-pattern',
144+
message:
145+
'This component adopts the rule and does not need an exception. Please remove the exception. https://mdb.link/mongodb-atlas-openapi-validation#xgen-IPA-102-collection-identifier-pattern',
146+
path: [
147+
'paths',
148+
'/resource-groups/{id}/sub_resources',
149+
'x-xgen-IPA-exception',
150+
'xgen-IPA-102-collection-identifier-pattern',
151+
],
152+
severity: DiagnosticSeverity.Error,
153+
},
154+
{
155+
code: 'xgen-IPA-102-collection-identifier-pattern',
156+
message:
157+
'This component adopts the rule and does not need an exception. Please remove the exception. https://mdb.link/mongodb-atlas-openapi-validation#xgen-IPA-102-collection-identifier-pattern',
158+
path: [
159+
'paths',
160+
'/resource-groups/{id}/sub_resources/{subId}',
161+
'x-xgen-IPA-exception',
162+
'xgen-IPA-102-collection-identifier-pattern',
163+
],
164+
severity: DiagnosticSeverity.Error,
165+
},
166+
],
167+
},
92168
]);

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

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,4 +152,67 @@ 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+
},
170+
{
171+
name: 'child paths have exceptions along with parent exceptions',
172+
document: {
173+
paths: {
174+
'/api/atlas/v2/resourceName1/resourceName2': {
175+
'x-xgen-IPA-exception': {
176+
'xgen-IPA-102-path-alternate-resource-name-path-param': 'parent exception reason',
177+
},
178+
},
179+
'/api/atlas/v2/resourceName1/resourceName2/child': {
180+
'x-xgen-IPA-exception': {
181+
'xgen-IPA-102-path-alternate-resource-name-path-param': 'child exception reason',
182+
},
183+
},
184+
'/api/atlas/v2/resourceName1/resourceName2/child/{id}': {
185+
'x-xgen-IPA-exception': {
186+
'xgen-IPA-102-path-alternate-resource-name-path-param': 'child exception reason',
187+
},
188+
},
189+
},
190+
},
191+
errors: [
192+
{
193+
code: 'xgen-IPA-102-path-alternate-resource-name-path-param',
194+
message:
195+
'This component adopts the rule and does not need an exception. Please remove the exception. https://mdb.link/mongodb-atlas-openapi-validation#xgen-IPA-102-path-alternate-resource-name-path-param',
196+
path: [
197+
'paths',
198+
'/api/atlas/v2/resourceName1/resourceName2/child',
199+
'x-xgen-IPA-exception',
200+
'xgen-IPA-102-path-alternate-resource-name-path-param',
201+
],
202+
severity: DiagnosticSeverity.Error,
203+
},
204+
{
205+
code: 'xgen-IPA-102-path-alternate-resource-name-path-param',
206+
message:
207+
'This component adopts the rule and does not need an exception. Please remove the exception. https://mdb.link/mongodb-atlas-openapi-validation#xgen-IPA-102-path-alternate-resource-name-path-param',
208+
path: [
209+
'paths',
210+
'/api/atlas/v2/resourceName1/resourceName2/child/{id}',
211+
'x-xgen-IPA-exception',
212+
'xgen-IPA-102-path-alternate-resource-name-path-param',
213+
],
214+
severity: DiagnosticSeverity.Error,
215+
},
216+
],
217+
},
155218
]);

tools/spectral/ipa/__tests__/utils/collectionUtils.test.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,8 +125,6 @@ describe('tools/spectral/ipa/rulesets/functions/utils/collectionUtils.js', () =>
125125
expect(result[0].message).toEqual(
126126
'This component adopts the rule and does not need an exception. Please remove the exception.'
127127
);
128-
expect(collector.add).toHaveBeenCalledTimes(1);
129-
expect(collector.add).toHaveBeenCalledWith(TEST_ENTRY_TYPE.VIOLATION, testPath, testRuleName);
130128
});
131129
});
132130

tools/spectral/ipa/ipa-spectral.yaml

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ overrides:
5858
- '**#/paths/~1rest~1unauth~1version' # external reference, to be covered by CLOUDP-309694
5959
rules:
6060
xgen-IPA-114-error-responses-refer-to-api-error: 'off'
61-
- files:
61+
- files: # To be removed in CLOUDP-338425
6262
- '**#/paths/~1api~1atlas~1v2~1groups~1%7BgroupId%7D~1clusters~1%7BclusterName%7D~1%7BclusterView%7D~1%7BdatabaseName%7D~1%7BcollectionName%7D~1collStats~1measurements' # reference to support future investigation - CLOUDP-310775
6363
- '**#/paths/~1api~1atlas~1v2~1groups~1%7BgroupId%7D~1processes~1%7BprocessId%7D~1%7BdatabaseName%7D~1%7BcollectionName%7D~1collStats~1measurements' # reference to support future investigation - CLOUDP-310775
6464
- '**#/components/schemas/HostMetricValue' # reference to support future investigation - CLOUDP-310775
@@ -177,3 +177,40 @@ overrides:
177177
- '**#/components/schemas/NewRelic'
178178
rules:
179179
xgen-IPA-117-description-must-not-use-html: 'off'
180+
- files: # To be removed in CLOUDP-338425
181+
- '**#/paths/~1api~1atlas~1v2~1groups~1%7BgroupId%7D~1backup~1exportBuckets~1%7BexportBucketId%7D'
182+
- '**#/paths/~1api~1atlas~1v2~1groups~1%7BgroupId%7D~1clusters~1%7BclusterName%7D~1backup~1exports~1%7BexportId%7D'
183+
- '**#/paths/~1api~1atlas~1v2~1groups~1%7BgroupId%7D~1clusters~1%7BclusterName%7D~1backup~1restoreJobs~1%7BrestoreJobId%7D'
184+
- '**#/paths/~1api~1atlas~1v2~1groups~1%7BgroupId%7D~1clusters~1%7BclusterName%7D~1backup~1snapshots~1%7BsnapshotId%7D'
185+
- '**#/paths/~1api~1atlas~1v2~1groups~1%7BgroupId%7D~1clusters~1%7BclusterName%7D~1backup~1snapshots~1shardedCluster~1%7BsnapshotId%7D'
186+
- '**#/paths/~1api~1atlas~1v2~1groups~1%7BgroupId%7D~1clusters~1%7BclusterName%7D~1backup~1snapshots~1shardedClusters'
187+
- '**#/paths/~1api~1atlas~1v2~1groups~1%7BgroupId%7D~1clusters~1%7BclusterName%7D~1backup~1tenant~1restores~1%7BrestoreId%7D'
188+
- '**#/paths/~1api~1atlas~1v2~1groups~1%7BgroupId%7D~1clusters~1%7BclusterName%7D~1backup~1tenant~1snapshots~1%7BsnapshotId%7D'
189+
- '**#/paths/~1api~1atlas~1v2~1groups~1%7BgroupId%7D~1clusters~1%7BclusterName%7D~1fts~1indexes~1%7BdatabaseName%7D~1%7BcollectionName%7D'
190+
- '**#/paths/~1api~1atlas~1v2~1groups~1%7BgroupId%7D~1clusters~1%7BclusterName%7D~1fts~1indexes~1%7BindexId%7D'
191+
- '**#/paths/~1api~1atlas~1v2~1groups~1%7BgroupId%7D~1clusters~1%7BclusterName%7D~1search~1indexes~1%7BdatabaseName%7D~1%7BcollectionName%7D'
192+
- '**#/paths/~1api~1atlas~1v2~1groups~1%7BgroupId%7D~1clusters~1%7BclusterName%7D~1search~1indexes~1%7BdatabaseName%7D~1%7BcollectionName%7D~1%7BindexName%7D'
193+
- '**#/paths/~1api~1atlas~1v2~1groups~1%7BgroupId%7D~1clusters~1%7BclusterName%7D~1search~1indexes~1%7BindexId%7D'
194+
- '**#/paths/~1api~1atlas~1v2~1groups~1%7BgroupId%7D~1customDBRoles~1roles~1%7BroleName%7D'
195+
- '**#/paths/~1api~1atlas~1v2~1groups~1%7BgroupId%7D~1flexClusters~1%7Bname%7D~1backup~1restoreJobs~1%7BrestoreJobId%7D'
196+
- '**#/paths/~1api~1atlas~1v2~1groups~1%7BgroupId%7D~1flexClusters~1%7Bname%7D~1backup~1snapshots~1%7BsnapshotId%7D'
197+
- '**#/paths/~1api~1atlas~1v2~1groups~1%7BgroupId%7D~1hosts~1%7BprocessId%7D~1fts~1metrics~1measurements'
198+
- '**#/paths/~1api~1atlas~1v2~1groups~1%7BgroupId%7D~1privateEndpoint~1serverless~1instance~1%7BinstanceName%7D~1endpoint~1%7BendpointId%7D'
199+
- '**#/paths/~1api~1atlas~1v2~1groups~1%7BgroupId%7D~1privateNetworkSettings~1endpointIds~1%7BendpointId%7D'
200+
- '**#/paths/~1api~1atlas~1v2~1groups~1%7BgroupId%7D~1serverless~1%7BclusterName%7D~1backup~1restoreJobs~1%7BrestoreJobId%7D'
201+
- '**#/paths/~1api~1atlas~1v2~1groups~1%7BgroupId%7D~1serverless~1%7BclusterName%7D~1backup~1snapshots~1%7BsnapshotId%7D'
202+
- '**#/paths/~1api~1atlas~1v2~1groups~1%7BgroupId%7D~1streams~1privateLinkConnections~1%7BconnectionId%7D'
203+
- '**#/paths/~1api~1atlas~1v2~1groups~1%7BgroupId%7D~1streams~1vpcPeeringConnections~1%7Bid%7D'
204+
- '**#/paths/~1api~1atlas~1v2~1groups~1%7BgroupId%7D~1streams~1vpcPeeringConnections~1%7Bid%7D%3Aaccept'
205+
- '**#/paths/~1api~1atlas~1v2~1groups~1%7BgroupId%7D~1streams~1vpcPeeringConnections~1%7Bid%7D%3Areject'
206+
- '**#/paths/~1api~1atlas~1v2~1groups~1%7BgroupId%7D~1userSecurity~1ldap~1verify~1%7BrequestId%7D'
207+
- '**#/paths/~1api~1atlas~1v2~1orgs~1%7BorgId%7D~1billing~1costExplorer~1usage~1%7Btoken%7D'
208+
- '**#/paths/~1api~1atlas~1v2~1groups~1%7BgroupId%7D~1hosts~1%7BprocessId%7D~1fts~1metrics~1indexes~1%7BdatabaseName%7D~1%7BcollectionName%7D~1measurements'
209+
- '**#/paths/~1api~1atlas~1v2~1groups~1%7BgroupId%7D~1hosts~1%7BprocessId%7D~1fts~1metrics~1indexes~1%7BdatabaseName%7D~1%7BcollectionName%7D~1%7BindexName%7D~1measurements'
210+
- '**#/paths/~1api~1atlas~1v2~1groups~1%7BgroupId%7D~1liveMigrations~1validate~1%7BvalidationId%7D'
211+
rules:
212+
xgen-IPA-102-path-alternate-resource-name-path-param: 'off'
213+
- files: # To be removed in CLOUDP-338425
214+
- '**#/paths/~1api~1atlas~1v2~1groups~1%7BgroupId%7D~1customDBRoles~1roles~1%7BroleName%7D'
215+
rules:
216+
xgen-IPA-102-collection-identifier-camelCase: 'off'

tools/spectral/ipa/rulesets/IPA-102.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ rules:
1616
argument to the rule
1717
- Paths with `x-xgen-IPA-exception` for this rule are excluded from validation
1818
- Double slashes (//) are not allowed in paths
19+
- If any parent path has an exception for this rule, the exception will be inherited.
1920
2021
message: '{{error}} https://mdb.link/mongodb-atlas-openapi-validation#xgen-IPA-102-collection-identifier-camelCase'
2122
severity: error
@@ -38,6 +39,8 @@ rules:
3839
- Even-indexed path segments should be resource names (not path parameters)
3940
- Odd-indexed path segments should be path parameters
4041
- Paths with `x-xgen-IPA-exception` for this rule are excluded from validation
42+
- If any parent path has an exception for this rule, the exception will be inherited.
43+
4144
message: '{{error}} https://mdb.link/mongodb-atlas-openapi-validation#xgen-IPA-102-path-alternate-resource-name-path-param'
4245
severity: error
4346
given: '$.paths'
@@ -57,6 +60,7 @@ rules:
5760
- Custom methods (segments containing colons) are excluded from validation
5861
- Paths with `x-xgen-IPA-exception` for this rule are excluded from validation
5962
- Each non-parameter path segment must start with a lowercase letter followed by any combination of ASCII letters and numbers
63+
- If any parent path has an exception for this rule, the exception will be inherited.
6064
6165
message: '{{error}} https://mdb.link/mongodb-atlas-openapi-validation#xgen-IPA-102-collection-identifier-pattern'
6266
severity: error

tools/spectral/ipa/rulesets/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ Collection identifiers must be in camelCase.
4444
argument to the rule
4545
- Paths with `x-xgen-IPA-exception` for this rule are excluded from validation
4646
- Double slashes (//) are not allowed in paths
47+
- If any parent path has an exception for this rule, the exception will be inherited.
4748

4849
#### xgen-IPA-102-path-alternate-resource-name-path-param
4950

@@ -57,6 +58,7 @@ Rule checks for the following conditions:
5758
- Even-indexed path segments should be resource names (not path parameters)
5859
- Odd-indexed path segments should be path parameters
5960
- Paths with `x-xgen-IPA-exception` for this rule are excluded from validation
61+
- If any parent path has an exception for this rule, the exception will be inherited.
6062

6163
#### xgen-IPA-102-collection-identifier-pattern
6264

@@ -71,6 +73,7 @@ Rule checks for the following conditions:
7173
- Custom methods (segments containing colons) are excluded from validation
7274
- Paths with `x-xgen-IPA-exception` for this rule are excluded from validation
7375
- Each non-parameter path segment must start with a lowercase letter followed by any combination of ASCII letters and numbers
76+
- If any parent path has an exception for this rule, the exception will be inherited.
7477

7578

7679

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
import { evaluateAndCollectAdoptionStatus, handleInternalError } from './utils/collectionUtils.js';
22
import { isPathParam } from './utils/componentUtils.js';
33
import { casing } from '@stoplight/spectral-functions';
4+
import { findExceptionInPathHierarchy } from './utils/exceptions.js';
45

56
const RULE_NAME = 'xgen-IPA-102-collection-identifier-camelCase';
67
const ERROR_MESSAGE = 'Collection identifiers must be in camelCase.';
78

89
/**
910
* Checks if collection identifiers in paths follow camelCase convention
1011
*
12+
* The function checks the entire path hierarchy. If any parent path has an exception, the exception will be inherited.
13+
*
1114
* @param {object} input - The path key from the OpenAPI spec
1215
* @param {object} options - Rule configuration options
1316
* @param {object} context - The context object containing the path and documentInventory
@@ -21,7 +24,14 @@ export default (input, options, { path, documentInventory }) => {
2124

2225
const violations = checkViolations(pathKey, path, ignoredValues);
2326

24-
return evaluateAndCollectAdoptionStatus(violations, RULE_NAME, oas.paths[input], path);
27+
// Check for exceptions in path hierarchy
28+
const result = findExceptionInPathHierarchy(oas, pathKey, RULE_NAME, path);
29+
if (result?.error) {
30+
return result.error;
31+
}
32+
const objectToCheckForException = result ? oas.paths[result.parentPath] : oas.paths[input];
33+
34+
return evaluateAndCollectAdoptionStatus(violations, RULE_NAME, objectToCheckForException, path);
2535
};
2636

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

0 commit comments

Comments
 (0)