From d5890fc26b2adbbf700b15e6121fcbd6fdec3042 Mon Sep 17 00:00:00 2001 From: m0003r Date: Thu, 27 Feb 2020 11:45:42 +0300 Subject: [PATCH] Fix #1154 Immediately exit from allOf's children, but use separate refCounter to do not enter into same scheme recursively Also add pseudo-recursive example --- demo/pseudo-recursive.yaml | 58 +++++++++++++++++++++++++++++++++++ src/services/OpenAPIParser.ts | 9 ++++-- src/services/models/Schema.ts | 28 ++++++++++++----- 3 files changed, 84 insertions(+), 11 deletions(-) create mode 100644 demo/pseudo-recursive.yaml diff --git a/demo/pseudo-recursive.yaml b/demo/pseudo-recursive.yaml new file mode 100644 index 00000000..68e48327 --- /dev/null +++ b/demo/pseudo-recursive.yaml @@ -0,0 +1,58 @@ +openapi: 3.0.0 +info: + title: False positive recursion + version: '3.0' +tags: + - name: a + +components: + schemas: + id: + tags: + - a + type: object + properties: + id: + type: number + description: id + pet: + tags: + - a + type: object + properties: + name: + type: string + friends: + type: array + items: + allOf: + - $ref: '#/components/schemas/pet' +paths: + /documents: + get: + summary: Example + responses: + '200': + description: Pseudo-recursvie + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/id' + - properties: + second: + allOf: + - $ref: '#/components/schemas/id' + - properties: + third: + allOf: + - $ref: '#/components/schemas/id' + - properties: + something: + type: string + '204': + description: Recursvie + content: + application/json: + schema: + $ref: '#/components/schemas/pet' diff --git a/src/services/OpenAPIParser.ts b/src/services/OpenAPIParser.ts index 8adbc7ab..a7ef0cff 100644 --- a/src/services/OpenAPIParser.ts +++ b/src/services/OpenAPIParser.ts @@ -18,7 +18,7 @@ export type MergedOpenAPISchema = OpenAPISchema & { parentRefs?: string[] }; * Helper class to keep track of visited references to avoid * endless recursion because of circular refs */ -class RefCounter { +export class RefCounter { _counter = {}; reset(): void { @@ -46,6 +46,8 @@ export class OpenAPIParser { spec: OpenAPISpec; mergeRefs: Set; + schemaCounter: RefCounter = new RefCounter(); + private _refCounter: RefCounter = new RefCounter(); constructor( @@ -221,6 +223,7 @@ export class OpenAPIParser { const resolved = this.deref(subSchema, forceCircular); const subRef = subSchema.$ref || undefined; + this.exitRef(subSchema); const subMerged = this.mergeAllOf(resolved, subRef, forceCircular, used$Refs); receiver.parentRefs!.push(...(subMerged.parentRefs || [])); return { @@ -311,8 +314,8 @@ export class OpenAPIParser { return res; } - exitParents(shema: MergedOpenAPISchema) { - for (const parent$ref of shema.parentRefs || []) { + exitParents(schema: MergedOpenAPISchema) { + for (const parent$ref of schema.parentRefs || []) { this.exitRef({ $ref: parent$ref }); } } diff --git a/src/services/models/Schema.ts b/src/services/models/Schema.ts index 7fb5a8e9..22f89a7d 100644 --- a/src/services/models/Schema.ts +++ b/src/services/models/Schema.ts @@ -2,7 +2,7 @@ import { action, observable } from 'mobx'; import { OpenAPIExternalDocumentation, OpenAPISchema, Referenced } from '../../types'; -import { OpenAPIParser } from '../OpenAPIParser'; +import {OpenAPIParser, RefCounter} from '../OpenAPIParser'; import { RedocNormalizedOptions } from '../RedocNormalizedOptions'; import { FieldModel } from './Field'; @@ -21,6 +21,8 @@ import { import { l } from '../Labels'; +const schemaCounter = new RefCounter(); + // TODO: refactor this model, maybe use getters instead of copying all the values export class SchemaModel { pointer: string; @@ -73,16 +75,26 @@ export class SchemaModel { isChild: boolean = false, ) { this.pointer = schemaOrRef.$ref || pointer || ''; - this.rawSchema = parser.deref(schemaOrRef); - this.schema = parser.mergeAllOf(this.rawSchema, this.pointer, isChild); + let visited = false; + if (this.pointer) { + visited = schemaCounter.visited(this.pointer); + schemaCounter.visit(this.pointer); + } + if (!visited) { + this.rawSchema = parser.deref(schemaOrRef); + 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.exitRef(schemaOrRef); + parser.exitParents(this.schema); - if (options.showExtensions) { - this.extensions = extractExtensions(this.schema, options.showExtensions); + if (options.showExtensions) { + this.extensions = extractExtensions(this.schema, options.showExtensions); + } + } + if (this.pointer) { + schemaCounter.exit(this.pointer); } }