fix: lazy fields to prevent crash on complex nested oneOf's

This commit is contained in:
romanhotsiy 2020-10-27 10:38:31 +02:00
parent fd6171b93a
commit ec5d11838d
No known key found for this signature in database
GPG Key ID: 0BC2221278CCBBB8
5 changed files with 54 additions and 16 deletions

View File

@ -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();
}); });
}); });
}); });

View File

@ -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 {

View File

@ -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;

View File

@ -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;

View File

@ -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
), ),
); );
} }