diff --git a/src/services/OpenAPIParser.ts b/src/services/OpenAPIParser.ts index 869e89c5..c27e89c7 100644 --- a/src/services/OpenAPIParser.ts +++ b/src/services/OpenAPIParser.ts @@ -182,6 +182,8 @@ export class OpenAPIParser { $ref?: string, forceCircular: boolean = false, ): MergedOpenAPISchema { + schema = this.hoistOneOfs(schema); + if (schema.allOf === undefined) { return schema; } @@ -291,4 +293,28 @@ export class OpenAPIParser { } return res; } + + private hoistOneOfs(schema: OpenAPISchema) { + if (schema.allOf === undefined) { + return schema; + } + + const allOf = schema.allOf; + for (let i = 0; i < allOf.length; i++) { + const sub = allOf[i]; + if (Array.isArray(sub.oneOf)) { + const beforeAllOf = allOf.slice(0, i); + const afterAllOf = allOf.slice(i + 1); + return { + oneOf: sub.oneOf.map(part => { + return this.mergeAllOf({ + allOf: [...beforeAllOf, part, ...afterAllOf], + }); + }), + }; + } + } + + return schema; + } } diff --git a/src/services/__tests__/OpenAPIParser.test.ts b/src/services/__tests__/OpenAPIParser.test.ts new file mode 100644 index 00000000..df2f9f16 --- /dev/null +++ b/src/services/__tests__/OpenAPIParser.test.ts @@ -0,0 +1,16 @@ +import { OpenAPIParser } from '../OpenAPIParser'; +import { RedocNormalizedOptions } from '../RedocNormalizedOptions'; + +const opts = new RedocNormalizedOptions({}); + +describe('Models', () => { + describe('Schema', () => { + let parser; + + test('should hoist oneOfs when mergin allOf', () => { + const spec = require('./fixtures/oneOfHoist.json'); + parser = new OpenAPIParser(spec, undefined, opts); + expect(parser.mergeAllOf(spec.components.schemas.test)).toMatchSnapshot(); + }); + }); +}); diff --git a/src/services/__tests__/__snapshots__/OpenAPIParser.test.ts.snap b/src/services/__tests__/__snapshots__/OpenAPIParser.test.ts.snap new file mode 100644 index 00000000..3ec279e0 --- /dev/null +++ b/src/services/__tests__/__snapshots__/OpenAPIParser.test.ts.snap @@ -0,0 +1,84 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Models Schema should hoist oneOfs when mergin allOf 1`] = ` +Object { + "oneOf": Array [ + Object { + "oneOf": Array [ + Object { + "allOf": undefined, + "parentRefs": Array [], + "properties": Object { + "extra": Object { + "type": "string", + }, + "password": Object { + "description": "The user's password", + "type": "string", + }, + "username": Object { + "description": "The user's name", + "type": "string", + }, + }, + }, + Object { + "allOf": undefined, + "parentRefs": Array [], + "properties": Object { + "extra": Object { + "type": "string", + }, + "mobile": Object { + "description": "The user's mobile", + "type": "string", + }, + "username": Object { + "description": "The user's name", + "type": "string", + }, + }, + }, + ], + }, + Object { + "oneOf": Array [ + Object { + "allOf": undefined, + "parentRefs": Array [], + "properties": Object { + "email": Object { + "description": "The user's email", + "type": "string", + }, + "extra": Object { + "type": "string", + }, + "password": Object { + "description": "The user's password", + "type": "string", + }, + }, + }, + Object { + "allOf": undefined, + "parentRefs": Array [], + "properties": Object { + "email": Object { + "description": "The user's email", + "type": "string", + }, + "extra": Object { + "type": "string", + }, + "mobile": Object { + "description": "The user's mobile", + "type": "string", + }, + }, + }, + ], + }, + ], +} +`; diff --git a/src/services/__tests__/fixtures/oneOfHoist.json b/src/services/__tests__/fixtures/oneOfHoist.json new file mode 100644 index 00000000..05568aaf --- /dev/null +++ b/src/services/__tests__/fixtures/oneOfHoist.json @@ -0,0 +1,62 @@ +{ + "openapi": "3.0.0", + "info": { + "version": "1.0", + "title": "Foo" + }, + "components": { + "schemas": { + "test": { + "allOf": [ + { + "oneOf": [ + { + "properties": { + "username": { + "description": "The user's name", + "type": "string" + } + } + }, + { + "properties": { + "email": { + "description": "The user's email", + "type": "string" + } + } + } + ] + }, + { + "properties": { + "extra": { + "type": "string" + } + } + }, + { + "oneOf": [ + { + "properties": { + "password": { + "description": "The user's password", + "type": "string" + } + } + }, + { + "properties": { + "mobile": { + "description": "The user's mobile", + "type": "string" + } + } + } + ] + } + ] + } + } + } +} \ No newline at end of file