Skip to content

Commit 4277079

Browse files
authored
fix: dont validate schema properties called 'responses' as responses (#194)
1 parent a8ff44d commit 4277079

File tree

8 files changed

+111
-13
lines changed

8 files changed

+111
-13
lines changed

.travis.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ cache:
66
npm: false
77
script:
88
- npm run test-travis
9+
- npm run lint
910
after_success:
1011
- npm run report-coverage
1112
deploy:

src/plugins/utils/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@
33
module.exports.checkCase = require('./caseConventionCheck');
44
module.exports.walk = require('./walk');
55
module.exports.isParameterObject = require('./isParameter');
6+
module.exports.isResponseObject = require('./is-response');
67
module.exports.hasRefProperty = require('./hasRefProperty');

src/plugins/utils/is-response.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
module.exports = (path, isOAS3 = true) => {
2+
const operations = [
3+
'get',
4+
'put',
5+
'post',
6+
'delete',
7+
'options',
8+
'head',
9+
'patch',
10+
'trace'
11+
];
12+
13+
const pathLength = path.length;
14+
15+
// a necessary but not sufficient check for a response object
16+
// without these, a schema property named "responses"
17+
// may be validated as a response
18+
const isResponsesProperty = path[pathLength - 1] === 'responses';
19+
20+
// three scenarios:
21+
// 1) inside of an operation
22+
const isOperationResponse = operations.includes(path[pathLength - 2]);
23+
24+
// 2) inside of components -> responses (oas 3)
25+
const isResponseInComponents =
26+
pathLength === 2 && path[pathLength - 2] === 'components' && isOAS3;
27+
28+
// 3) top level responses (swagger 2)
29+
const isTopLevelResponse = pathLength === 1 && !isOAS3;
30+
31+
return (
32+
isResponsesProperty &&
33+
(isOperationResponse || isResponseInComponents || isTopLevelResponse)
34+
);
35+
};

src/plugins/validation/2and3/semantic-validators/items-required-for-array-objects.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,23 +28,23 @@ const checkReqProp = function(jsSpec, obj, requiredProp) {
2828
} else if (Array.isArray(obj.anyOf) || Array.isArray(obj.oneOf)) {
2929
const childList = obj.anyOf || obj.oneOf;
3030
let reqPropDefined = true;
31-
childList.forEach((childObj) => {
31+
childList.forEach(childObj => {
3232
if (!checkReqProp(jsSpec, childObj, requiredProp)) {
3333
reqPropDefined = false;
3434
}
3535
});
3636
return reqPropDefined;
3737
} else if (Array.isArray(obj.allOf)) {
3838
let reqPropDefined = false;
39-
obj.allOf.forEach((childObj) => {
39+
obj.allOf.forEach(childObj => {
4040
if (checkReqProp(jsSpec, childObj, requiredProp)) {
4141
reqPropDefined = true;
4242
}
4343
});
4444
return reqPropDefined;
4545
}
4646
return false;
47-
}
47+
};
4848

4949
module.exports.validate = function({ jsSpec }, config) {
5050
const messages = new MessageCarrier();
@@ -71,7 +71,8 @@ module.exports.validate = function({ jsSpec }, config) {
7171
}
7272

7373
// Assertation 2
74-
const undefinedRequiredProperties = config.schemas.undefined_required_properties;
74+
const undefinedRequiredProperties =
75+
config.schemas.undefined_required_properties;
7576
if (Array.isArray(obj.required)) {
7677
obj.required.forEach((requiredProp, i) => {
7778
if (!checkReqProp(jsSpec, obj, requiredProp)) {

src/plugins/validation/2and3/semantic-validators/responses.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
const each = require('lodash/each');
2-
const { walk } = require('../../../utils');
2+
const { walk, isResponseObject } = require('../../../utils');
33
const MessageCarrier = require('../../../utils/messageCarrier');
44

55
const INLINE_SCHEMA_MESSAGE =
@@ -11,10 +11,9 @@ module.exports.validate = function({ jsSpec, isOAS3 }, config) {
1111
config = config.responses;
1212

1313
walk(jsSpec, [], function(obj, path) {
14-
const contentsOfResponsesObject = path[path.length - 1] === 'responses';
1514
const isRef = !!obj.$ref;
1615

17-
if (contentsOfResponsesObject && !isRef) {
16+
if (isResponseObject(path, isOAS3) && !isRef) {
1817
each(obj, (response, responseKey) => {
1918
if (isOAS3) {
2019
each(response.content, (mediaType, mediaTypeKey) => {

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
// Assertation 5. Response bodies with application/json content should not use schema
1616
// type: string, format: binary.
1717

18-
const { walk } = require('../../../utils');
18+
const { walk, isResponseObject } = require('../../../utils');
1919
const MessageCarrier = require('../../../utils/messageCarrier');
2020
const findOctetSequencePaths = require('../../../utils/findOctetSequencePaths')
2121
.findOctetSequencePaths;
@@ -27,10 +27,10 @@ module.exports.validate = function({ resolvedSpec }, config) {
2727
config = config.responses;
2828

2929
walk(resolvedSpec, [], function(obj, path) {
30-
const contentsOfResponsesObject =
31-
path[0] === 'paths' && path[path.length - 1] === 'responses';
32-
33-
if (contentsOfResponsesObject) {
30+
// because we are using the resolved spec here,
31+
// and only want to validate the responses inside of operations,
32+
// check that we are within the `paths` object
33+
if (isResponseObject(path) && path[0] === 'paths') {
3434
const [statusCodes, successCodes] = getResponseCodes(obj);
3535

3636
const binaryStringStatus = configSchemas.json_or_param_binary_string;
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
const expect = require('expect');
2+
const { isResponseObject } = require('../../../src/plugins/utils');
3+
4+
describe('is response object - util', () => {
5+
describe('OpenAPI 3', () => {
6+
const isOas3 = true;
7+
8+
it('should return false for top-level responses objects', () => {
9+
const path = ['responses'];
10+
expect(isResponseObject(path, isOas3)).toBe(false);
11+
});
12+
13+
it('should return true for components responses objects', () => {
14+
const path = ['components', 'responses'];
15+
16+
// the second argument, `isOas3`, is optional. test that optionality
17+
expect(isResponseObject(path)).toBe(true);
18+
});
19+
20+
it('should return true for operation responses', () => {
21+
const path = ['paths', '/resource', 'post', 'responses'];
22+
expect(isResponseObject(path, isOas3)).toBe(true);
23+
});
24+
25+
it('should return false for non responses', () => {
26+
const path = ['paths', '/resource', 'post', 'parameters'];
27+
expect(isResponseObject(path, isOas3)).toBe(false);
28+
});
29+
30+
it('should return false for schemas with properties named responses', () => {
31+
const path = [
32+
'components',
33+
'schemas',
34+
'MySchema',
35+
'properties',
36+
'responses'
37+
];
38+
expect(isResponseObject(path, isOas3)).toBe(false);
39+
});
40+
});
41+
42+
describe('Swagger 2', () => {
43+
const isOas3 = false;
44+
45+
it('should return true for top-level responses objects', () => {
46+
const path = ['responses'];
47+
expect(isResponseObject(path, isOas3)).toBe(true);
48+
});
49+
50+
it('should return false for components responses objects', () => {
51+
const path = ['components', 'responses'];
52+
expect(isResponseObject(path, isOas3)).toBe(false);
53+
});
54+
});
55+
});

test/plugins/validation/oas3/responses.test.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,13 @@ describe('validation plugin - semantic - responses - oas3', function() {
110110
const res = validate({ resolvedSpec: spec }, config);
111111
expect(res.warnings.length).toEqual(0);
112112
expect(res.errors.length).toEqual(1);
113-
expect(res.errors[0].path).toEqual(['paths', '/pets', 'get', 'responses', '200']);
113+
expect(res.errors[0].path).toEqual([
114+
'paths',
115+
'/pets',
116+
'get',
117+
'responses',
118+
'200'
119+
]);
114120
expect(res.errors[0].message).toEqual(
115121
'All responses must include a description.'
116122
);

0 commit comments

Comments
 (0)