diff --git a/demo/openapi-3-1.yaml b/demo/openapi-3-1.yaml index 6f6496aa..c905d4d8 100644 --- a/demo/openapi-3-1.yaml +++ b/demo/openapi-3-1.yaml @@ -964,8 +964,7 @@ components: properties: id: description: Category ID - allOf: - - $ref: '#/components/schemas/Id' + $ref: '#/components/schemas/Id' name: description: Category name type: string @@ -1015,12 +1014,10 @@ components: properties: id: description: Order ID - allOf: - - $ref: '#/components/schemas/Id' + $ref: '#/components/schemas/Id' petId: description: Pet ID - allOf: - - $ref: '#/components/schemas/Id' + $ref: '#/components/schemas/Id' quantity: type: integer format: int32 @@ -1065,12 +1062,10 @@ components: description: "Find more info here" url: "https://example.com" description: Pet ID - allOf: - - $ref: '#/components/schemas/Id' + $ref: '#/components/schemas/Id' category: description: Categories this pet belongs to - allOf: - - $ref: '#/components/schemas/Category' + $ref: '#/components/schemas/Category' name: description: The name given to a pet type: string @@ -1087,8 +1082,7 @@ components: type: string format: url friend: - allOf: - - $ref: '#/components/schemas/Pet' + $ref: '#/components/schemas/Pet' tags: description: Tags attached to the pet type: array @@ -1117,8 +1111,7 @@ components: properties: id: description: Tag ID - allOf: - - $ref: '#/components/schemas/Id' + $ref: '#/components/schemas/Id' name: description: Tag name type: string @@ -1133,6 +1126,7 @@ components: pet: oneOf: - $ref: '#/components/schemas/Pet' + title: Pettie - $ref: '#/components/schemas/Tag' username: description: User supplied username @@ -1179,10 +1173,9 @@ components: content: application/json: schema: - allOf: - - description: My Pet - title: Pettie - - $ref: '#/components/schemas/Pet' + description: My Pet + title: Pettie + $ref: '#/components/schemas/Pet' application/xml: schema: type: 'object' diff --git a/demo/playground/hmr-playground.tsx b/demo/playground/hmr-playground.tsx index efd843ab..3901cfbd 100644 --- a/demo/playground/hmr-playground.tsx +++ b/demo/playground/hmr-playground.tsx @@ -9,7 +9,7 @@ const swagger = window.location.search.indexOf('swagger') > -1; const userUrl = window.location.search.match(/url=(.*)$/); const specUrl = - (userUrl && userUrl[1]) || (swagger ? 'swagger.yaml' : big ? 'big-openapi.json' : 'openapi.yaml'); + (userUrl && userUrl[1]) || (swagger ? 'swagger.yaml' : big ? 'big-openapi.json' : 'openapi-3-1.yaml'); const options: RedocRawOptions = { nativeScrollbars: false, maxDisplayedEnumValues: 3 }; diff --git a/src/services/OpenAPIParser.ts b/src/services/OpenAPIParser.ts index b0d3a8f4..83a65fda 100644 --- a/src/services/OpenAPIParser.ts +++ b/src/services/OpenAPIParser.ts @@ -45,9 +45,9 @@ class RefCounter { export class OpenAPIParser { specUrl?: string; spec: OpenAPISpec; - mergeRefs: Set; private _refCounter: RefCounter = new RefCounter(); + private allowMergeRefs: boolean = false; constructor( spec: OpenAPISpec, @@ -58,8 +58,7 @@ export class OpenAPIParser { this.preprocess(spec); this.spec = spec; - - this.mergeRefs = new Set(); + this.allowMergeRefs = spec.openapi.startsWith('3.1'); const href = IS_BROWSER ? window.location.href : ''; if (typeof specUrl === 'string') { @@ -165,16 +164,36 @@ export class OpenAPIParser { return Object.assign({}, resolved, { 'x-circular-ref': true }); } // deref again in case one more $ref is here + let result = resolved; if (this.isRef(resolved)) { - const res = this.deref(resolved); + result = this.deref(resolved); this.exitRef(resolved); - return res; } - return resolved; + return this.allowMergeRefs ? this.mergeRefs(obj, resolved) : result; } return obj; } + mergeRefs(ref, resolved) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { $ref, ...rest } = ref; + const keys = Object.keys(rest); + if (keys.length === 0) { + return resolved; + } + if (keys.some((k) => k !== 'description' && k !== 'title' && k !== 'externalDocs')) { + return { + allOf: [resolved, rest], + }; + } else { + // small optimization + return { + ...resolved, + ...rest, + }; + } + } + shalowDeref(obj: OpenAPIRef | T): T { if (this.isRef(obj)) { return this.byRef(obj.$ref)!; @@ -234,7 +253,7 @@ export class OpenAPIParser { schema: subMerged, }; }) - .filter(child => child !== undefined) as Array<{ + .filter((child) => child !== undefined) as Array<{ $ref: string | undefined; schema: MergedOpenAPISchema; }>; @@ -265,7 +284,7 @@ export class OpenAPIParser { { allOf: [receiver.properties[prop], subSchema.properties[prop]] }, $ref + '/properties/' + prop, ); - receiver.properties[prop] = mergedProp + receiver.properties[prop] = mergedProp; this.exitParents(mergedProp); // every prop resolution should have separate recursive stack } } @@ -313,7 +332,7 @@ export class OpenAPIParser { const def = this.deref(schemas[defName]); if ( def.allOf !== undefined && - def.allOf.find(obj => obj.$ref !== undefined && $refs.indexOf(obj.$ref) > -1) + def.allOf.find((obj) => obj.$ref !== undefined && $refs.indexOf(obj.$ref) > -1) ) { res['#/components/schemas/' + defName] = [def['x-discriminator-value'] || defName]; } @@ -339,7 +358,7 @@ export class OpenAPIParser { const beforeAllOf = allOf.slice(0, i); const afterAllOf = allOf.slice(i + 1); return { - oneOf: sub.oneOf.map(part => { + oneOf: sub.oneOf.map((part) => { const merged = this.mergeAllOf({ allOf: [...beforeAllOf, part, ...afterAllOf], });