Skip to content

Commit f4ea368

Browse files
authored
feat: merge refs oas 3.1 (#1640)
1 parent 0a4d172 commit f4ea368

File tree

4 files changed

+55
-76
lines changed

4 files changed

+55
-76
lines changed

demo/openapi-3-1.yaml

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -964,8 +964,7 @@ components:
964964
properties:
965965
id:
966966
description: Category ID
967-
allOf:
968-
- $ref: '#/components/schemas/Id'
967+
$ref: '#/components/schemas/Id'
969968
name:
970969
description: Category name
971970
type: string
@@ -1015,12 +1014,10 @@ components:
10151014
properties:
10161015
id:
10171016
description: Order ID
1018-
allOf:
1019-
- $ref: '#/components/schemas/Id'
1017+
$ref: '#/components/schemas/Id'
10201018
petId:
10211019
description: Pet ID
1022-
allOf:
1023-
- $ref: '#/components/schemas/Id'
1020+
$ref: '#/components/schemas/Id'
10241021
quantity:
10251022
type: integer
10261023
format: int32
@@ -1065,12 +1062,10 @@ components:
10651062
description: "Find more info here"
10661063
url: "https://example.com"
10671064
description: Pet ID
1068-
allOf:
1069-
- $ref: '#/components/schemas/Id'
1065+
$ref: '#/components/schemas/Id'
10701066
category:
10711067
description: Categories this pet belongs to
1072-
allOf:
1073-
- $ref: '#/components/schemas/Category'
1068+
$ref: '#/components/schemas/Category'
10741069
name:
10751070
description: The name given to a pet
10761071
type: string
@@ -1087,8 +1082,7 @@ components:
10871082
type: string
10881083
format: url
10891084
friend:
1090-
allOf:
1091-
- $ref: '#/components/schemas/Pet'
1085+
$ref: '#/components/schemas/Pet'
10921086
tags:
10931087
description: Tags attached to the pet
10941088
type: array
@@ -1117,8 +1111,7 @@ components:
11171111
properties:
11181112
id:
11191113
description: Tag ID
1120-
allOf:
1121-
- $ref: '#/components/schemas/Id'
1114+
$ref: '#/components/schemas/Id'
11221115
name:
11231116
description: Tag name
11241117
type: string
@@ -1133,6 +1126,7 @@ components:
11331126
pet:
11341127
oneOf:
11351128
- $ref: '#/components/schemas/Pet'
1129+
title: Pettie
11361130
- $ref: '#/components/schemas/Tag'
11371131
username:
11381132
description: User supplied username
@@ -1179,10 +1173,9 @@ components:
11791173
content:
11801174
application/json:
11811175
schema:
1182-
allOf:
1183-
- description: My Pet
1184-
title: Pettie
1185-
- $ref: '#/components/schemas/Pet'
1176+
description: My Pet
1177+
title: Pettie
1178+
$ref: '#/components/schemas/Pet'
11861179
application/xml:
11871180
schema:
11881181
type: 'object'

src/services/OpenAPIParser.ts

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,9 @@ class RefCounter {
4545
export class OpenAPIParser {
4646
specUrl?: string;
4747
spec: OpenAPISpec;
48-
mergeRefs: Set<string>;
4948

5049
private _refCounter: RefCounter = new RefCounter();
50+
private allowMergeRefs: boolean = false;
5151

5252
constructor(
5353
spec: OpenAPISpec,
@@ -58,8 +58,7 @@ export class OpenAPIParser {
5858
this.preprocess(spec);
5959

6060
this.spec = spec;
61-
62-
this.mergeRefs = new Set();
61+
this.allowMergeRefs = spec.openapi.startsWith('3.1');
6362

6463
const href = IS_BROWSER ? window.location.href : '';
6564
if (typeof specUrl === 'string') {
@@ -149,7 +148,7 @@ export class OpenAPIParser {
149148
* @param obj object to dereference
150149
* @param forceCircular whether to dereference even if it is circular ref
151150
*/
152-
deref<T extends object>(obj: OpenAPIRef | T, forceCircular = false): T {
151+
deref<T extends object>(obj: OpenAPIRef | T, forceCircular = false, mergeAsAllOf = false): T {
153152
if (this.isRef(obj)) {
154153
const schemaName = getDefinitionName(obj.$ref);
155154
if (schemaName && this.options.ignoreNamedSchemas.has(schemaName)) {
@@ -165,16 +164,36 @@ export class OpenAPIParser {
165164
return Object.assign({}, resolved, { 'x-circular-ref': true });
166165
}
167166
// deref again in case one more $ref is here
167+
let result = resolved;
168168
if (this.isRef(resolved)) {
169-
const res = this.deref(resolved);
169+
result = this.deref(resolved, false, mergeAsAllOf);
170170
this.exitRef(resolved);
171-
return res;
172171
}
173-
return resolved;
172+
return this.allowMergeRefs ? this.mergeRefs(obj, resolved, mergeAsAllOf) : result;
174173
}
175174
return obj;
176175
}
177176

177+
mergeRefs(ref, resolved, mergeAsAllOf: boolean) {
178+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
179+
const { $ref, ...rest } = ref;
180+
const keys = Object.keys(rest);
181+
if (keys.length === 0) {
182+
return resolved;
183+
}
184+
if (mergeAsAllOf && keys.some((k) => k !== 'description' && k !== 'title' && k !== 'externalDocs')) {
185+
return {
186+
allOf: [resolved, rest],
187+
};
188+
} else {
189+
// small optimization
190+
return {
191+
...resolved,
192+
...rest,
193+
};
194+
}
195+
}
196+
178197
shalowDeref<T extends object>(obj: OpenAPIRef | T): T {
179198
if (this.isRef(obj)) {
180199
return this.byRef<T>(obj.$ref)!;
@@ -225,7 +244,7 @@ export class OpenAPIParser {
225244
return undefined;
226245
}
227246

228-
const resolved = this.deref(subSchema, forceCircular);
247+
const resolved = this.deref(subSchema, forceCircular, true);
229248
const subRef = subSchema.$ref || undefined;
230249
const subMerged = this.mergeAllOf(resolved, subRef, forceCircular, used$Refs);
231250
receiver.parentRefs!.push(...(subMerged.parentRefs || []));
@@ -234,7 +253,7 @@ export class OpenAPIParser {
234253
schema: subMerged,
235254
};
236255
})
237-
.filter(child => child !== undefined) as Array<{
256+
.filter((child) => child !== undefined) as Array<{
238257
$ref: string | undefined;
239258
schema: MergedOpenAPISchema;
240259
}>;
@@ -265,7 +284,7 @@ export class OpenAPIParser {
265284
{ allOf: [receiver.properties[prop], subSchema.properties[prop]] },
266285
$ref + '/properties/' + prop,
267286
);
268-
receiver.properties[prop] = mergedProp
287+
receiver.properties[prop] = mergedProp;
269288
this.exitParents(mergedProp); // every prop resolution should have separate recursive stack
270289
}
271290
}
@@ -313,7 +332,7 @@ export class OpenAPIParser {
313332
const def = this.deref(schemas[defName]);
314333
if (
315334
def.allOf !== undefined &&
316-
def.allOf.find(obj => obj.$ref !== undefined && $refs.indexOf(obj.$ref) > -1)
335+
def.allOf.find((obj) => obj.$ref !== undefined && $refs.indexOf(obj.$ref) > -1)
317336
) {
318337
res['#/components/schemas/' + defName] = [def['x-discriminator-value'] || defName];
319338
}
@@ -339,7 +358,7 @@ export class OpenAPIParser {
339358
const beforeAllOf = allOf.slice(0, i);
340359
const afterAllOf = allOf.slice(i + 1);
341360
return {
342-
oneOf: sub.oneOf.map(part => {
361+
oneOf: sub.oneOf.map((part) => {
343362
const merged = this.mergeAllOf({
344363
allOf: [...beforeAllOf, part, ...afterAllOf],
345364
});

src/services/models/Schema.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ export class SchemaModel {
7676
makeObservable(this);
7777

7878
this.pointer = schemaOrRef.$ref || pointer || '';
79-
this.rawSchema = parser.deref(schemaOrRef);
79+
this.rawSchema = parser.deref(schemaOrRef, false, true);
8080
this.schema = parser.mergeAllOf(this.rawSchema, this.pointer, isChild);
8181

8282
this.init(parser, isChild);
@@ -193,7 +193,7 @@ export class SchemaModel {
193193

194194
private initOneOf(oneOf: OpenAPISchema[], parser: OpenAPIParser) {
195195
this.oneOf = oneOf!.map((variant, idx) => {
196-
const derefVariant = parser.deref(variant);
196+
const derefVariant = parser.deref(variant, false, true);
197197

198198
const merged = parser.mergeAllOf(derefVariant, this.pointer + '/oneOf/' + idx);
199199

src/utils/__tests__/__snapshots__/loadAndBundleSpec.test.ts.snap

Lines changed: 11 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1864,15 +1864,9 @@ Object {
18641864
"content": Object {
18651865
"application/json": Object {
18661866
"schema": Object {
1867-
"allOf": Array [
1868-
Object {
1869-
"description": "My Pet",
1870-
"title": "Pettie",
1871-
},
1872-
Object {
1873-
"$ref": "#/components/schemas/Pet",
1874-
},
1875-
],
1867+
"$ref": "#/components/schemas/Pet",
1868+
"description": "My Pet",
1869+
"title": "Pettie",
18761870
},
18771871
},
18781872
"application/xml": Object {
@@ -1952,11 +1946,7 @@ Object {
19521946
"Category": Object {
19531947
"properties": Object {
19541948
"id": Object {
1955-
"allOf": Array [
1956-
Object {
1957-
"$ref": "#/components/schemas/Id",
1958-
},
1959-
],
1949+
"$ref": "#/components/schemas/Id",
19601950
"description": "Category ID",
19611951
},
19621952
"name": Object {
@@ -2039,19 +2029,11 @@ Object {
20392029
"type": "boolean",
20402030
},
20412031
"id": Object {
2042-
"allOf": Array [
2043-
Object {
2044-
"$ref": "#/components/schemas/Id",
2045-
},
2046-
],
2032+
"$ref": "#/components/schemas/Id",
20472033
"description": "Order ID",
20482034
},
20492035
"petId": Object {
2050-
"allOf": Array [
2051-
Object {
2052-
"$ref": "#/components/schemas/Id",
2053-
},
2054-
],
2036+
"$ref": "#/components/schemas/Id",
20552037
"description": "Pet ID",
20562038
},
20572039
"quantity": Object {
@@ -2096,26 +2078,14 @@ Object {
20962078
},
20972079
"properties": Object {
20982080
"category": Object {
2099-
"allOf": Array [
2100-
Object {
2101-
"$ref": "#/components/schemas/Category",
2102-
},
2103-
],
2081+
"$ref": "#/components/schemas/Category",
21042082
"description": "Categories this pet belongs to",
21052083
},
21062084
"friend": Object {
2107-
"allOf": Array [
2108-
Object {
2109-
"$ref": "#/components/schemas/Pet",
2110-
},
2111-
],
2085+
"$ref": "#/components/schemas/Pet",
21122086
},
21132087
"id": Object {
2114-
"allOf": Array [
2115-
Object {
2116-
"$ref": "#/components/schemas/Id",
2117-
},
2118-
],
2088+
"$ref": "#/components/schemas/Id",
21192089
"description": "Pet ID",
21202090
"externalDocs": Object {
21212091
"description": "Find more info here",
@@ -2186,11 +2156,7 @@ Object {
21862156
"Tag": Object {
21872157
"properties": Object {
21882158
"id": Object {
2189-
"allOf": Array [
2190-
Object {
2191-
"$ref": "#/components/schemas/Id",
2192-
},
2193-
],
2159+
"$ref": "#/components/schemas/Id",
21942160
"description": "Tag ID",
21952161
},
21962162
"name": Object {
@@ -2239,6 +2205,7 @@ Object {
22392205
"oneOf": Array [
22402206
Object {
22412207
"$ref": "#/components/schemas/Pet",
2208+
"title": "Pettie",
22422209
},
22432210
Object {
22442211
"$ref": "#/components/schemas/Tag",

0 commit comments

Comments
 (0)