Skip to content

Commit 1c2fa35

Browse files
CLOUDP-271988: IPA-102: Validate paths follow expected format (#312)
1 parent e271f57 commit 1c2fa35

File tree

8 files changed

+213
-10
lines changed

8 files changed

+213
-10
lines changed

.prettierignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@
44
*.yaml
55
*.yml
66
*.html
7+
!/tools/spectral/ipa/**/*.yaml
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import testRule from './__helpers__/testRule';
2+
import { DiagnosticSeverity } from '@stoplight/types';
3+
4+
testRule('xgen-IPA-102-path-alternate-resource-name-path-param', [
5+
{
6+
name: 'valid paths - api/atlas/v2',
7+
document: {
8+
paths: {
9+
'/api/atlas/v2/resourceName': {},
10+
'/api/atlas/v2/resourceName/{pathParam}': {},
11+
'/api/atlas/v2/resourceName1/{pathParam}/resourceName2': {},
12+
'/api/atlas/v2/resourceName1/{pathParam1p}/resourceName2/{pathParam2}': {},
13+
'/api/atlas/v2/resourceName/{pathParam}:method': {},
14+
'/api/atlas/v2/custom:method': {},
15+
'/api/atlas/v2': {},
16+
},
17+
},
18+
errors: [],
19+
},
20+
{
21+
name: 'valid paths - api/atlas/v2/unauth',
22+
document: {
23+
paths: {
24+
'/api/atlas/v2/unauth/resourceName': {},
25+
'/api/atlas/v2/unauth/resourceName/{pathParam}': {},
26+
'/api/atlas/v2/unauth/resourceName1/{pathParam}/resourceName2': {},
27+
'/api/atlas/v2/unauth/resourceName1/{pathParam1p}/resourceName2/{pathParam2}': {},
28+
'/api/atlas/v2/unauth/resourceName/{pathParam}:method': {},
29+
'/api/atlas/v2/unauth/custom:method': {},
30+
'/api/atlas/v2/unauth': {},
31+
},
32+
},
33+
errors: [],
34+
},
35+
{
36+
name: 'invalid paths - api/atlas/v2',
37+
document: {
38+
paths: {
39+
'/api/atlas/v2/resourceName1/resourceName2': {},
40+
'/api/atlas/v2/resourceName/{pathParam1}/{pathParam2}': {},
41+
'/api/atlas/v2/resourceName1/{pathParam1}/resourceName2/resourceName3': {},
42+
'/api/atlas/v2/resourceName1/{pathParam1}/resourceName2/{pathParam2}/{pathParam3}': {},
43+
'/api/atlas/v2/{pathParam}': {},
44+
'/api/atlas/v2/{pathParam1}/{pathParam2}': {},
45+
},
46+
},
47+
errors: [
48+
{
49+
code: 'xgen-IPA-102-path-alternate-resource-name-path-param',
50+
message: 'API paths must alternate between resource name and path params. http://go/ipa/102',
51+
path: ['paths', '/api/atlas/v2/resourceName1/resourceName2'],
52+
severity: DiagnosticSeverity.Warning,
53+
},
54+
{
55+
code: 'xgen-IPA-102-path-alternate-resource-name-path-param',
56+
message: 'API paths must alternate between resource name and path params. http://go/ipa/102',
57+
path: ['paths', '/api/atlas/v2/resourceName/{pathParam1}/{pathParam2}'],
58+
severity: DiagnosticSeverity.Warning,
59+
},
60+
{
61+
code: 'xgen-IPA-102-path-alternate-resource-name-path-param',
62+
message: 'API paths must alternate between resource name and path params. http://go/ipa/102',
63+
path: ['paths', '/api/atlas/v2/resourceName1/{pathParam1}/resourceName2/resourceName3'],
64+
severity: DiagnosticSeverity.Warning,
65+
},
66+
{
67+
code: 'xgen-IPA-102-path-alternate-resource-name-path-param',
68+
message: 'API paths must alternate between resource name and path params. http://go/ipa/102',
69+
path: ['paths', '/api/atlas/v2/resourceName1/{pathParam1}/resourceName2/{pathParam2}/{pathParam3}'],
70+
severity: DiagnosticSeverity.Warning,
71+
},
72+
{
73+
code: 'xgen-IPA-102-path-alternate-resource-name-path-param',
74+
message: 'API paths must alternate between resource name and path params. http://go/ipa/102',
75+
path: ['paths', '/api/atlas/v2/{pathParam}'],
76+
severity: DiagnosticSeverity.Warning,
77+
},
78+
{
79+
code: 'xgen-IPA-102-path-alternate-resource-name-path-param',
80+
message: 'API paths must alternate between resource name and path params. http://go/ipa/102',
81+
path: ['paths', '/api/atlas/v2/{pathParam1}/{pathParam2}'],
82+
severity: DiagnosticSeverity.Warning,
83+
},
84+
],
85+
},
86+
{
87+
name: 'invalid paths - api/atlas/v2/unauth',
88+
document: {
89+
paths: {
90+
'/api/atlas/v2/unauth/resourceName1/resourceName2': {},
91+
'/api/atlas/v2/unauth/resourceName/{pathParam1}/{pathParam2}': {},
92+
'/api/atlas/v2/unauth/resourceName1/{pathParam1}/resourceName2/resourceName3': {},
93+
'/api/atlas/v2/unauth/resourceName1/{pathParam1}/resourceName2/{pathParam2}/{pathParam3}': {},
94+
'/api/atlas/v2/unauth/{pathParam}': {},
95+
'/api/atlas/v2/unauth/{pathParam1}/{pathParam2}': {},
96+
},
97+
},
98+
errors: [
99+
{
100+
code: 'xgen-IPA-102-path-alternate-resource-name-path-param',
101+
message: 'API paths must alternate between resource name and path params. http://go/ipa/102',
102+
path: ['paths', '/api/atlas/v2/unauth/resourceName1/resourceName2'],
103+
severity: DiagnosticSeverity.Warning,
104+
},
105+
{
106+
code: 'xgen-IPA-102-path-alternate-resource-name-path-param',
107+
message: 'API paths must alternate between resource name and path params. http://go/ipa/102',
108+
path: ['paths', '/api/atlas/v2/unauth/resourceName/{pathParam1}/{pathParam2}'],
109+
severity: DiagnosticSeverity.Warning,
110+
},
111+
{
112+
code: 'xgen-IPA-102-path-alternate-resource-name-path-param',
113+
message: 'API paths must alternate between resource name and path params. http://go/ipa/102',
114+
path: ['paths', '/api/atlas/v2/unauth/resourceName1/{pathParam1}/resourceName2/resourceName3'],
115+
severity: DiagnosticSeverity.Warning,
116+
},
117+
{
118+
code: 'xgen-IPA-102-path-alternate-resource-name-path-param',
119+
message: 'API paths must alternate between resource name and path params. http://go/ipa/102',
120+
path: ['paths', '/api/atlas/v2/unauth/resourceName1/{pathParam1}/resourceName2/{pathParam2}/{pathParam3}'],
121+
severity: DiagnosticSeverity.Warning,
122+
},
123+
{
124+
code: 'xgen-IPA-102-path-alternate-resource-name-path-param',
125+
message: 'API paths must alternate between resource name and path params. http://go/ipa/102',
126+
path: ['paths', '/api/atlas/v2/unauth/{pathParam}'],
127+
severity: DiagnosticSeverity.Warning,
128+
},
129+
{
130+
code: 'xgen-IPA-102-path-alternate-resource-name-path-param',
131+
message: 'API paths must alternate between resource name and path params. http://go/ipa/102',
132+
path: ['paths', '/api/atlas/v2/unauth/{pathParam1}/{pathParam2}'],
133+
severity: DiagnosticSeverity.Warning,
134+
},
135+
],
136+
},
137+
]);

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -95,31 +95,31 @@ testRule('xgen-IPA-104-resource-has-GET', [
9595
errors: [
9696
{
9797
code: 'xgen-IPA-104-resource-has-GET',
98-
message: 'APIs must provide a get method for resources. http://go/ipa/117',
98+
message: 'APIs must provide a get method for resources. http://go/ipa/104',
9999
path: ['paths', '/standard'],
100100
severity: DiagnosticSeverity.Warning,
101101
},
102102
{
103103
code: 'xgen-IPA-104-resource-has-GET',
104-
message: 'APIs must provide a get method for resources. http://go/ipa/117',
104+
message: 'APIs must provide a get method for resources. http://go/ipa/104',
105105
path: ['paths', '/standard/{exampleId}/nested'],
106106
severity: DiagnosticSeverity.Warning,
107107
},
108108
{
109109
code: 'xgen-IPA-104-resource-has-GET',
110-
message: 'APIs must provide a get method for resources. http://go/ipa/117',
110+
message: 'APIs must provide a get method for resources. http://go/ipa/104',
111111
path: ['paths', '/standard/{exampleId}/nestedSingleton'],
112112
severity: DiagnosticSeverity.Warning,
113113
},
114114
{
115115
code: 'xgen-IPA-104-resource-has-GET',
116-
message: 'APIs must provide a get method for resources. http://go/ipa/117',
116+
message: 'APIs must provide a get method for resources. http://go/ipa/104',
117117
path: ['paths', '/custom'],
118118
severity: DiagnosticSeverity.Warning,
119119
},
120120
{
121121
code: 'xgen-IPA-104-resource-has-GET',
122-
message: 'APIs must provide a get method for resources. http://go/ipa/117',
122+
message: 'APIs must provide a get method for resources. http://go/ipa/104',
123123
path: ['paths', '/singleton'],
124124
severity: DiagnosticSeverity.Warning,
125125
},
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
extends:
2+
- ./rulesets/IPA-102.yaml
23
- ./rulesets/IPA-104.yaml
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# IPA-102: Resource Identifiers
2+
# http://go/ipa/102
3+
4+
functions:
5+
- eachPathAlternatesBetweenResourceNameAndPathParam
6+
7+
rules:
8+
xgen-IPA-102-path-alternate-resource-name-path-param:
9+
description: 'Paths should alternate between resource names and path params. http://go/ipa/102'
10+
message: '{{error}} http://go/ipa/102'
11+
severity: warn
12+
given: '$.paths'
13+
then:
14+
field: '@key'
15+
function: 'eachPathAlternatesBetweenResourceNameAndPathParam'

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ functions:
66

77
rules:
88
xgen-IPA-104-resource-has-GET:
9-
description: "APIs must provide a get method for resources. http://go/ipa/104"
10-
message: "{{error}} http://go/ipa/117"
9+
description: 'APIs must provide a get method for resources. http://go/ipa/104'
10+
message: '{{error}} http://go/ipa/104'
1111
severity: warn
12-
given: "$.paths"
12+
given: '$.paths'
1313
then:
14-
field: "@key"
15-
function: "eachResourceHasGetMethod"
14+
field: '@key'
15+
function: 'eachResourceHasGetMethod'
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { isPathParam } from './utils/pathUtils';
2+
3+
const ERROR_MESSAGE = 'API paths must alternate between resource name and path params.';
4+
const ERROR_RESULT = [{ message: ERROR_MESSAGE }];
5+
const AUTH_PREFIX = '/api/atlas/v2';
6+
const UNAUTH_PREFIX = '/api/atlas/v2/unauth';
7+
8+
const getPrefix = (path) => {
9+
if (path.includes(UNAUTH_PREFIX)) return UNAUTH_PREFIX;
10+
if (path.includes(AUTH_PREFIX)) return AUTH_PREFIX;
11+
return null;
12+
};
13+
14+
const validatePathStructure = (elements) => {
15+
return elements.every((element, index) => {
16+
const isEvenIndex = index % 2 === 0;
17+
return isEvenIndex ? !isPathParam(element) : isPathParam(element);
18+
});
19+
};
20+
21+
export default (input) => {
22+
const prefix = getPrefix(input);
23+
if (!prefix) return;
24+
25+
let suffixWithLeadingSlash = input.slice(prefix.length);
26+
if (suffixWithLeadingSlash.length === 0) {
27+
return;
28+
}
29+
30+
let suffix = suffixWithLeadingSlash.slice(1);
31+
let elements = suffix.split('/');
32+
if (!validatePathStructure(elements)) {
33+
return ERROR_RESULT;
34+
}
35+
};
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/**
2+
* Checks if a string belongs to a path parameter or a path parameter with a custom method.
3+
*
4+
* A path parameter has the format: `{paramName}`
5+
* A path parameter with a custom method has the format: `{paramName}:customMethod`
6+
*
7+
* @param {string} str - A string extracted from a path split by slashes.
8+
* @returns {boolean} True if the string matches the expected formats, false otherwise.
9+
*/
10+
export function isPathParam(str) {
11+
const pathParamRegEx = new RegExp(`^{[a-z][a-zA-Z0-9]*}$`);
12+
const pathParamWithCustomMethodRegEx = new RegExp(`^{[a-z][a-zA-Z0-9]*}:[a-z][a-zA-Z0-9]*$`);
13+
return pathParamRegEx.test(str) || pathParamWithCustomMethodRegEx.test(str);
14+
}

0 commit comments

Comments
 (0)