Skip to content

Commit 371a762

Browse files
SamerJaser96dpopp07
authored andcommitted
* fix: response schema support for oneOf, anyOf, and allOf (#70)
Check for inline schema definitions in combined schemas
1 parent f51446b commit 371a762

File tree

2 files changed

+314
-13
lines changed

2 files changed

+314
-13
lines changed

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

Lines changed: 51 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,23 +19,61 @@ module.exports.validate = function({ jsSpec, isOAS3 }, config) {
1919
each(obj, (response, responseKey) => {
2020
if (isOAS3) {
2121
each(response.content, (mediaType, mediaTypeKey) => {
22-
const hasInlineSchema = mediaType.schema && !mediaType.schema.$ref;
22+
const combinedSchemaTypes = ['allOf', 'oneOf', 'anyOf'];
23+
2324
if (
24-
hasInlineSchema &&
25+
mediaType.schema &&
2526
mediaTypeKey.startsWith('application/json')
2627
) {
27-
const checkStatus = config.inline_response_schema;
28-
if (checkStatus !== 'off') {
29-
result[checkStatus].push({
30-
path: [
31-
...path,
32-
responseKey,
33-
'content',
34-
mediaTypeKey,
35-
'schema'
36-
],
37-
message: INLINE_SCHEMA_MESSAGE
28+
const hasCombinedSchema =
29+
mediaType.schema.allOf ||
30+
mediaType.schema.anyOf ||
31+
mediaType.schema.oneOf;
32+
33+
if (hasCombinedSchema) {
34+
combinedSchemaTypes.forEach(schemaType => {
35+
if (mediaType.schema[schemaType]) {
36+
for (
37+
let i = 0;
38+
i < mediaType.schema[schemaType].length;
39+
i++
40+
) {
41+
const hasInlineSchema = !mediaType.schema[schemaType][i]
42+
.$ref;
43+
if (hasInlineSchema) {
44+
const checkStatus = config.inline_response_schema;
45+
if (checkStatus !== 'off') {
46+
result[checkStatus].push({
47+
path: [
48+
...path,
49+
responseKey,
50+
'content',
51+
mediaTypeKey,
52+
'schema',
53+
schemaType,
54+
i
55+
],
56+
message: INLINE_SCHEMA_MESSAGE
57+
});
58+
}
59+
}
60+
}
61+
}
3862
});
63+
} else if (!mediaType.schema.$ref) {
64+
const checkStatus = config.inline_response_schema;
65+
if (checkStatus !== 'off') {
66+
result[checkStatus].push({
67+
path: [
68+
...path,
69+
responseKey,
70+
'content',
71+
mediaTypeKey,
72+
'schema'
73+
],
74+
message: INLINE_SCHEMA_MESSAGE
75+
});
76+
}
3977
}
4078
}
4179
});

test/plugins/validation/2and3/responses.js

Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,116 @@ describe('validation plugin - semantic - responses', function() {
161161
expect(res.errors.length).toEqual(0);
162162
});
163163

164+
it('should not complain for a valid response in oneOf', function() {
165+
const spec = {
166+
paths: {
167+
'/stuff': {
168+
get: {
169+
summary: 'list stuff',
170+
operationId: 'listStuff',
171+
responses: {
172+
200: {
173+
description: 'successful operation',
174+
content: {
175+
'application/json': {
176+
schema: {
177+
oneOf: [
178+
{
179+
$ref:
180+
'#/components/schemas/ListStuffResponseModel'
181+
},
182+
{
183+
$ref: '#/components/schemas/ListStuffSecondModel'
184+
}
185+
]
186+
}
187+
}
188+
}
189+
}
190+
}
191+
}
192+
}
193+
}
194+
};
195+
196+
const res = validate({ jsSpec: spec, isOAS3: true }, config);
197+
expect(res.warnings.length).toEqual(0);
198+
expect(res.errors.length).toEqual(0);
199+
});
200+
201+
it('should not complain for a valid response in allOf', function() {
202+
const spec = {
203+
paths: {
204+
'/stuff': {
205+
get: {
206+
summary: 'list stuff',
207+
operationId: 'listStuff',
208+
responses: {
209+
200: {
210+
description: 'successful operation',
211+
content: {
212+
'application/json': {
213+
schema: {
214+
allOf: [
215+
{
216+
$ref:
217+
'#/components/schemas/ListStuffResponseModel'
218+
},
219+
{
220+
$ref: '#/components/schemas/ListStuffSecondModel'
221+
}
222+
]
223+
}
224+
}
225+
}
226+
}
227+
}
228+
}
229+
}
230+
}
231+
};
232+
233+
const res = validate({ jsSpec: spec, isOAS3: true }, config);
234+
expect(res.warnings.length).toEqual(0);
235+
expect(res.errors.length).toEqual(0);
236+
});
237+
238+
it('should not complain for a valid response in anyOf', function() {
239+
const spec = {
240+
paths: {
241+
'/stuff': {
242+
get: {
243+
summary: 'list stuff',
244+
operationId: 'listStuff',
245+
responses: {
246+
200: {
247+
description: 'successful operation',
248+
content: {
249+
'application/json': {
250+
schema: {
251+
anyOf: [
252+
{
253+
$ref:
254+
'#/components/schemas/ListStuffResponseModel'
255+
},
256+
{
257+
$ref: '#/components/schemas/ListStuffSecondModel'
258+
}
259+
]
260+
}
261+
}
262+
}
263+
}
264+
}
265+
}
266+
}
267+
}
268+
};
269+
270+
const res = validate({ jsSpec: spec, isOAS3: true }, config);
271+
expect(res.warnings.length).toEqual(0);
272+
expect(res.errors.length).toEqual(0);
273+
});
164274
it('should complain about an inline schema', function() {
165275
const spec = {
166276
paths: {
@@ -208,6 +318,159 @@ describe('validation plugin - semantic - responses', function() {
208318
expect(res.errors.length).toEqual(0);
209319
});
210320

321+
it('should complain about an inline schema when using oneOf', function() {
322+
const spec = {
323+
paths: {
324+
'/stuff': {
325+
get: {
326+
summary: 'list stuff',
327+
operationId: 'listStuff',
328+
responses: {
329+
200: {
330+
description: 'successful operation',
331+
content: {
332+
'application/json': {
333+
schema: {
334+
oneOf: [
335+
{
336+
type: 'object'
337+
},
338+
{
339+
$ref: 'ref1'
340+
}
341+
]
342+
}
343+
}
344+
}
345+
}
346+
}
347+
}
348+
}
349+
}
350+
};
351+
352+
const res = validate({ jsSpec: spec, isOAS3: true }, config);
353+
expect(res.warnings.length).toEqual(1);
354+
expect(res.warnings[0].path).toEqual([
355+
'paths',
356+
'/stuff',
357+
'get',
358+
'responses',
359+
'200',
360+
'content',
361+
'application/json',
362+
'schema',
363+
'oneOf',
364+
0
365+
]);
366+
expect(res.warnings[0].message).toEqual(
367+
'Response schemas should be defined with a named ref.'
368+
);
369+
expect(res.errors.length).toEqual(0);
370+
});
371+
372+
it('should complain about an inline schema when using allOf', function() {
373+
const spec = {
374+
paths: {
375+
'/stuff': {
376+
get: {
377+
summary: 'list stuff',
378+
operationId: 'listStuff',
379+
responses: {
380+
200: {
381+
description: 'successful operation',
382+
content: {
383+
'application/json': {
384+
schema: {
385+
allOf: [
386+
{
387+
type: 'object'
388+
},
389+
{
390+
$ref: 'ref1'
391+
}
392+
]
393+
}
394+
}
395+
}
396+
}
397+
}
398+
}
399+
}
400+
}
401+
};
402+
403+
const res = validate({ jsSpec: spec, isOAS3: true }, config);
404+
expect(res.warnings.length).toEqual(1);
405+
expect(res.warnings[0].path).toEqual([
406+
'paths',
407+
'/stuff',
408+
'get',
409+
'responses',
410+
'200',
411+
'content',
412+
'application/json',
413+
'schema',
414+
'allOf',
415+
0
416+
]);
417+
expect(res.warnings[0].message).toEqual(
418+
'Response schemas should be defined with a named ref.'
419+
);
420+
expect(res.errors.length).toEqual(0);
421+
});
422+
423+
it('should complain about an inline schema when using anyOf', function() {
424+
const spec = {
425+
paths: {
426+
'/stuff': {
427+
get: {
428+
summary: 'list stuff',
429+
operationId: 'listStuff',
430+
responses: {
431+
200: {
432+
description: 'successful operation',
433+
content: {
434+
'application/json': {
435+
schema: {
436+
anyOf: [
437+
{
438+
type: 'object'
439+
},
440+
{
441+
$ref: 'ref1'
442+
}
443+
]
444+
}
445+
}
446+
}
447+
}
448+
}
449+
}
450+
}
451+
}
452+
};
453+
454+
const res = validate({ jsSpec: spec, isOAS3: true }, config);
455+
expect(res.warnings.length).toEqual(1);
456+
expect(res.warnings[0].path).toEqual([
457+
'paths',
458+
'/stuff',
459+
'get',
460+
'responses',
461+
'200',
462+
'content',
463+
'application/json',
464+
'schema',
465+
'anyOf',
466+
0
467+
]);
468+
expect(res.warnings[0].message).toEqual(
469+
'Response schemas should be defined with a named ref.'
470+
);
471+
expect(res.errors.length).toEqual(0);
472+
});
473+
211474
it('should not complain for a response with no schema', function() {
212475
const spec = {
213476
paths: {

0 commit comments

Comments
 (0)