mirror of
https://github.com/Redocly/redoc.git
synced 2024-11-30 12:33:43 +03:00
fix: lazy fields to prevent crash on complex nested oneOf's
This commit is contained in:
parent
fd6171b93a
commit
ec5d11838d
|
@ -50,7 +50,7 @@ describe('Components', () => {
|
||||||
}}
|
}}
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
expect(filterPropsDeep(toJson(schemaView), ['field.schema.options'])).toMatchSnapshot();
|
expect(filterPropsDeep(toJson(schemaView), ['field.schema.options', 'field.schema.parent', 'field.schema.parser'])).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -27,10 +27,12 @@ exports[`Components SchemaView discriminator should correctly render discriminat
|
||||||
"example": undefined,
|
"example": undefined,
|
||||||
"externalDocs": undefined,
|
"externalDocs": undefined,
|
||||||
"format": undefined,
|
"format": undefined,
|
||||||
"isCircular": undefined,
|
"isCircular": false,
|
||||||
"isPrimitive": true,
|
"isPrimitive": true,
|
||||||
"nullable": false,
|
"nullable": false,
|
||||||
"options": "<<<filtered>>>",
|
"options": "<<<filtered>>>",
|
||||||
|
"parent": "<<<filtered>>>",
|
||||||
|
"parser": "<<<filtered>>>",
|
||||||
"pattern": undefined,
|
"pattern": undefined,
|
||||||
"pointer": "#/components/schemas/Dog/properties/packSize",
|
"pointer": "#/components/schemas/Dog/properties/packSize",
|
||||||
"rawSchema": Object {
|
"rawSchema": Object {
|
||||||
|
@ -77,10 +79,12 @@ exports[`Components SchemaView discriminator should correctly render discriminat
|
||||||
"example": undefined,
|
"example": undefined,
|
||||||
"externalDocs": undefined,
|
"externalDocs": undefined,
|
||||||
"format": undefined,
|
"format": undefined,
|
||||||
"isCircular": undefined,
|
"isCircular": false,
|
||||||
"isPrimitive": true,
|
"isPrimitive": true,
|
||||||
"nullable": false,
|
"nullable": false,
|
||||||
"options": "<<<filtered>>>",
|
"options": "<<<filtered>>>",
|
||||||
|
"parent": "<<<filtered>>>",
|
||||||
|
"parser": "<<<filtered>>>",
|
||||||
"pattern": undefined,
|
"pattern": undefined,
|
||||||
"pointer": "#/components/schemas/Dog/properties/type",
|
"pointer": "#/components/schemas/Dog/properties/type",
|
||||||
"rawSchema": Object {
|
"rawSchema": Object {
|
||||||
|
|
|
@ -177,6 +177,11 @@ export class OpenAPIParser {
|
||||||
|
|
||||||
shallowDeref<T extends object>(obj: OpenAPIRef | T): T {
|
shallowDeref<T extends object>(obj: OpenAPIRef | T): T {
|
||||||
if (this.isRef(obj)) {
|
if (this.isRef(obj)) {
|
||||||
|
const schemaName = getDefinitionName(obj.$ref);
|
||||||
|
if (schemaName && this.options.ignoreNamedSchemas.has(schemaName)) {
|
||||||
|
return { type: 'object', title: schemaName } as T;
|
||||||
|
}
|
||||||
|
|
||||||
return this.byRef<T>(obj.$ref)!;
|
return this.byRef<T>(obj.$ref)!;
|
||||||
}
|
}
|
||||||
return obj;
|
return obj;
|
||||||
|
|
|
@ -60,6 +60,7 @@ export class FieldModel {
|
||||||
infoOrRef: Referenced<OpenAPIParameter> & { name?: string; kind?: string },
|
infoOrRef: Referenced<OpenAPIParameter> & { name?: string; kind?: string },
|
||||||
pointer: string,
|
pointer: string,
|
||||||
options: RedocNormalizedOptions,
|
options: RedocNormalizedOptions,
|
||||||
|
parentSchema?: SchemaModel,
|
||||||
) {
|
) {
|
||||||
makeObservable(this);
|
makeObservable(this);
|
||||||
|
|
||||||
|
@ -76,7 +77,7 @@ export class FieldModel {
|
||||||
fieldSchema = info.content[serializationMime] && info.content[serializationMime].schema;
|
fieldSchema = info.content[serializationMime] && info.content[serializationMime].schema;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.schema = new SchemaModel(parser, fieldSchema || {}, pointer, options);
|
this.schema = new SchemaModel(parser, fieldSchema || {}, pointer, options, false, parentSchema);
|
||||||
this.description =
|
this.description =
|
||||||
info.description === undefined ? this.schema.description || '' : info.description;
|
info.description === undefined ? this.schema.description || '' : info.description;
|
||||||
this.example = info.example || this.schema.example;
|
this.example = info.example || this.schema.example;
|
||||||
|
|
|
@ -48,7 +48,7 @@ export class SchemaModel {
|
||||||
|
|
||||||
constraints: string[];
|
constraints: string[];
|
||||||
|
|
||||||
fields?: FieldModel[];
|
private _fields?: FieldModel[];
|
||||||
items?: SchemaModel;
|
items?: SchemaModel;
|
||||||
|
|
||||||
oneOf?: SchemaModel[];
|
oneOf?: SchemaModel[];
|
||||||
|
@ -66,21 +66,20 @@ export class SchemaModel {
|
||||||
* When true forces dereferencing in allOfs even if circular
|
* When true forces dereferencing in allOfs even if circular
|
||||||
*/
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
parser: OpenAPIParser,
|
private parser: OpenAPIParser,
|
||||||
schemaOrRef: Referenced<OpenAPISchema>,
|
schemaOrRef: Referenced<OpenAPISchema>,
|
||||||
pointer: string,
|
pointer: string,
|
||||||
private options: RedocNormalizedOptions,
|
private options: RedocNormalizedOptions,
|
||||||
isChild: boolean = false,
|
isChild: boolean = false,
|
||||||
|
private parent?: SchemaModel,
|
||||||
) {
|
) {
|
||||||
makeObservable(this);
|
makeObservable(this);
|
||||||
|
|
||||||
this.pointer = schemaOrRef.$ref || pointer || '';
|
this.pointer = schemaOrRef.$ref || pointer || '';
|
||||||
this.rawSchema = parser.deref(schemaOrRef);
|
this.rawSchema = parser.shallowDeref(schemaOrRef);
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
parser.exitRef(schemaOrRef);
|
|
||||||
parser.exitParents(this.schema);
|
parser.exitParents(this.schema);
|
||||||
|
|
||||||
if (options.showExtensions) {
|
if (options.showExtensions) {
|
||||||
|
@ -97,9 +96,35 @@ export class SchemaModel {
|
||||||
this.activeOneOf = idx;
|
this.activeOneOf = idx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get fields() {
|
||||||
|
if (this.isCircular) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this._fields && this.type === 'object') {
|
||||||
|
this._fields = buildFields(this.parser, this.schema, this.pointer, this.options, this);
|
||||||
|
}
|
||||||
|
return this._fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check circular refs for lazy fields
|
||||||
|
hasCircularParent($refs: string[]) {
|
||||||
|
if (this.parent) {
|
||||||
|
const res = $refs.some($ref => {
|
||||||
|
const parent = this.parent!;
|
||||||
|
if (parent.pointer === $ref) return true;
|
||||||
|
if (parent.schema.parentRefs?.some?.(parentRef => parentRef === $ref)) return true;
|
||||||
|
})
|
||||||
|
if (res) return true;
|
||||||
|
if (this.parent.hasCircularParent($refs)) return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
init(parser: OpenAPIParser, isChild: boolean) {
|
init(parser: OpenAPIParser, isChild: boolean) {
|
||||||
const schema = this.schema;
|
const schema = this.schema;
|
||||||
this.isCircular = schema['x-circular-ref'];
|
this.isCircular = schema['x-circular-ref'] || this.hasCircularParent([this.pointer, ...(this.schema.parentRefs || [])]);
|
||||||
|
|
||||||
this.title =
|
this.title =
|
||||||
schema.title || (isNamedDefinition(this.pointer) && JsonPointer.baseName(this.pointer)) || '';
|
schema.title || (isNamedDefinition(this.pointer) && JsonPointer.baseName(this.pointer)) || '';
|
||||||
|
@ -154,10 +179,8 @@ export class SchemaModel {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.type === 'object') {
|
if (this.type === 'array' && schema.items) {
|
||||||
this.fields = buildFields(parser, schema, this.pointer, this.options);
|
this.items = new SchemaModel(parser, schema.items, this.pointer + '/items', this.options, false, this);
|
||||||
} else if (this.type === 'array' && schema.items) {
|
|
||||||
this.items = new SchemaModel(parser, schema.items, this.pointer + '/items', this.options);
|
|
||||||
this.displayType = pluralizeType(this.items.displayType);
|
this.displayType = pluralizeType(this.items.displayType);
|
||||||
this.displayFormat = this.items.format;
|
this.displayFormat = this.items.format;
|
||||||
this.typePrefix = this.items.typePrefix + l('arrayOf');
|
this.typePrefix = this.items.typePrefix + l('arrayOf');
|
||||||
|
@ -199,6 +222,8 @@ export class SchemaModel {
|
||||||
} as OpenAPISchema,
|
} as OpenAPISchema,
|
||||||
this.pointer + '/oneOf/' + idx,
|
this.pointer + '/oneOf/' + idx,
|
||||||
this.options,
|
this.options,
|
||||||
|
false,
|
||||||
|
this
|
||||||
);
|
);
|
||||||
|
|
||||||
parser.exitRef(variant);
|
parser.exitRef(variant);
|
||||||
|
@ -319,7 +344,7 @@ export class SchemaModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.oneOf = refs.map(({ $ref, name }) => {
|
this.oneOf = refs.map(({ $ref, name }) => {
|
||||||
const innerSchema = new SchemaModel(parser, parser.byRef($ref)!, $ref, this.options, true);
|
const innerSchema = new SchemaModel(parser, parser.byRef($ref)!, $ref, this.options, true, this.parent);
|
||||||
innerSchema.title = name;
|
innerSchema.title = name;
|
||||||
return innerSchema;
|
return innerSchema;
|
||||||
});
|
});
|
||||||
|
@ -331,6 +356,7 @@ function buildFields(
|
||||||
schema: OpenAPISchema,
|
schema: OpenAPISchema,
|
||||||
$ref: string,
|
$ref: string,
|
||||||
options: RedocNormalizedOptions,
|
options: RedocNormalizedOptions,
|
||||||
|
parent?: SchemaModel
|
||||||
): FieldModel[] {
|
): FieldModel[] {
|
||||||
const props = schema.properties || {};
|
const props = schema.properties || {};
|
||||||
const additionalProps = schema.additionalProperties;
|
const additionalProps = schema.additionalProperties;
|
||||||
|
@ -360,6 +386,7 @@ function buildFields(
|
||||||
},
|
},
|
||||||
$ref + '/properties/' + fieldName,
|
$ref + '/properties/' + fieldName,
|
||||||
options,
|
options,
|
||||||
|
parent
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -386,6 +413,7 @@ function buildFields(
|
||||||
},
|
},
|
||||||
$ref + '/additionalProperties',
|
$ref + '/additionalProperties',
|
||||||
options,
|
options,
|
||||||
|
parent
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user