Skip to content

Commit d6ae9be

Browse files
CLOUDP-287247: IPA-109: Validate custom method must use colon (:) followed by the custom verb (#328)
1 parent 8162fb7 commit d6ae9be

File tree

8 files changed

+152
-0
lines changed

8 files changed

+152
-0
lines changed

package-lock.json

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"dependencies": {
2020
"@stoplight/spectral-cli": "^6.14.2",
2121
"@stoplight/spectral-core": "^1.19.4",
22+
"@stoplight/spectral-functions": "^1.9.3",
2223
"@stoplight/spectral-ref-resolver": "^1.0.5",
2324
"@stoplight/spectral-ruleset-bundler": "^1.6.1",
2425
"eslint-plugin-jest": "^28.9.0",

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,28 @@ testRule('xgen-IPA-109-custom-method-must-be-GET-or-POST', [
3030
},
3131
errors: [],
3232
},
33+
{
34+
name: 'invalid methods with exception',
35+
document: {
36+
paths: {
37+
'/d/{exampleId}:method': {
38+
get: {},
39+
post: {},
40+
'x-xgen-IPA-exception': {
41+
'xgen-IPA-109-custom-method-must-be-GET-or-POST': {},
42+
},
43+
},
44+
'/d:method': {
45+
get: {},
46+
post: {},
47+
'x-xgen-IPA-exception': {
48+
'xgen-IPA-109-custom-method-must-be-GET-or-POST': {},
49+
},
50+
},
51+
},
52+
},
53+
errors: [],
54+
},
3355
{
3456
name: 'invalid methods',
3557
document: {
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import testRule from './__helpers__/testRule';
2+
import { DiagnosticSeverity } from '@stoplight/types';
3+
4+
testRule('xgen-IPA-109-custom-method-must-use-camel-case', [
5+
{
6+
name: 'valid methods',
7+
document: {
8+
paths: {
9+
'/a/{exampleId}:methodName': {},
10+
'/a:methodName': {},
11+
},
12+
},
13+
errors: [],
14+
},
15+
{
16+
name: 'invalid methods with exception',
17+
document: {
18+
paths: {
19+
'/b/{exampleId}:MethodName': {
20+
'x-xgen-IPA-exception': {
21+
'xgen-IPA-109-custom-method-must-use-camel-case': {},
22+
},
23+
},
24+
'/b:MethodName': {
25+
'x-xgen-IPA-exception': {
26+
'xgen-IPA-109-custom-method-must-use-camel-case': {},
27+
},
28+
},
29+
},
30+
},
31+
errors: [],
32+
},
33+
{
34+
name: 'invalid methods',
35+
document: {
36+
paths: {
37+
'/a/{exampleId}:MethodName': {},
38+
'/a:MethodName': {},
39+
'/a/{exampleId}:method_name': {},
40+
'/a:method_name': {},
41+
'/a/{exampleId}:': {},
42+
'/a:': {},
43+
},
44+
},
45+
errors: [
46+
{
47+
code: 'xgen-IPA-109-custom-method-must-use-camel-case',
48+
message: 'MethodName must use camelCase format. http://go/ipa/109',
49+
path: ['paths', '/a/{exampleId}:MethodName'],
50+
severity: DiagnosticSeverity.Warning,
51+
},
52+
{
53+
code: 'xgen-IPA-109-custom-method-must-use-camel-case',
54+
message: 'MethodName must use camelCase format. http://go/ipa/109',
55+
path: ['paths', '/a:MethodName'],
56+
severity: DiagnosticSeverity.Warning,
57+
},
58+
{
59+
code: 'xgen-IPA-109-custom-method-must-use-camel-case',
60+
message: 'method_name must use camelCase format. http://go/ipa/109',
61+
path: ['paths', '/a/{exampleId}:method_name'],
62+
severity: DiagnosticSeverity.Warning,
63+
},
64+
{
65+
code: 'xgen-IPA-109-custom-method-must-use-camel-case',
66+
message: 'method_name must use camelCase format. http://go/ipa/109',
67+
path: ['paths', '/a:method_name'],
68+
severity: DiagnosticSeverity.Warning,
69+
},
70+
{
71+
code: 'xgen-IPA-109-custom-method-must-use-camel-case',
72+
message: 'Custom method name cannot be empty or blank. http://go/ipa/109',
73+
path: ['paths', '/a/{exampleId}:'],
74+
severity: DiagnosticSeverity.Warning,
75+
},
76+
{
77+
code: 'xgen-IPA-109-custom-method-must-use-camel-case',
78+
message: 'Custom method name cannot be empty or blank. http://go/ipa/109',
79+
path: ['paths', '/a:'],
80+
severity: DiagnosticSeverity.Warning,
81+
},
82+
],
83+
},
84+
]);

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
functions:
55
- eachCustomMethodMustBeGetOrPost
6+
- eachCustomMethodMustUseCamelCase
67

78
rules:
89
xgen-IPA-109-custom-method-must-be-GET-or-POST:
@@ -12,3 +13,11 @@ rules:
1213
given: '$.paths[*]'
1314
then:
1415
function: 'eachCustomMethodMustBeGetOrPost'
16+
17+
xgen-IPA-109-custom-method-must-use-camel-case:
18+
description: 'The custom method must use camelCase format. http://go/ipa/109'
19+
message: '{{error}} http://go/ipa/109'
20+
severity: warn
21+
given: '$.paths[*]'
22+
then:
23+
function: 'eachCustomMethodMustUseCamelCase'

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { isCustomMethod } from './utils/resourceEvaluation.js';
2+
import { hasException } from './utils/exceptions.js';
23

4+
const RULE_NAME = 'xgen-IPA-109-custom-method-must-be-GET-or-POST';
35
const ERROR_MESSAGE = 'The HTTP method for custom methods must be GET or POST.';
46
const ERROR_RESULT = [{ message: ERROR_MESSAGE }];
57
const VALID_METHODS = ['get', 'post'];
@@ -11,6 +13,10 @@ export default (input, opts, { path }) => {
1113

1214
if (!isCustomMethod(pathKey)) return;
1315

16+
if (hasException(input, RULE_NAME)) {
17+
return;
18+
}
19+
1420
//Extract the keys which are equivalent of the http methods
1521
let keys = Object.keys(input);
1622
const httpMethods = keys.filter((key) => HTTP_METHODS.includes(key));
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { getCustomMethodName, isCustomMethod } from './utils/resourceEvaluation.js';
2+
import { hasException } from './utils/exceptions.js';
3+
import { casing } from '@stoplight/spectral-functions';
4+
5+
const RULE_NAME = 'xgen-IPA-109-custom-method-must-use-camel-case';
6+
7+
export default (input, opts, { path }) => {
8+
// Extract the path key (e.g., '/a/{exampleId}:method') from the JSONPath.
9+
let pathKey = path[1];
10+
11+
if (!isCustomMethod(pathKey)) return;
12+
13+
if (hasException(input, RULE_NAME)) {
14+
return;
15+
}
16+
17+
let methodName = getCustomMethodName(pathKey);
18+
if (methodName.length === 0 || methodName.trim().length === 0) {
19+
return [{ message: 'Custom method name cannot be empty or blank.' }];
20+
}
21+
22+
if (casing(methodName, { type: 'camel', disallowDigits: true })) {
23+
return [{ message: `${methodName} must use camelCase format.` }];
24+
}
25+
};

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ export function isCustomMethod(path) {
66
return path.includes(':');
77
}
88

9+
export function getCustomMethodName(path) {
10+
return path.split(':')[1];
11+
}
12+
913
/**
1014
* Checks if a resource is a singleton resource ({@link https://docs.devprod.prod.corp.mongodb.com/ipa/113 IPA-113}) based on the paths for the
1115
* resource. The resource may have custom methods. Use {@link getResourcePaths} to get all paths of a resource.

0 commit comments

Comments
 (0)