From 33831144a4cd1a57355f4c7f981264a2d7c8d09d Mon Sep 17 00:00:00 2001 From: Dustin Popp Date: Tue, 27 Aug 2019 08:51:48 -0500 Subject: [PATCH] fix: treat response schemas with an `items` field as arrays, even if there is no `type` (#99) --- .../semantic-validators/operations-shared.js | 12 ++- .../validation/2and3/operations-shared.js | 85 +++++++++++++++++++ 2 files changed, 95 insertions(+), 2 deletions(-) diff --git a/src/plugins/validation/2and3/semantic-validators/operations-shared.js b/src/plugins/validation/2and3/semantic-validators/operations-shared.js index aade9e384..b397558af 100644 --- a/src/plugins/validation/2and3/semantic-validators/operations-shared.js +++ b/src/plugins/validation/2and3/semantic-validators/operations-shared.js @@ -72,7 +72,11 @@ module.exports.validate = function({ resolvedSpec, isOAS3 }, config) { each(op.responses, (response, name) => { if (isOAS3) { each(response.content, (content, contentType) => { - if (content.schema && content.schema.type === 'array') { + const isArray = + content.schema && + (content.schema.type === 'array' || content.schema.items); + + if (isArray) { result[checkStatusArrRes].push({ path: `paths.${pathKey}.${opKey}.responses.${name}.content.${contentType}.schema`, message: @@ -81,7 +85,11 @@ module.exports.validate = function({ resolvedSpec, isOAS3 }, config) { } }); } else { - if (response.schema && response.schema.type === 'array') { + const isArray = + response.schema && + (response.schema.type === 'array' || response.schema.items); + + if (isArray) { result[checkStatusArrRes].push({ path: `paths.${pathKey}.${opKey}.responses.${name}.schema`, message: diff --git a/test/plugins/validation/2and3/operations-shared.js b/test/plugins/validation/2and3/operations-shared.js index ff6694444..9e8364db9 100644 --- a/test/plugins/validation/2and3/operations-shared.js +++ b/test/plugins/validation/2and3/operations-shared.js @@ -293,6 +293,40 @@ describe('validation plugin - semantic - operations-shared', function() { expect(res.warnings.length).toEqual(0); }); + it('should complain about an anonymous array response model with no type but an `items` field', function() { + const spec = { + paths: { + '/stuff': { + get: { + summary: 'list stuff', + operationId: 'list_stuff', + produces: ['application/json'], + responses: { + 200: { + description: 'successful operation', + schema: { + items: { + type: 'string' + } + } + } + } + } + } + } + }; + + const res = validate({ resolvedSpec: spec }, config); + expect(res.errors.length).toEqual(1); + expect(res.errors[0].path).toEqual( + 'paths./stuff.get.responses.200.schema' + ); + expect(res.errors[0].message).toEqual( + 'Arrays MUST NOT be returned as the top-level structure in a response body.' + ); + expect(res.warnings.length).toEqual(0); + }); + it('should not complain about an empty summary within a vendor extension', function() { const spec = { paths: { @@ -726,6 +760,57 @@ describe('validation plugin - semantic - operations-shared', function() { expect(res.warnings.length).toEqual(0); }); + it('should complain about a top-level array response without a type but with an `items` field', function() { + const spec = { + paths: { + '/': { + put: { + operationId: 'get_everything', + summary: 'get everything as a string or an array', + requestBody: { + description: 'simple body', + content: { + 'application/json': { + schema: { + type: 'string' + } + } + } + }, + responses: { + '200': { + content: { + 'text/plain': { + schema: { + type: 'string' + } + }, + 'application/json': { + schema: { + items: { + type: 'string' + } + } + } + } + } + } + } + } + } + }; + + const res = validate({ resolvedSpec: spec, isOAS3: true }, config); + expect(res.errors.length).toEqual(1); + expect(res.errors[0].path).toEqual( + 'paths./.put.responses.200.content.application/json.schema' + ); + expect(res.errors[0].message).toEqual( + 'Arrays MUST NOT be returned as the top-level structure in a response body.' + ); + expect(res.warnings.length).toEqual(0); + }); + it('should complain about an unused tag', function() { const spec = { tags: [