Skip to content

Commit 9a2a9aa

Browse files
authored
CLOUDP 306294: Add x-xgen-method-verb-override extension logic (#817)
1 parent 1067d90 commit 9a2a9aa

12 files changed

+234
-72
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
"testPathIgnorePatterns": [
2121
"__helpers__",
2222
"metrics/data",
23-
"IPA\\d+ValidOperationID\\.test\\.js$"
23+
"IPA\\d+ValidOperationID\\.test\\.js$"
2424
]
2525
},
2626
"dependencies": {
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { describe, it, expect, toBe } from '@jest/globals';
2+
import {
3+
hasMethodWithVerbOverride,
4+
hasCustomMethodOverride,
5+
hasMethodVerbOverride,
6+
} from '../../rulesets/functions/utils/extensions';
7+
8+
const methodWithExtension = {
9+
'x-xgen-method-verb-override': {
10+
verb: 'get',
11+
customMethod: false,
12+
},
13+
};
14+
15+
const customMethod = {
16+
'x-xgen-method-verb-override': {
17+
verb: 'add',
18+
customMethod: true,
19+
},
20+
};
21+
22+
const endpointWithMethodExtension = {
23+
delete: {
24+
'x-xgen-method-verb-override': { verb: 'remove', customMethod: true },
25+
},
26+
};
27+
28+
const endpointWithNoMethodExtension = {
29+
exception: true,
30+
};
31+
32+
describe('tools/spectral/ipa/rulesets/functions/utils/extensions.js', () => {
33+
describe('hasMethodWithVerbOverride', () => {
34+
it('returns true if endpoint has method with the extension', () => {
35+
expect(hasMethodWithVerbOverride(endpointWithMethodExtension)).toBe(true);
36+
});
37+
it('returns false if object does not a method with the extension', () => {
38+
expect(hasMethodWithVerbOverride(endpointWithNoMethodExtension)).toBe(false);
39+
});
40+
});
41+
});
42+
43+
describe('tools/spectral/ipa/rulesets/functions/utils/extensions.js', () => {
44+
describe('hasCustomMethodOverride', () => {
45+
it('returns true if the method has the extension with the cusotmMethod boolean set to true', () => {
46+
expect(hasCustomMethodOverride(customMethod)).toBe(true);
47+
});
48+
it('returns false if the method does not have the extension', () => {
49+
expect(hasCustomMethodOverride({})).toBe(false);
50+
});
51+
it('returns false if the method has the extension but is not a custom method', () => {
52+
expect(hasCustomMethodOverride(methodWithExtension)).toBe(false);
53+
});
54+
});
55+
});
56+
57+
describe('tools/spectral/ipa/rulesets/functions/utils/extensions.js', () => {
58+
describe('hasMethodVerbOverride', () => {
59+
it('returns true if the method has the extension with the expected verb', () => {
60+
expect(hasMethodVerbOverride(methodWithExtension, 'get')).toBe(true);
61+
});
62+
it('returns false if the method does not have the extension', () => {
63+
expect(hasMethodVerbOverride({}, 'get')).toBe(false);
64+
});
65+
it('returns false if the method has the extension but with an unexpected verb', () => {
66+
expect(hasMethodVerbOverride(methodWithExtension, 'put')).toBe(false);
67+
});
68+
});
69+
});

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ describe('tools/spectral/ipa/utils/operationIdGeneration.js', () => {
2828
expect(generateOperationID('addNode', '/groups/{groupId}/clusters/{clusterName}')).toEqual('addGroupClusterNode');
2929
expect(generateOperationID('get', '/api/atlas/v2/groups/byName/{groupName}')).toEqual('getGroupByName');
3030
expect(generateOperationID('', '/api/atlas/v2/groups/{groupId}/backup/exportBuckets/{exportBucketId}')).toEqual(
31-
'exportGroupBackupBuckets'
31+
'exportGroupBackupBucket'
3232
);
3333
});
3434

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

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
11
import { generateOperationID } from './utils/operationIdGeneration.js';
22
import { collectAdoption, collectAndReturnViolation, collectException } from './utils/collectionUtils.js';
33
import { hasException } from './utils/exceptions.js';
4-
import {
5-
isSingleResourceIdentifier,
6-
isResourceCollectionIdentifier,
7-
isSingletonResource,
8-
getResourcePathItems,
9-
isCustomMethodIdentifier,
10-
} from './utils/resourceEvaluation.js';
4+
import { getResourcePathItems, isCustomMethodIdentifier } from './utils/resourceEvaluation.js';
5+
import { hasCustomMethodOverride, hasMethodVerbOverride } from './utils/extensions.js';
6+
import { isInvalidGetMethod } from './utils/methodLogic.js';
117

128
const RULE_NAME = 'xgen-IPA-104-valid-operation-id';
139
const ERROR_MESSAGE = 'Invalid OperationID.';
@@ -18,9 +14,10 @@ export default (input, { methodName }, { path, documentInventory }) => {
1814
const resourcePaths = getResourcePathItems(resourcePath, oas.paths);
1915

2016
if (
17+
hasCustomMethodOverride(input) ||
2118
isCustomMethodIdentifier(resourcePath) ||
22-
(!isSingleResourceIdentifier(resourcePath) &&
23-
!(isResourceCollectionIdentifier(resourcePath) && isSingletonResource(resourcePaths)))
19+
hasMethodVerbOverride(input, 'list') ||
20+
(isInvalidGetMethod(resourcePath, resourcePaths) && !hasMethodVerbOverride(input, methodName))
2421
) {
2522
return;
2623
}

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

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,23 @@
11
import { hasException } from './utils/exceptions.js';
22
import { collectAdoption, collectAndReturnViolation, collectException } from './utils/collectionUtils.js';
3-
import {
4-
getResourcePathItems,
5-
isCustomMethodIdentifier,
6-
isResourceCollectionIdentifier,
7-
isSingletonResource,
8-
} from './utils/resourceEvaluation.js';
3+
import { getResourcePathItems, isCustomMethodIdentifier } from './utils/resourceEvaluation.js';
94
import { generateOperationID } from './utils/operationIdGeneration.js';
5+
import { isInvalidListMethod } from './utils/methodLogic.js';
6+
import { hasCustomMethodOverride, hasMethodVerbOverride } from './utils/extensions.js';
107

118
const RULE_NAME = 'xgen-IPA-105-valid-operation-id';
129
const ERROR_MESSAGE = 'Invalid OperationID.';
1310

1411
export default (input, { methodName }, { path, documentInventory }) => {
1512
const resourcePath = path[1];
1613
const oas = documentInventory.resolved;
14+
const resourcePaths = getResourcePathItems(resourcePath, oas.paths);
1715

1816
if (
17+
hasCustomMethodOverride(input) ||
1918
isCustomMethodIdentifier(resourcePath) ||
20-
!isResourceCollectionIdentifier(resourcePath) ||
21-
isSingletonResource(getResourcePathItems(resourcePath, oas.paths))
19+
hasMethodVerbOverride(input, 'get') ||
20+
(isInvalidListMethod(resourcePath, resourcePaths) && !hasMethodVerbOverride(input, methodName))
2221
) {
2322
return;
2423
}

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

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,16 @@
11
import { hasException } from './utils/exceptions.js';
22
import { collectAdoption, collectException, collectAndReturnViolation } from './utils/collectionUtils.js';
3-
import {
4-
isCustomMethodIdentifier,
5-
isResourceCollectionIdentifier,
6-
isSingletonResource,
7-
getResourcePathItems,
8-
} from './utils/resourceEvaluation.js';
3+
import { isCustomMethodIdentifier } from './utils/resourceEvaluation.js';
94
import { generateOperationID } from './utils/operationIdGeneration.js';
5+
import { hasCustomMethodOverride } from './utils/extensions.js';
106

117
const RULE_NAME = 'xgen-IPA-106-valid-operation-id';
128
const ERROR_MESSAGE = 'Invalid OperationID.';
139

14-
export default (input, { methodName }, { path, documentInventory }) => {
10+
export default (input, { methodName }, { path }) => {
1511
const resourcePath = path[1];
16-
const oas = documentInventory.resolved;
17-
const resourcePaths = getResourcePathItems(resourcePath, oas.paths);
1812

19-
const isResourceCollection = isResourceCollectionIdentifier(resourcePath) && !isSingletonResource(resourcePaths);
20-
if (isCustomMethodIdentifier(resourcePath) || !isResourceCollection) {
13+
if (hasCustomMethodOverride(input) || isCustomMethodIdentifier(resourcePath)) {
2114
return;
2215
}
2316

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

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,16 @@
11
import { hasException } from './utils/exceptions.js';
22
import { collectAdoption, collectException, collectAndReturnViolation } from './utils/collectionUtils.js';
3-
import {
4-
isSingleResourceIdentifier,
5-
isResourceCollectionIdentifier,
6-
isSingletonResource,
7-
getResourcePathItems,
8-
isCustomMethodIdentifier,
9-
} from './utils/resourceEvaluation.js';
3+
import { isCustomMethodIdentifier } from './utils/resourceEvaluation.js';
104
import { generateOperationID } from './utils/operationIdGeneration.js';
5+
import { hasCustomMethodOverride } from './utils/extensions.js';
116

127
const RULE_NAME = 'xgen-IPA-107-valid-operation-id';
138
const ERROR_MESSAGE = 'Invalid OperationID.';
149

15-
export default (input, { methodName }, { path, documentInventory }) => {
10+
export default (input, { methodName }, { path }) => {
1611
const resourcePath = path[1];
17-
const oas = documentInventory.resolved;
18-
const resourcePaths = getResourcePathItems(resourcePath, oas.paths);
1912

20-
if (
21-
isCustomMethodIdentifier(resourcePath) ||
22-
(!isSingleResourceIdentifier(resourcePath) &&
23-
!(isResourceCollectionIdentifier(resourcePath) && isSingletonResource(resourcePaths)))
24-
) {
13+
if (isCustomMethodIdentifier(resourcePath) || hasCustomMethodOverride(input)) {
2514
return;
2615
}
2716

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
import { hasException } from './utils/exceptions.js';
22
import { collectAdoption, collectException, collectAndReturnViolation } from './utils/collectionUtils.js';
3-
import { isCustomMethodIdentifier, isSingleResourceIdentifier } from './utils/resourceEvaluation.js';
3+
import { isCustomMethodIdentifier } from './utils/resourceEvaluation.js';
44
import { generateOperationID } from './utils/operationIdGeneration.js';
5+
import { hasCustomMethodOverride } from './utils/extensions.js';
56

67
const RULE_NAME = 'xgen-IPA-108-valid-operation-id';
78
const ERROR_MESSAGE = 'Invalid OperationID.';
89

910
export default (input, { methodName }, { path }) => {
1011
const resourcePath = path[1];
1112

12-
if (isCustomMethodIdentifier(resourcePath) || !isSingleResourceIdentifier(resourcePath)) {
13+
if (isCustomMethodIdentifier(resourcePath) || hasCustomMethodOverride(input)) {
1314
return;
1415
}
1516

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

Lines changed: 48 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,44 +2,68 @@ import { hasException } from './utils/exceptions.js';
22
import { collectAdoption, collectException, collectAndReturnViolation } from './utils/collectionUtils.js';
33
import { isCustomMethodIdentifier, getCustomMethodName, stripCustomMethodName } from './utils/resourceEvaluation.js';
44
import { generateOperationID } from './utils/operationIdGeneration.js';
5+
import { hasMethodWithVerbOverride, hasCustomMethodOverride, VERB_OVERRIDE_EXTENSION } from './utils/extensions.js';
56

67
const RULE_NAME = 'xgen-IPA-109-valid-operation-id';
78
const ERROR_MESSAGE = 'Invalid OperationID.';
89

910
export default (input, _, { path }) => {
1011
let resourcePath = path[1];
11-
const methodName = getCustomMethodName(resourcePath);
1212

13-
if (!isCustomMethodIdentifier(resourcePath)) {
13+
if (!isCustomMethodIdentifier(resourcePath) && !hasMethodWithVerbOverride(input)) {
1414
return;
1515
}
1616

17-
// TODO detect custom method extension - CLOUDP-306294
18-
19-
let obj;
20-
if (input.post) {
21-
obj = input.post;
22-
} else if (input.get) {
23-
obj = input.get;
24-
} else {
17+
if (hasException(input, RULE_NAME)) {
18+
collectException(input, RULE_NAME, path);
2519
return;
2620
}
2721

28-
if (hasException(obj, RULE_NAME)) {
29-
collectException(obj, RULE_NAME, path);
30-
return;
31-
}
22+
if (isCustomMethodIdentifier(resourcePath)) {
23+
let obj;
24+
if (input.post) {
25+
obj = input.post;
26+
} else if (input.get) {
27+
obj = input.get;
28+
} else {
29+
return;
30+
}
3231

33-
const operationId = obj.operationId;
34-
const expectedOperationID = generateOperationID(methodName, stripCustomMethodName(resourcePath));
35-
if (expectedOperationID !== operationId) {
36-
const errors = [
37-
{
38-
path,
39-
message: `${ERROR_MESSAGE} Found ${operationId}, expected ${expectedOperationID}.`,
40-
},
41-
];
42-
return collectAndReturnViolation(path, RULE_NAME, errors);
32+
const operationId = obj.operationId;
33+
const expectedOperationID = generateOperationID(
34+
getCustomMethodName(resourcePath),
35+
stripCustomMethodName(resourcePath)
36+
);
37+
if (operationId !== expectedOperationID) {
38+
const errors = [
39+
{
40+
path,
41+
message: `${ERROR_MESSAGE} Found ${operationId}, expected ${expectedOperationID}.`,
42+
},
43+
];
44+
return collectAndReturnViolation(path, RULE_NAME, errors);
45+
}
46+
} else if (hasMethodWithVerbOverride(input)) {
47+
const methods = Object.values(input);
48+
let errors = [];
49+
methods.forEach((method) => {
50+
if (hasCustomMethodOverride(method)) {
51+
const operationId = method.operationId;
52+
const expectedOperationID = generateOperationID(method[VERB_OVERRIDE_EXTENSION].verb, resourcePath);
53+
if (operationId !== expectedOperationID) {
54+
errors.push({
55+
path,
56+
message: `${ERROR_MESSAGE} Found ${operationId}, expected ${expectedOperationID}.`,
57+
});
58+
}
59+
}
60+
});
61+
62+
if (errors.length !== 0) {
63+
return collectAndReturnViolation(path, RULE_NAME, errors);
64+
}
65+
} else {
66+
return;
4367
}
4468

4569
collectAdoption(path, RULE_NAME);
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
export const VERB_OVERRIDE_EXTENSION = 'x-xgen-method-verb-override';
2+
3+
/**
4+
* Checks if the endpoint has a method with an extension "x-xgen-method-verb-override"
5+
*
6+
* @param endpoint the endpoint to evaluate
7+
* @returns {boolean} true if the endpoint has a nested method with the extension, otherwise false
8+
*/
9+
export function hasMethodWithVerbOverride(endpoint) {
10+
return Object.values(endpoint).some(hasVerbOverride);
11+
}
12+
13+
/**
14+
* Checks if the object has an extension "x-xgen-method-verb-override" with the customMethod boolean set to true
15+
*
16+
* @param object the object to evaluate
17+
* @returns {boolean} true if the object has an extension with customMethod=True, otherwise false
18+
*/
19+
export function hasCustomMethodOverride(object) {
20+
return hasVerbOverride(object) && object[VERB_OVERRIDE_EXTENSION].customMethod;
21+
}
22+
23+
/**
24+
* Checks if the object has an extension "x-xgen-method-verb-override" with the verb set to a specific verb
25+
*
26+
* @param object the object to evaluate
27+
* @param verb the verb to inspect the extension for
28+
* @returns {boolean} true if the object has the extension with the given verb, otherwise false
29+
*/
30+
export function hasMethodVerbOverride(object, verb) {
31+
return hasVerbOverride(object) && object[VERB_OVERRIDE_EXTENSION].verb === verb;
32+
}
33+
34+
/**
35+
* Checks if the object has an extension "x-xgen-method-verb-override"
36+
*
37+
* @param object the object to evaluate
38+
* @returns {boolean} true if the object has the extension, otherwise false
39+
*/
40+
function hasVerbOverride(object) {
41+
if (!object[VERB_OVERRIDE_EXTENSION]) {
42+
return false;
43+
}
44+
return true;
45+
}

0 commit comments

Comments
 (0)