mirror of
				https://github.com/Redocly/redoc.git
				synced 2025-10-26 13:31:06 +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