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: [