Skip to content

Commit 3383114

Browse files
authored
fix: treat response schemas with an items field as arrays, even if there is no type (#99)
1 parent 8a2102a commit 3383114

File tree

2 files changed

+95
-2
lines changed

2 files changed

+95
-2
lines changed

src/plugins/validation/2and3/semantic-validators/operations-shared.js

+10-2
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,11 @@ module.exports.validate = function({ resolvedSpec, isOAS3 }, config) {
7272
each(op.responses, (response, name) => {
7373
if (isOAS3) {
7474
each(response.content, (content, contentType) => {
75-
if (content.schema && content.schema.type === 'array') {
75+
const isArray =
76+
content.schema &&
77+
(content.schema.type === 'array' || content.schema.items);
78+
79+
if (isArray) {
7680
result[checkStatusArrRes].push({
7781
path: `paths.${pathKey}.${opKey}.responses.${name}.content.${contentType}.schema`,
7882
message:
@@ -81,7 +85,11 @@ module.exports.validate = function({ resolvedSpec, isOAS3 }, config) {
8185
}
8286
});
8387
} else {
84-
if (response.schema && response.schema.type === 'array') {
88+
const isArray =
89+
response.schema &&
90+
(response.schema.type === 'array' || response.schema.items);
91+
92+
if (isArray) {
8593
result[checkStatusArrRes].push({
8694
path: `paths.${pathKey}.${opKey}.responses.${name}.schema`,
8795
message:

test/plugins/validation/2and3/operations-shared.js

+85
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,40 @@ describe('validation plugin - semantic - operations-shared', function() {
293293
expect(res.warnings.length).toEqual(0);
294294
});
295295

296+
it('should complain about an anonymous array response model with no type but an `items` field', function() {
297+
const spec = {
298+
paths: {
299+
'/stuff': {
300+
get: {
301+
summary: 'list stuff',
302+
operationId: 'list_stuff',
303+
produces: ['application/json'],
304+
responses: {
305+
200: {
306+
description: 'successful operation',
307+
schema: {
308+
items: {
309+
type: 'string'
310+
}
311+
}
312+
}
313+
}
314+
}
315+
}
316+
}
317+
};
318+
319+
const res = validate({ resolvedSpec: spec }, config);
320+
expect(res.errors.length).toEqual(1);
321+
expect(res.errors[0].path).toEqual(
322+
'paths./stuff.get.responses.200.schema'
323+
);
324+
expect(res.errors[0].message).toEqual(
325+
'Arrays MUST NOT be returned as the top-level structure in a response body.'
326+
);
327+
expect(res.warnings.length).toEqual(0);
328+
});
329+
296330
it('should not complain about an empty summary within a vendor extension', function() {
297331
const spec = {
298332
paths: {
@@ -726,6 +760,57 @@ describe('validation plugin - semantic - operations-shared', function() {
726760
expect(res.warnings.length).toEqual(0);
727761
});
728762

763+
it('should complain about a top-level array response without a type but with an `items` field', function() {
764+
const spec = {
765+
paths: {
766+
'/': {
767+
put: {
768+
operationId: 'get_everything',
769+
summary: 'get everything as a string or an array',
770+
requestBody: {
771+
description: 'simple body',
772+
content: {
773+
'application/json': {
774+
schema: {
775+
type: 'string'
776+
}
777+
}
778+
}
779+
},
780+
responses: {
781+
'200': {
782+
content: {
783+
'text/plain': {
784+
schema: {
785+
type: 'string'
786+
}
787+
},
788+
'application/json': {
789+
schema: {
790+
items: {
791+
type: 'string'
792+
}
793+
}
794+
}
795+
}
796+
}
797+
}
798+
}
799+
}
800+
}
801+
};
802+
803+
const res = validate({ resolvedSpec: spec, isOAS3: true }, config);
804+
expect(res.errors.length).toEqual(1);
805+
expect(res.errors[0].path).toEqual(
806+
'paths./.put.responses.200.content.application/json.schema'
807+
);
808+
expect(res.errors[0].message).toEqual(
809+
'Arrays MUST NOT be returned as the top-level structure in a response body.'
810+
);
811+
expect(res.warnings.length).toEqual(0);
812+
});
813+
729814
it('should complain about an unused tag', function() {
730815
const spec = {
731816
tags: [

0 commit comments

Comments
 (0)