Skip to content

Commit a229fc0

Browse files
author
Dustin Popp
committed
feat: flag operations with non-form request bodies that do not specify a name
1 parent cf64905 commit a229fc0

File tree

4 files changed

+131
-9
lines changed

4 files changed

+131
-9
lines changed

README.md

+2
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ The supported rules are described below:
183183
| no_array_responses | Flag any operations with a top-level array response. | shared |
184184
| parameter_order | Flag any operations with optional parameters before a required param. | shared |
185185
| no_request_body_content | [Flag any operations with a `requestBody` that does not have a `content` field.][3] | oas3 |
186+
| no_request_body_name | Flag any operations with a non-form `requestBody` that does not have a name set with `x-codegen-request-body-name`. | oas3 |
186187

187188
##### parameters
188189
| Rule | Description | Spec |
@@ -307,6 +308,7 @@ The default values for each rule are described below.
307308
| Rule | Default |
308309
| --------------------------- | --------|
309310
| no_request_body_content | error |
311+
| no_request_body_name | error |
310312

311313
###### parameters
312314
| Rule | Default |

src/.defaultsForValidator.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,8 @@ const defaults = {
7373
},
7474
'oas3': {
7575
'operations': {
76-
'no_request_body_content': 'error'
76+
'no_request_body_content': 'error',
77+
'no_request_body_name': 'warning'
7778
},
7879
'parameters': {
7980
'no_in_property': 'error',

src/plugins/validation/oas3/semantic-validators/operations.js

+34-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
// Assertation 1. Request body objects must have a `content` property
22
// https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#requestBodyObject
33

4+
// Assertation 2. Operations with non-form request bodies should set the `x-codegen-request-body-name`
5+
// annotation (for code generation purposes)
6+
47
const pick = require('lodash/pick');
58
const each = require('lodash/each');
69

@@ -11,6 +14,8 @@ module.exports.validate = function({ resolvedSpec }, config) {
1114

1215
config = config.operations;
1316

17+
const REQUEST_BODY_NAME = 'x-codegen-request-body-name';
18+
1419
// get, head, and delete are not in this list because they are not allowed
1520
// to have request bodies
1621
const allowedOps = ['post', 'put', 'patch', 'options', 'trace'];
@@ -24,18 +29,45 @@ module.exports.validate = function({ resolvedSpec }, config) {
2429
// Assertation 1
2530
if (op.requestBody) {
2631
const requestBodyContent = op.requestBody.content;
27-
if (!requestBodyContent || !Object.keys(requestBodyContent).length) {
32+
const requestBodyMimeTypes =
33+
op.requestBody.content && Object.keys(requestBodyContent);
34+
if (!requestBodyContent || !requestBodyMimeTypes.length) {
2835
const checkStatus = config.no_request_body_content;
2936
if (checkStatus !== 'off') {
30-
result.error.push({
37+
result[checkStatus].push({
3138
path: `paths.${pathName}.${opName}.requestBody`,
3239
message: 'Request bodies MUST specify a `content` property'
3340
});
3441
}
42+
} else {
43+
// request body has content
44+
const firstMimeType = requestBodyMimeTypes[0]; // code generation uses the first mime type
45+
const hasRequestBodyName =
46+
op[REQUEST_BODY_NAME] && op[REQUEST_BODY_NAME].trim().length;
47+
if (isBodyParameter(firstMimeType) && !hasRequestBodyName) {
48+
const checkStatus = config.no_request_body_name;
49+
if (checkStatus != 'off') {
50+
const message =
51+
'Operations with non-form request bodies should set a name with the x-codegen-request-body-name annotation.';
52+
result[checkStatus].push({
53+
path: `paths.${pathName}.${opName}`,
54+
message
55+
});
56+
}
57+
}
3558
}
3659
}
3760
});
3861
});
3962

4063
return { errors: result.error, warnings: result.warning };
4164
};
65+
66+
function isBodyParameter(mimeType) {
67+
const formDataMimeTypes = [
68+
'multipart/form-data',
69+
'application/x-www-form-urlencoded',
70+
'application/octet-stream'
71+
];
72+
return !formDataMimeTypes.includes(mimeType);
73+
}

test/plugins/validation/oas3/operations.js

+93-6
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,10 @@ const expect = require('expect');
22
const {
33
validate
44
} = require('../../../../src/plugins/validation/oas3/semantic-validators/operations');
5+
const config = require('../../../../src/.defaultsForValidator').defaults.oas3;
56

67
describe('validation plugin - semantic - operations - oas3', function() {
78
it('should complain about a request body not having a content field', function() {
8-
const config = {
9-
operations: {
10-
no_request_body_content: 'error'
11-
}
12-
};
13-
149
const spec = {
1510
paths: {
1611
'/pets': {
@@ -33,4 +28,96 @@ describe('validation plugin - semantic - operations - oas3', function() {
3328
);
3429
expect(res.warnings.length).toEqual(0);
3530
});
31+
32+
it('should warn about an operation with a non-form request body that does not set a name', function() {
33+
const spec = {
34+
paths: {
35+
'/pets': {
36+
post: {
37+
summary: 'this is a summary',
38+
operationId: 'operationId',
39+
requestBody: {
40+
description: 'body for request',
41+
content: {
42+
'application/json': {
43+
schema: {
44+
type: 'string'
45+
}
46+
}
47+
}
48+
}
49+
}
50+
}
51+
}
52+
};
53+
54+
const res = validate({ resolvedSpec: spec }, config);
55+
expect(res.warnings.length).toEqual(1);
56+
expect(res.warnings[0].path).toEqual('paths./pets.post');
57+
expect(res.warnings[0].message).toEqual(
58+
'Operations with non-form request bodies should set a name with the x-codegen-request-body-name annotation.'
59+
);
60+
expect(res.errors.length).toEqual(0);
61+
});
62+
63+
it('should not warn about an operation with a non-form request body that sets a name', function() {
64+
const spec = {
65+
paths: {
66+
'/pets': {
67+
post: {
68+
'x-codegen-request-body-name': 'goodRequestBody',
69+
summary: 'this is a summary',
70+
operationId: 'operationId',
71+
requestBody: {
72+
description: 'body for request',
73+
content: {
74+
'application/json': {
75+
schema: {
76+
type: 'string'
77+
}
78+
}
79+
}
80+
}
81+
}
82+
}
83+
}
84+
};
85+
86+
const res = validate({ resolvedSpec: spec }, config);
87+
expect(res.warnings.length).toEqual(0);
88+
expect(res.errors.length).toEqual(0);
89+
});
90+
91+
// should not warn about a form request body
92+
it('should not warn about an operation with a form request body that does not set a name', function() {
93+
const spec = {
94+
paths: {
95+
'/pets': {
96+
post: {
97+
summary: 'this is a summary',
98+
operationId: 'operationId',
99+
requestBody: {
100+
description: 'body for request',
101+
content: {
102+
'multipart/form-data': {
103+
schema: {
104+
type: 'object',
105+
properties: {
106+
name: {
107+
type: 'string'
108+
}
109+
}
110+
}
111+
}
112+
}
113+
}
114+
}
115+
}
116+
}
117+
};
118+
119+
const res = validate({ resolvedSpec: spec }, config);
120+
expect(res.warnings.length).toEqual(0);
121+
expect(res.errors.length).toEqual(0);
122+
});
36123
});

0 commit comments

Comments
 (0)