mirror of
https://github.com/Redocly/redoc.git
synced 2024-11-25 18:13:44 +03:00
feat: merge refs oas 3.1 (#1640)
This commit is contained in:
parent
0a4d172333
commit
f4ea368f78
|
@ -964,8 +964,7 @@ components:
|
||||||
properties:
|
properties:
|
||||||
id:
|
id:
|
||||||
description: Category ID
|
description: Category ID
|
||||||
allOf:
|
$ref: '#/components/schemas/Id'
|
||||||
- $ref: '#/components/schemas/Id'
|
|
||||||
name:
|
name:
|
||||||
description: Category name
|
description: Category name
|
||||||
type: string
|
type: string
|
||||||
|
@ -1015,12 +1014,10 @@ components:
|
||||||
properties:
|
properties:
|
||||||
id:
|
id:
|
||||||
description: Order ID
|
description: Order ID
|
||||||
allOf:
|
$ref: '#/components/schemas/Id'
|
||||||
- $ref: '#/components/schemas/Id'
|
|
||||||
petId:
|
petId:
|
||||||
description: Pet ID
|
description: Pet ID
|
||||||
allOf:
|
$ref: '#/components/schemas/Id'
|
||||||
- $ref: '#/components/schemas/Id'
|
|
||||||
quantity:
|
quantity:
|
||||||
type: integer
|
type: integer
|
||||||
format: int32
|
format: int32
|
||||||
|
@ -1065,12 +1062,10 @@ components:
|
||||||
description: "Find more info here"
|
description: "Find more info here"
|
||||||
url: "https://example.com"
|
url: "https://example.com"
|
||||||
description: Pet ID
|
description: Pet ID
|
||||||
allOf:
|
$ref: '#/components/schemas/Id'
|
||||||
- $ref: '#/components/schemas/Id'
|
|
||||||
category:
|
category:
|
||||||
description: Categories this pet belongs to
|
description: Categories this pet belongs to
|
||||||
allOf:
|
$ref: '#/components/schemas/Category'
|
||||||
- $ref: '#/components/schemas/Category'
|
|
||||||
name:
|
name:
|
||||||
description: The name given to a pet
|
description: The name given to a pet
|
||||||
type: string
|
type: string
|
||||||
|
@ -1087,8 +1082,7 @@ components:
|
||||||
type: string
|
type: string
|
||||||
format: url
|
format: url
|
||||||
friend:
|
friend:
|
||||||
allOf:
|
$ref: '#/components/schemas/Pet'
|
||||||
- $ref: '#/components/schemas/Pet'
|
|
||||||
tags:
|
tags:
|
||||||
description: Tags attached to the pet
|
description: Tags attached to the pet
|
||||||
type: array
|
type: array
|
||||||
|
@ -1117,8 +1111,7 @@ components:
|
||||||
properties:
|
properties:
|
||||||
id:
|
id:
|
||||||
description: Tag ID
|
description: Tag ID
|
||||||
allOf:
|
$ref: '#/components/schemas/Id'
|
||||||
- $ref: '#/components/schemas/Id'
|
|
||||||
name:
|
name:
|
||||||
description: Tag name
|
description: Tag name
|
||||||
type: string
|
type: string
|
||||||
|
@ -1133,6 +1126,7 @@ components:
|
||||||
pet:
|
pet:
|
||||||
oneOf:
|
oneOf:
|
||||||
- $ref: '#/components/schemas/Pet'
|
- $ref: '#/components/schemas/Pet'
|
||||||
|
title: Pettie
|
||||||
- $ref: '#/components/schemas/Tag'
|
- $ref: '#/components/schemas/Tag'
|
||||||
username:
|
username:
|
||||||
description: User supplied username
|
description: User supplied username
|
||||||
|
@ -1179,10 +1173,9 @@ components:
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
allOf:
|
description: My Pet
|
||||||
- description: My Pet
|
|
||||||
title: Pettie
|
title: Pettie
|
||||||
- $ref: '#/components/schemas/Pet'
|
$ref: '#/components/schemas/Pet'
|
||||||
application/xml:
|
application/xml:
|
||||||
schema:
|
schema:
|
||||||
type: 'object'
|
type: 'object'
|
||||||
|
|
|
@ -45,9 +45,9 @@ class RefCounter {
|
||||||
export class OpenAPIParser {
|
export class OpenAPIParser {
|
||||||
specUrl?: string;
|
specUrl?: string;
|
||||||
spec: OpenAPISpec;
|
spec: OpenAPISpec;
|
||||||
mergeRefs: Set<string>;
|
|
||||||
|
|
||||||
private _refCounter: RefCounter = new RefCounter();
|
private _refCounter: RefCounter = new RefCounter();
|
||||||
|
private allowMergeRefs: boolean = false;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
spec: OpenAPISpec,
|
spec: OpenAPISpec,
|
||||||
|
@ -58,8 +58,7 @@ export class OpenAPIParser {
|
||||||
this.preprocess(spec);
|
this.preprocess(spec);
|
||||||
|
|
||||||
this.spec = spec;
|
this.spec = spec;
|
||||||
|
this.allowMergeRefs = spec.openapi.startsWith('3.1');
|
||||||
this.mergeRefs = new Set();
|
|
||||||
|
|
||||||
const href = IS_BROWSER ? window.location.href : '';
|
const href = IS_BROWSER ? window.location.href : '';
|
||||||
if (typeof specUrl === 'string') {
|
if (typeof specUrl === 'string') {
|
||||||
|
@ -149,7 +148,7 @@ export class OpenAPIParser {
|
||||||
* @param obj object to dereference
|
* @param obj object to dereference
|
||||||
* @param forceCircular whether to dereference even if it is circular ref
|
* @param forceCircular whether to dereference even if it is circular ref
|
||||||
*/
|
*/
|
||||||
deref<T extends object>(obj: OpenAPIRef | T, forceCircular = false): T {
|
deref<T extends object>(obj: OpenAPIRef | T, forceCircular = false, mergeAsAllOf = false): T {
|
||||||
if (this.isRef(obj)) {
|
if (this.isRef(obj)) {
|
||||||
const schemaName = getDefinitionName(obj.$ref);
|
const schemaName = getDefinitionName(obj.$ref);
|
||||||
if (schemaName && this.options.ignoreNamedSchemas.has(schemaName)) {
|
if (schemaName && this.options.ignoreNamedSchemas.has(schemaName)) {
|
||||||
|
@ -165,16 +164,36 @@ export class OpenAPIParser {
|
||||||
return Object.assign({}, resolved, { 'x-circular-ref': true });
|
return Object.assign({}, resolved, { 'x-circular-ref': true });
|
||||||
}
|
}
|
||||||
// deref again in case one more $ref is here
|
// deref again in case one more $ref is here
|
||||||
|
let result = resolved;
|
||||||
if (this.isRef(resolved)) {
|
if (this.isRef(resolved)) {
|
||||||
const res = this.deref(resolved);
|
result = this.deref(resolved, false, mergeAsAllOf);
|
||||||
this.exitRef(resolved);
|
this.exitRef(resolved);
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
return resolved;
|
return this.allowMergeRefs ? this.mergeRefs(obj, resolved, mergeAsAllOf) : result;
|
||||||
}
|
}
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mergeRefs(ref, resolved, mergeAsAllOf: boolean) {
|
||||||
|
// 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 (mergeAsAllOf && keys.some((k) => k !== 'description' && k !== 'title' && k !== 'externalDocs')) {
|
||||||
|
return {
|
||||||
|
allOf: [resolved, rest],
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// small optimization
|
||||||
|
return {
|
||||||
|
...resolved,
|
||||||
|
...rest,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
shalowDeref<T extends object>(obj: OpenAPIRef | T): T {
|
shalowDeref<T extends object>(obj: OpenAPIRef | T): T {
|
||||||
if (this.isRef(obj)) {
|
if (this.isRef(obj)) {
|
||||||
return this.byRef<T>(obj.$ref)!;
|
return this.byRef<T>(obj.$ref)!;
|
||||||
|
@ -225,7 +244,7 @@ export class OpenAPIParser {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const resolved = this.deref(subSchema, forceCircular);
|
const resolved = this.deref(subSchema, forceCircular, true);
|
||||||
const subRef = subSchema.$ref || undefined;
|
const subRef = subSchema.$ref || undefined;
|
||||||
const subMerged = this.mergeAllOf(resolved, subRef, forceCircular, used$Refs);
|
const subMerged = this.mergeAllOf(resolved, subRef, forceCircular, used$Refs);
|
||||||
receiver.parentRefs!.push(...(subMerged.parentRefs || []));
|
receiver.parentRefs!.push(...(subMerged.parentRefs || []));
|
||||||
|
@ -234,7 +253,7 @@ export class OpenAPIParser {
|
||||||
schema: subMerged,
|
schema: subMerged,
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.filter(child => child !== undefined) as Array<{
|
.filter((child) => child !== undefined) as Array<{
|
||||||
$ref: string | undefined;
|
$ref: string | undefined;
|
||||||
schema: MergedOpenAPISchema;
|
schema: MergedOpenAPISchema;
|
||||||
}>;
|
}>;
|
||||||
|
@ -265,7 +284,7 @@ export class OpenAPIParser {
|
||||||
{ allOf: [receiver.properties[prop], subSchema.properties[prop]] },
|
{ allOf: [receiver.properties[prop], subSchema.properties[prop]] },
|
||||||
$ref + '/properties/' + prop,
|
$ref + '/properties/' + prop,
|
||||||
);
|
);
|
||||||
receiver.properties[prop] = mergedProp
|
receiver.properties[prop] = mergedProp;
|
||||||
this.exitParents(mergedProp); // every prop resolution should have separate recursive stack
|
this.exitParents(mergedProp); // every prop resolution should have separate recursive stack
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -313,7 +332,7 @@ export class OpenAPIParser {
|
||||||
const def = this.deref(schemas[defName]);
|
const def = this.deref(schemas[defName]);
|
||||||
if (
|
if (
|
||||||
def.allOf !== undefined &&
|
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];
|
res['#/components/schemas/' + defName] = [def['x-discriminator-value'] || defName];
|
||||||
}
|
}
|
||||||
|
@ -339,7 +358,7 @@ export class OpenAPIParser {
|
||||||
const beforeAllOf = allOf.slice(0, i);
|
const beforeAllOf = allOf.slice(0, i);
|
||||||
const afterAllOf = allOf.slice(i + 1);
|
const afterAllOf = allOf.slice(i + 1);
|
||||||
return {
|
return {
|
||||||
oneOf: sub.oneOf.map(part => {
|
oneOf: sub.oneOf.map((part) => {
|
||||||
const merged = this.mergeAllOf({
|
const merged = this.mergeAllOf({
|
||||||
allOf: [...beforeAllOf, part, ...afterAllOf],
|
allOf: [...beforeAllOf, part, ...afterAllOf],
|
||||||
});
|
});
|
||||||
|
|
|
@ -76,7 +76,7 @@ export class SchemaModel {
|
||||||
makeObservable(this);
|
makeObservable(this);
|
||||||
|
|
||||||
this.pointer = schemaOrRef.$ref || pointer || '';
|
this.pointer = schemaOrRef.$ref || pointer || '';
|
||||||
this.rawSchema = parser.deref(schemaOrRef);
|
this.rawSchema = parser.deref(schemaOrRef, false, true);
|
||||||
this.schema = parser.mergeAllOf(this.rawSchema, this.pointer, isChild);
|
this.schema = parser.mergeAllOf(this.rawSchema, this.pointer, isChild);
|
||||||
|
|
||||||
this.init(parser, isChild);
|
this.init(parser, isChild);
|
||||||
|
@ -193,7 +193,7 @@ export class SchemaModel {
|
||||||
|
|
||||||
private initOneOf(oneOf: OpenAPISchema[], parser: OpenAPIParser) {
|
private initOneOf(oneOf: OpenAPISchema[], parser: OpenAPIParser) {
|
||||||
this.oneOf = oneOf!.map((variant, idx) => {
|
this.oneOf = oneOf!.map((variant, idx) => {
|
||||||
const derefVariant = parser.deref(variant);
|
const derefVariant = parser.deref(variant, false, true);
|
||||||
|
|
||||||
const merged = parser.mergeAllOf(derefVariant, this.pointer + '/oneOf/' + idx);
|
const merged = parser.mergeAllOf(derefVariant, this.pointer + '/oneOf/' + idx);
|
||||||
|
|
||||||
|
|
|
@ -1864,16 +1864,10 @@ Object {
|
||||||
"content": Object {
|
"content": Object {
|
||||||
"application/json": Object {
|
"application/json": Object {
|
||||||
"schema": Object {
|
"schema": Object {
|
||||||
"allOf": Array [
|
"$ref": "#/components/schemas/Pet",
|
||||||
Object {
|
|
||||||
"description": "My Pet",
|
"description": "My Pet",
|
||||||
"title": "Pettie",
|
"title": "Pettie",
|
||||||
},
|
},
|
||||||
Object {
|
|
||||||
"$ref": "#/components/schemas/Pet",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
"application/xml": Object {
|
"application/xml": Object {
|
||||||
"schema": Object {
|
"schema": Object {
|
||||||
|
@ -1952,11 +1946,7 @@ Object {
|
||||||
"Category": Object {
|
"Category": Object {
|
||||||
"properties": Object {
|
"properties": Object {
|
||||||
"id": Object {
|
"id": Object {
|
||||||
"allOf": Array [
|
|
||||||
Object {
|
|
||||||
"$ref": "#/components/schemas/Id",
|
"$ref": "#/components/schemas/Id",
|
||||||
},
|
|
||||||
],
|
|
||||||
"description": "Category ID",
|
"description": "Category ID",
|
||||||
},
|
},
|
||||||
"name": Object {
|
"name": Object {
|
||||||
|
@ -2039,19 +2029,11 @@ Object {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
},
|
},
|
||||||
"id": Object {
|
"id": Object {
|
||||||
"allOf": Array [
|
|
||||||
Object {
|
|
||||||
"$ref": "#/components/schemas/Id",
|
"$ref": "#/components/schemas/Id",
|
||||||
},
|
|
||||||
],
|
|
||||||
"description": "Order ID",
|
"description": "Order ID",
|
||||||
},
|
},
|
||||||
"petId": Object {
|
"petId": Object {
|
||||||
"allOf": Array [
|
|
||||||
Object {
|
|
||||||
"$ref": "#/components/schemas/Id",
|
"$ref": "#/components/schemas/Id",
|
||||||
},
|
|
||||||
],
|
|
||||||
"description": "Pet ID",
|
"description": "Pet ID",
|
||||||
},
|
},
|
||||||
"quantity": Object {
|
"quantity": Object {
|
||||||
|
@ -2096,26 +2078,14 @@ Object {
|
||||||
},
|
},
|
||||||
"properties": Object {
|
"properties": Object {
|
||||||
"category": Object {
|
"category": Object {
|
||||||
"allOf": Array [
|
|
||||||
Object {
|
|
||||||
"$ref": "#/components/schemas/Category",
|
"$ref": "#/components/schemas/Category",
|
||||||
},
|
|
||||||
],
|
|
||||||
"description": "Categories this pet belongs to",
|
"description": "Categories this pet belongs to",
|
||||||
},
|
},
|
||||||
"friend": Object {
|
"friend": Object {
|
||||||
"allOf": Array [
|
|
||||||
Object {
|
|
||||||
"$ref": "#/components/schemas/Pet",
|
"$ref": "#/components/schemas/Pet",
|
||||||
},
|
},
|
||||||
],
|
|
||||||
},
|
|
||||||
"id": Object {
|
"id": Object {
|
||||||
"allOf": Array [
|
|
||||||
Object {
|
|
||||||
"$ref": "#/components/schemas/Id",
|
"$ref": "#/components/schemas/Id",
|
||||||
},
|
|
||||||
],
|
|
||||||
"description": "Pet ID",
|
"description": "Pet ID",
|
||||||
"externalDocs": Object {
|
"externalDocs": Object {
|
||||||
"description": "Find more info here",
|
"description": "Find more info here",
|
||||||
|
@ -2186,11 +2156,7 @@ Object {
|
||||||
"Tag": Object {
|
"Tag": Object {
|
||||||
"properties": Object {
|
"properties": Object {
|
||||||
"id": Object {
|
"id": Object {
|
||||||
"allOf": Array [
|
|
||||||
Object {
|
|
||||||
"$ref": "#/components/schemas/Id",
|
"$ref": "#/components/schemas/Id",
|
||||||
},
|
|
||||||
],
|
|
||||||
"description": "Tag ID",
|
"description": "Tag ID",
|
||||||
},
|
},
|
||||||
"name": Object {
|
"name": Object {
|
||||||
|
@ -2239,6 +2205,7 @@ Object {
|
||||||
"oneOf": Array [
|
"oneOf": Array [
|
||||||
Object {
|
Object {
|
||||||
"$ref": "#/components/schemas/Pet",
|
"$ref": "#/components/schemas/Pet",
|
||||||
|
"title": "Pettie",
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"$ref": "#/components/schemas/Tag",
|
"$ref": "#/components/schemas/Tag",
|
||||||
|
|
Loading…
Reference in New Issue
Block a user