mirror of
https://github.com/Redocly/redoc.git
synced 2024-11-22 08:36:33 +03:00
fix: hoistOneOf missing refs stack and improve allOf for same $ref
Co-authored-by: Roman Hotsiy <gotsijroman@gmail.com>
This commit is contained in:
parent
6ac1e1eb18
commit
bb325d0d28
|
@ -173,7 +173,7 @@ export class OpenAPIParser {
|
||||||
return schema;
|
return schema;
|
||||||
}
|
}
|
||||||
|
|
||||||
schema = this.hoistOneOfs(schema);
|
schema = this.hoistOneOfs(schema, refsStack);
|
||||||
|
|
||||||
if (schema.allOf === undefined) {
|
if (schema.allOf === undefined) {
|
||||||
return schema;
|
return schema;
|
||||||
|
@ -194,30 +194,34 @@ export class OpenAPIParser {
|
||||||
receiver.items = { ...receiver.items };
|
receiver.items = { ...receiver.items };
|
||||||
}
|
}
|
||||||
|
|
||||||
const allOfSchemas = schema.allOf
|
const allOfSchemas = uniqByPropIncludeMissing(
|
||||||
.map((subSchema: OpenAPISchema) => {
|
schema.allOf
|
||||||
const { resolved, refsStack: subRefsStack } = this.deref(subSchema, refsStack, true);
|
.map((subSchema: OpenAPISchema) => {
|
||||||
|
const { resolved, refsStack: subRefsStack } = this.deref(subSchema, refsStack, true);
|
||||||
|
|
||||||
const subRef = subSchema.$ref || undefined;
|
const subRef = subSchema.$ref || undefined;
|
||||||
const subMerged = this.mergeAllOf(resolved, subRef, subRefsStack);
|
const subMerged = this.mergeAllOf(resolved, subRef, subRefsStack);
|
||||||
if (subMerged['x-circular-ref'] && subMerged.allOf) {
|
if (subMerged['x-circular-ref'] && subMerged.allOf) {
|
||||||
// if mergeAllOf is circular and still contains allOf, we should ignore it
|
// if mergeAllOf is circular and still contains allOf, we should ignore it
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
if (subRef) {
|
if (subRef) {
|
||||||
// collect information for implicit descriminator lookup
|
// collect information for implicit descriminator lookup
|
||||||
receiver['x-parentRefs']?.push(...(subMerged['x-parentRefs'] || []), subRef);
|
receiver['x-parentRefs']?.push(...(subMerged['x-parentRefs'] || []), subRef);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
$ref: subRef,
|
$ref: subRef,
|
||||||
refsStack: pushRef(subRefsStack, subRef),
|
refsStack: pushRef(subRefsStack, subRef),
|
||||||
schema: subMerged,
|
schema: subMerged,
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.filter(child => child !== undefined) as Array<{
|
.filter(child => child !== undefined) as Array<{
|
||||||
schema: MergedOpenAPISchema;
|
schema: MergedOpenAPISchema;
|
||||||
refsStack: string[];
|
refsStack: string[];
|
||||||
}>;
|
$ref?: string;
|
||||||
|
}>,
|
||||||
|
'$ref',
|
||||||
|
);
|
||||||
|
|
||||||
for (const { schema: subSchema, refsStack: subRefsStack } of allOfSchemas) {
|
for (const { schema: subSchema, refsStack: subRefsStack } of allOfSchemas) {
|
||||||
const {
|
const {
|
||||||
|
@ -269,7 +273,10 @@ export class OpenAPIParser {
|
||||||
// merge inner properties
|
// merge inner properties
|
||||||
const mergedProp = this.mergeAllOf(
|
const mergedProp = this.mergeAllOf(
|
||||||
{
|
{
|
||||||
allOf: [receiver.properties[prop], properties[prop]],
|
allOf: [
|
||||||
|
receiver.properties[prop],
|
||||||
|
{ ...properties[prop], 'x-refsStack': propRefsStack } as any,
|
||||||
|
],
|
||||||
'x-refsStack': propRefsStack,
|
'x-refsStack': propRefsStack,
|
||||||
},
|
},
|
||||||
$ref + '/properties/' + prop,
|
$ref + '/properties/' + prop,
|
||||||
|
@ -348,7 +355,7 @@ export class OpenAPIParser {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
private hoistOneOfs(schema: OpenAPISchema) {
|
private hoistOneOfs(schema: OpenAPISchema, refsStack: string[]) {
|
||||||
if (schema.allOf === undefined) {
|
if (schema.allOf === undefined) {
|
||||||
return schema;
|
return schema;
|
||||||
}
|
}
|
||||||
|
@ -363,6 +370,7 @@ export class OpenAPIParser {
|
||||||
oneOf: sub.oneOf.map((part: OpenAPISchema) => {
|
oneOf: sub.oneOf.map((part: OpenAPISchema) => {
|
||||||
return {
|
return {
|
||||||
allOf: [...beforeAllOf, part, ...afterAllOf],
|
allOf: [...beforeAllOf, part, ...afterAllOf],
|
||||||
|
'x-refsStack': refsStack,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
@ -372,3 +380,15 @@ export class OpenAPIParser {
|
||||||
return schema;
|
return schema;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unique array by property, missing properties are included
|
||||||
|
*/
|
||||||
|
function uniqByPropIncludeMissing<T extends object>(arr: T[], prop: keyof T): T[] {
|
||||||
|
const seen = new Set();
|
||||||
|
return arr.filter(item => {
|
||||||
|
const k = item[prop];
|
||||||
|
if (!k) return true;
|
||||||
|
return k && !seen.has(k) && seen.add(k);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -57,6 +57,7 @@ Object {
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
"x-refsStack": undefined,
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"allOf": Array [
|
"allOf": Array [
|
||||||
|
@ -96,6 +97,7 @@ Object {
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
"x-refsStack": undefined,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
|
@ -655,5 +655,76 @@ describe('Models', () => {
|
||||||
type: <string>
|
type: <string>
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should detect and recursion with nested oneOf case same schema', () => {
|
||||||
|
const spec = parseYaml(outdent`
|
||||||
|
openapi: 3.0.0
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
Test:
|
||||||
|
allOf:
|
||||||
|
- type: object
|
||||||
|
required:
|
||||||
|
- "@relations"
|
||||||
|
properties:
|
||||||
|
"@relations":
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
A:
|
||||||
|
$ref: "#/components/schemas/A"
|
||||||
|
- type: object
|
||||||
|
required:
|
||||||
|
- "@relations"
|
||||||
|
properties:
|
||||||
|
"@relations":
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
A:
|
||||||
|
$ref: "#/components/schemas/A"
|
||||||
|
A:
|
||||||
|
type: object
|
||||||
|
description: Description
|
||||||
|
properties:
|
||||||
|
B:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
oneOf:
|
||||||
|
- type: object
|
||||||
|
- title: tableLookup
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
fallback:
|
||||||
|
type: array
|
||||||
|
default: []
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/A/properties/B"
|
||||||
|
`) as any;
|
||||||
|
|
||||||
|
parser = new OpenAPIParser(spec, undefined, opts);
|
||||||
|
const schema = new SchemaModel(
|
||||||
|
parser,
|
||||||
|
spec.components.schemas.Test,
|
||||||
|
'#/components/schemas/Test',
|
||||||
|
opts,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(printSchema(schema, circularDetailsPrinter)).toMatchInlineSnapshot(`
|
||||||
|
@relations*:
|
||||||
|
A:
|
||||||
|
B: [
|
||||||
|
oneOf
|
||||||
|
object -> <object>
|
||||||
|
tableLookup ->
|
||||||
|
fallback: [
|
||||||
|
[
|
||||||
|
oneOf
|
||||||
|
object -> <object>
|
||||||
|
tableLookup ->
|
||||||
|
fallback: [<array> !circular]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue
Block a user