Skip to content

Commit 054e36c

Browse files
CLOUDP-302981: Add rule xgen-IPA-104-get-method-response-has-no-input-fields (#496)
1 parent 7ba7b40 commit 054e36c

File tree

4 files changed

+290
-7
lines changed

4 files changed

+290
-7
lines changed
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
import testRule from './__helpers__/testRule';
2+
import { DiagnosticSeverity } from '@stoplight/types';
3+
4+
const componentSchemas = {
5+
schemas: {
6+
ValidSchemaResponse: {
7+
type: 'object',
8+
properties: {
9+
id: {
10+
type: 'string',
11+
readOnly: true,
12+
},
13+
name: {
14+
type: 'string',
15+
},
16+
},
17+
},
18+
InvalidSchemaResponse: {
19+
type: 'object',
20+
properties: {
21+
id: {
22+
type: 'string',
23+
readOnly: true,
24+
},
25+
name: {
26+
type: 'string',
27+
writeOnly: true,
28+
},
29+
},
30+
},
31+
},
32+
};
33+
34+
testRule('xgen-IPA-104-get-method-response-has-no-input-fields', [
35+
{
36+
name: 'valid get responses',
37+
document: {
38+
paths: {
39+
'/resource/{id}': {
40+
get: {
41+
responses: {
42+
200: {
43+
content: {
44+
'application/vnd.atlas.2024-08-05+json': {
45+
schema: {
46+
$ref: '#/components/schemas/ValidSchemaResponse',
47+
},
48+
},
49+
},
50+
},
51+
},
52+
},
53+
},
54+
'/resource/{id}/singleton': {
55+
get: {
56+
responses: {
57+
200: {
58+
content: {
59+
'application/vnd.atlas.2024-08-05+json': {
60+
schema: {
61+
type: 'object',
62+
properties: {
63+
id: {
64+
type: 'string',
65+
readOnly: true,
66+
},
67+
name: {
68+
type: 'string',
69+
},
70+
},
71+
},
72+
},
73+
},
74+
},
75+
},
76+
},
77+
},
78+
},
79+
components: componentSchemas,
80+
},
81+
errors: [],
82+
},
83+
{
84+
name: 'invalid GET with body',
85+
document: {
86+
paths: {
87+
'/resource/{id}': {
88+
get: {
89+
responses: {
90+
200: {
91+
content: {
92+
'application/vnd.atlas.2024-08-05+json': {
93+
schema: {
94+
$ref: '#/components/schemas/InvalidSchemaResponse',
95+
},
96+
},
97+
},
98+
},
99+
},
100+
},
101+
},
102+
'/resource/{id}/singleton': {
103+
get: {
104+
responses: {
105+
200: {
106+
content: {
107+
'application/vnd.atlas.2024-08-05+json': {
108+
schema: {
109+
type: 'object',
110+
properties: {
111+
id: {
112+
type: 'string',
113+
readOnly: true,
114+
},
115+
name: {
116+
type: 'string',
117+
writeOnly: true,
118+
},
119+
},
120+
},
121+
},
122+
},
123+
},
124+
},
125+
},
126+
},
127+
},
128+
components: componentSchemas,
129+
},
130+
errors: [
131+
{
132+
code: 'xgen-IPA-104-get-method-response-has-no-input-fields',
133+
message:
134+
'The get method response object must not include output fields (writeOnly properties). http://go/ipa/104',
135+
path: [
136+
'paths',
137+
'/resource/{id}',
138+
'get',
139+
'responses',
140+
'200',
141+
'content',
142+
'application/vnd.atlas.2024-08-05+json',
143+
],
144+
severity: DiagnosticSeverity.Warning,
145+
},
146+
{
147+
code: 'xgen-IPA-104-get-method-response-has-no-input-fields',
148+
message:
149+
'The get method response object must not include output fields (writeOnly properties). http://go/ipa/104',
150+
path: [
151+
'paths',
152+
'/resource/{id}/singleton',
153+
'get',
154+
'responses',
155+
'200',
156+
'content',
157+
'application/vnd.atlas.2024-08-05+json',
158+
],
159+
severity: DiagnosticSeverity.Warning,
160+
},
161+
],
162+
},
163+
{
164+
name: 'invalid with exception',
165+
document: {
166+
paths: {
167+
'/resource/{id}': {
168+
get: {
169+
responses: {
170+
200: {
171+
content: {
172+
'application/vnd.atlas.2024-08-05+json': {
173+
'x-xgen-IPA-exception': {
174+
'xgen-IPA-104-get-method-response-has-no-input-fields': 'reason',
175+
},
176+
schema: {
177+
$ref: '#/components/schemas/InvalidSchemaResponse',
178+
},
179+
},
180+
},
181+
},
182+
},
183+
},
184+
},
185+
'/resource/{id}/singleton': {
186+
get: {
187+
responses: {
188+
200: {
189+
content: {
190+
'application/vnd.atlas.2024-08-05+json': {
191+
'x-xgen-IPA-exception': {
192+
'xgen-IPA-104-get-method-response-has-no-input-fields': 'reason',
193+
},
194+
schema: {
195+
type: 'object',
196+
properties: {
197+
id: {
198+
type: 'string',
199+
readOnly: true,
200+
},
201+
name: {
202+
type: 'string',
203+
writeOnly: true,
204+
},
205+
},
206+
},
207+
},
208+
},
209+
},
210+
},
211+
},
212+
},
213+
},
214+
components: componentSchemas,
215+
},
216+
errors: [],
217+
},
218+
]);

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ functions:
66
- getMethodReturnsSingleResource
77
- getMethodReturnsResponseSuffixedObject
88
- getResponseCodeShouldBe200OK
9+
- getMethodResponseHasNoInputFields
910
- getMethodHasNoRequestBody
1011

1112
rules:
@@ -39,6 +40,14 @@ rules:
3940
then:
4041
field: '@key'
4142
function: 'getMethodReturnsResponseSuffixedObject'
43+
xgen-IPA-104-get-method-response-has-no-input-fields:
44+
description: 'The Get method response object must not include writeOnly properties (fields that should be used only on creation or update, ie output fields). http://go/ipa/104'
45+
message: '{{error}} http://go/ipa/104'
46+
severity: warn
47+
given: '$.paths[*].get.responses[*].content'
48+
then:
49+
field: '@key'
50+
function: 'getMethodResponseHasNoInputFields'
4251
xgen-IPA-104-get-method-no-request-body:
4352
description: 'The Get method request must not include a body. http://go/ipa/104'
4453
message: '{{error}} http://go/ipa/104'

tools/spectral/ipa/rulesets/README.md

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,14 @@ 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 |
34-
| xgen-IPA-104-get-method-returns-single-resource | The purpose of the Get method is to return data from a single resource. http://go/ipa/104 | warn |
35-
| xgen-IPA-104-get-method-response-code-is-200 | The Get method must return a 200 OK response. http://go/ipa/104 | warn |
36-
| xgen-IPA-104-get-method-returns-response-suffixed-object | The Get method of a resource should return a "Response" suffixed object. http://go/ipa/104 | warn |
37-
| xgen-IPA-104-get-method-no-request-body | The Get method request must not include a body. 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-method-returns-single-resource | The purpose of the Get method is to return data from a single resource. http://go/ipa/104 | warn |
35+
| xgen-IPA-104-get-method-response-code-is-200 | The Get method must return a 200 OK response. http://go/ipa/104 | warn |
36+
| xgen-IPA-104-get-method-returns-response-suffixed-object | The Get method of a resource should return a "Response" suffixed object. http://go/ipa/104 | warn |
37+
| xgen-IPA-104-get-method-response-has-no-input-fields | The Get method response object must not include writeOnly properties (fields that should be used only on creation or update, ie output fields). http://go/ipa/104 | warn |
38+
| xgen-IPA-104-get-method-no-request-body | The Get method request must not include a body. http://go/ipa/104 | warn |
3839

3940
### IPA-106
4041

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { hasException } from './utils/exceptions.js';
2+
import { collectAdoption, collectAndReturnViolation, collectException } from './utils/collectionUtils.js';
3+
import {
4+
getResourcePathItems,
5+
isResourceCollectionIdentifier,
6+
isSingleResourceIdentifier,
7+
isSingletonResource,
8+
} from './utils/resourceEvaluation.js';
9+
import { resolveObject } from './utils/componentUtils.js';
10+
11+
const RULE_NAME = 'xgen-IPA-104-get-method-response-has-no-input-fields';
12+
const ERROR_MESSAGE = 'The get method response object must not include output fields (writeOnly properties).';
13+
14+
export default (input, _, { path, documentInventory }) => {
15+
const resourcePath = path[1];
16+
const responseCode = path[4];
17+
const oas = documentInventory.resolved;
18+
const resourcePaths = getResourcePathItems(resourcePath, oas.paths);
19+
const contentPerMediaType = resolveObject(oas, path);
20+
21+
if (
22+
!contentPerMediaType.schema ||
23+
!responseCode.startsWith('2') ||
24+
!input.endsWith('json') ||
25+
(!isSingleResourceIdentifier(resourcePath) &&
26+
!(isResourceCollectionIdentifier(resourcePath) && isSingletonResource(resourcePaths)))
27+
) {
28+
return;
29+
}
30+
31+
if (hasException(contentPerMediaType, RULE_NAME)) {
32+
collectException(contentPerMediaType, RULE_NAME, path);
33+
return;
34+
}
35+
36+
const errors = checkViolationsAndReturnErrors(contentPerMediaType, path);
37+
38+
if (errors.length !== 0) {
39+
return collectAndReturnViolation(path, RULE_NAME, errors);
40+
}
41+
return collectAdoption(path, RULE_NAME);
42+
};
43+
44+
function checkViolationsAndReturnErrors(contentPerMediaType, path) {
45+
const schema = contentPerMediaType.schema;
46+
const properties = schema.properties;
47+
if (properties) {
48+
for (const [value] of Object.entries(properties)) {
49+
if (properties[value].writeOnly) {
50+
return [{ path, message: ERROR_MESSAGE }];
51+
}
52+
}
53+
}
54+
return [];
55+
}

0 commit comments

Comments
 (0)