Skip to content

Commit c7c7b28

Browse files
CLOUDP-302984: IPA rule Get methods should return data for a single resource
1 parent 1ed84af commit c7c7b28

File tree

8 files changed

+383
-15
lines changed

8 files changed

+383
-15
lines changed
Lines changed: 298 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,298 @@
1+
import testRule from './__helpers__/testRule';
2+
import { DiagnosticSeverity } from '@stoplight/types';
3+
4+
const componentSchemas = {
5+
schemas: {
6+
Schema: {
7+
properties: {
8+
exampleProperty: {
9+
type: 'string',
10+
},
11+
},
12+
},
13+
PaginatedSchema: {
14+
type: 'array',
15+
},
16+
ArraySchema: {
17+
properties: {
18+
results: {
19+
type: 'array',
20+
},
21+
},
22+
},
23+
},
24+
};
25+
26+
testRule('xgen-IPA-104-GET-resource-not-paginated', [
27+
{
28+
name: 'valid resources',
29+
document: {
30+
paths: {
31+
'/resource': {
32+
get: {
33+
responses: {
34+
200: {
35+
content: {
36+
'application/vnd.atlas.2024-08-05+json': {
37+
schema: {
38+
$ref: '#/components/schemas/PaginatedSchema',
39+
},
40+
},
41+
},
42+
},
43+
},
44+
},
45+
},
46+
'/resource/{id}': {
47+
get: {
48+
responses: {
49+
200: {
50+
content: {
51+
'application/vnd.atlas.2024-08-05+json': {
52+
schema: {
53+
$ref: '#/components/schemas/Schema',
54+
},
55+
},
56+
},
57+
},
58+
},
59+
},
60+
},
61+
'/resource/{id}:getAllThings': {
62+
get: {
63+
responses: {
64+
200: {
65+
content: {
66+
'application/vnd.atlas.2024-08-05+json': {
67+
schema: {
68+
$ref: '#/components/schemas/ArraySchema',
69+
},
70+
},
71+
},
72+
},
73+
},
74+
},
75+
},
76+
'/singleton': {
77+
get: {
78+
responses: {
79+
200: {
80+
content: {
81+
'application/vnd.atlas.2024-08-05+json': {
82+
schema: {
83+
$ref: '#/components/schemas/Schema',
84+
},
85+
},
86+
},
87+
},
88+
},
89+
},
90+
},
91+
},
92+
components: componentSchemas,
93+
},
94+
errors: [],
95+
},
96+
{
97+
name: 'invalid resources',
98+
document: {
99+
paths: {
100+
'/arrayResource': {
101+
get: {
102+
responses: {
103+
200: {
104+
content: {
105+
'application/vnd.atlas.2024-08-05+json': {
106+
schema: {
107+
$ref: '#/components/schemas/PaginatedSchema',
108+
},
109+
},
110+
},
111+
},
112+
},
113+
},
114+
},
115+
'/arrayResource/{id}': {
116+
get: {
117+
responses: {
118+
200: {
119+
content: {
120+
'application/vnd.atlas.2024-08-05+json': {
121+
schema: {
122+
$ref: '#/components/schemas/ArraySchema',
123+
},
124+
},
125+
},
126+
},
127+
},
128+
},
129+
},
130+
'/paginatedResource': {
131+
get: {
132+
responses: {
133+
200: {
134+
content: {
135+
'application/vnd.atlas.2024-08-05+json': {
136+
schema: {
137+
$ref: '#/components/schemas/PaginatedSchema',
138+
},
139+
},
140+
},
141+
},
142+
},
143+
},
144+
},
145+
'/paginatedResource/{id}': {
146+
get: {
147+
responses: {
148+
200: {
149+
content: {
150+
'application/vnd.atlas.2024-08-05+json': {
151+
schema: {
152+
$ref: '#/components/schemas/PaginatedSchema',
153+
},
154+
},
155+
},
156+
},
157+
},
158+
},
159+
},
160+
'/arraySingleton': {
161+
get: {
162+
responses: {
163+
200: {
164+
content: {
165+
'application/vnd.atlas.2024-08-05+json': {
166+
schema: {
167+
$ref: '#/components/schemas/ArraySchema',
168+
},
169+
},
170+
},
171+
},
172+
},
173+
},
174+
},
175+
'/paginatedSingleton': {
176+
get: {
177+
responses: {
178+
200: {
179+
content: {
180+
'application/vnd.atlas.2024-08-05+json': {
181+
schema: {
182+
$ref: '#/components/schemas/PaginatedSchema',
183+
},
184+
},
185+
},
186+
},
187+
},
188+
},
189+
},
190+
},
191+
components: componentSchemas,
192+
},
193+
errors: [
194+
{
195+
code: 'xgen-IPA-104-GET-resource-not-paginated',
196+
message:
197+
'Get methods should return data for a single resource. This method returns an array or a paginated response. http://go/ipa/104',
198+
path: [
199+
'paths',
200+
'/arrayResource/{id}',
201+
'get',
202+
'responses',
203+
'200',
204+
'content',
205+
'application/vnd.atlas.2024-08-05+json',
206+
],
207+
severity: DiagnosticSeverity.Warning,
208+
},
209+
{
210+
code: 'xgen-IPA-104-GET-resource-not-paginated',
211+
message:
212+
'Get methods should return data for a single resource. This method returns an array or a paginated response. http://go/ipa/104',
213+
path: [
214+
'paths',
215+
'/paginatedResource/{id}',
216+
'get',
217+
'responses',
218+
'200',
219+
'content',
220+
'application/vnd.atlas.2024-08-05+json',
221+
],
222+
severity: DiagnosticSeverity.Warning,
223+
},
224+
{
225+
code: 'xgen-IPA-104-GET-resource-not-paginated',
226+
message:
227+
'Get methods should return data for a single resource. This method returns an array or a paginated response. http://go/ipa/104',
228+
path: [
229+
'paths',
230+
'/arraySingleton',
231+
'get',
232+
'responses',
233+
'200',
234+
'content',
235+
'application/vnd.atlas.2024-08-05+json',
236+
],
237+
severity: DiagnosticSeverity.Warning,
238+
},
239+
{
240+
code: 'xgen-IPA-104-GET-resource-not-paginated',
241+
message:
242+
'Get methods should return data for a single resource. This method returns an array or a paginated response. http://go/ipa/104',
243+
path: [
244+
'paths',
245+
'/paginatedSingleton',
246+
'get',
247+
'responses',
248+
'200',
249+
'content',
250+
'application/vnd.atlas.2024-08-05+json',
251+
],
252+
severity: DiagnosticSeverity.Warning,
253+
},
254+
],
255+
},
256+
{
257+
name: 'invalid resource with exception',
258+
document: {
259+
paths: {
260+
'/arrayResource': {
261+
get: {
262+
responses: {
263+
200: {
264+
content: {
265+
'application/vnd.atlas.2024-08-05+json': {
266+
schema: {
267+
$ref: '#/components/schemas/PaginatedSchema',
268+
},
269+
},
270+
},
271+
},
272+
},
273+
},
274+
},
275+
'/arrayResource/{id}': {
276+
get: {
277+
responses: {
278+
200: {
279+
content: {
280+
'application/vnd.atlas.2024-08-05+json': {
281+
'x-xgen-IPA-exception': {
282+
'xgen-IPA-104-GET-resource-not-paginated': 'reason',
283+
},
284+
schema: {
285+
$ref: '#/components/schemas/ArraySchema',
286+
},
287+
},
288+
},
289+
},
290+
},
291+
},
292+
},
293+
},
294+
components: componentSchemas,
295+
},
296+
errors: [],
297+
},
298+
]);

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

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

44
functions:
55
- eachResourceHasGetMethod
6+
- getResourceIsNotPaginated
67

78
rules:
89
xgen-IPA-104-resource-has-GET:
@@ -13,3 +14,10 @@ rules:
1314
then:
1415
field: '@key'
1516
function: 'eachResourceHasGetMethod'
17+
xgen-IPA-104-GET-resource-not-paginated:
18+
description: 'The purpose of the get method is to return data from a single resource. http://go/ipa/104'
19+
message: '{{error}} http://go/ipa/104'
20+
severity: warn
21+
given: '$.paths[*].get'
22+
then:
23+
function: 'getResourceIsNotPaginated'

tools/spectral/ipa/rulesets/README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,10 @@ For rule definitions, see [IPA-102.yaml](https://github.com/mongodb/openapi/blob
2828

2929
For rule definitions, see [IPA-104.yaml](https://github.com/mongodb/openapi/blob/main/tools/spectral/ipa/rulesets/IPA-104.yaml).
3030

31-
| Rule Name | Description | Severity |
32-
| ----------------------------- | --------------------------------------------------------------- | -------- |
33-
| xgen-IPA-104-resource-has-GET | APIs must provide a get method for resources. http://go/ipa/104 | warn |
31+
| Rule Name | Description | Severity |
32+
| --------------------------------------- | ----------------------------------------------------------------------------------------- | -------- |
33+
| xgen-IPA-104-resource-has-GET | APIs must provide a get method for resources. http://go/ipa/104 | warn |
34+
| xgen-IPA-104-GET-resource-not-paginated | The purpose of the get method is to return data from a single resource. http://go/ipa/104 | warn |
3435

3536
### IPA-109
3637

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { isChild, isCustomMethod, isSingletonResource, getResourcePaths } from './utils/resourceEvaluation.js';
2+
import { collectAdoption, collectAndReturnViolation, collectException } from './utils/collectionUtils.js';
3+
import { getAllSuccessfulResponseSchemas } from './utils/methodUtils.js';
4+
import { hasException } from './utils/exceptions.js';
5+
import { schemaIsArray, schemaIsPaginated } from './utils/schemaUtils.js';
6+
import { resolveObject } from './utils/componentUtils.js';
7+
8+
const RULE_NAME = 'xgen-IPA-104-GET-resource-not-paginated';
9+
const ERROR_MESSAGE_STANDARD_RESOURCE =
10+
'Get methods should return data for a single resource. This method returns an array or a paginated response.';
11+
12+
export default (input, _, { path, documentInventory }) => {
13+
const oas = documentInventory.resolved;
14+
const resourcePath = path[1];
15+
const resourcePaths = getResourcePaths(resourcePath, Object.keys(oas.paths));
16+
17+
if (isCustomMethod(resourcePath) || (!isChild(resourcePath) && !isSingletonResource(resourcePaths))) {
18+
return;
19+
}
20+
21+
const errors = [];
22+
23+
const responseSchemas = getAllSuccessfulResponseSchemas(input);
24+
responseSchemas.forEach(({ schemaPath, schema }) => {
25+
const fullPath = path.concat(schemaPath);
26+
const responseObject = resolveObject(oas, fullPath);
27+
28+
if (hasException(responseObject, RULE_NAME)) {
29+
collectException(responseObject, RULE_NAME, fullPath);
30+
} else if (schemaIsPaginated(schema) || schemaIsArray(schema)) {
31+
collectAndReturnViolation(fullPath, RULE_NAME, ERROR_MESSAGE_STANDARD_RESOURCE);
32+
errors.push({ path: fullPath, message: ERROR_MESSAGE_STANDARD_RESOURCE });
33+
} else {
34+
collectAdoption(fullPath, RULE_NAME);
35+
}
36+
});
37+
38+
return errors;
39+
};

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
isSingletonResource,
77
} from './utils/resourceEvaluation.js';
88
import { hasException } from './utils/exceptions.js';
9-
import { getAllSuccessfulGetResponseSchemas } from './utils/methodUtils.js';
9+
import { getAllSuccessfulResponseSchemas } from './utils/methodUtils.js';
1010
import { collectAdoption, collectAndReturnViolation, collectException } from './utils/collectionUtils.js';
1111

1212
const RULE_NAME = 'xgen-IPA-113-singleton-must-not-have-id';
@@ -28,8 +28,8 @@ export default (input, opts, { path, documentInventory }) => {
2828
const resourcePaths = getResourcePaths(resourcePath, Object.keys(oas.paths));
2929

3030
if (isSingletonResource(resourcePaths) && hasGetMethod(input)) {
31-
const resourceSchemas = getAllSuccessfulGetResponseSchemas(input);
32-
if (resourceSchemas.some((schema) => schemaHasIdProperty(schema))) {
31+
const resourceSchemas = getAllSuccessfulResponseSchemas(input['get']);
32+
if (resourceSchemas.some(({ schema }) => schemaHasIdProperty(schema))) {
3333
return collectAndReturnViolation(path, RULE_NAME, ERROR_MESSAGE);
3434
}
3535
}

0 commit comments

Comments
 (0)