Immediately exit from allOf's children, but use separate refCounter to do not enter into same scheme recursively
Also add pseudo-recursive example
This commit is contained in:
m0003r 2020-02-27 11:45:42 +03:00
parent c05db38576
commit d5890fc26b
3 changed files with 84 additions and 11 deletions

View File

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

View File

@ -18,7 +18,7 @@ export type MergedOpenAPISchema = OpenAPISchema & { parentRefs?: string[] };
* Helper class to keep track of visited references to avoid * Helper class to keep track of visited references to avoid
* endless recursion because of circular refs * endless recursion because of circular refs
*/ */
class RefCounter { export class RefCounter {
_counter = {}; _counter = {};
reset(): void { reset(): void {
@ -46,6 +46,8 @@ export class OpenAPIParser {
spec: OpenAPISpec; spec: OpenAPISpec;
mergeRefs: Set<string>; mergeRefs: Set<string>;
schemaCounter: RefCounter = new RefCounter();
private _refCounter: RefCounter = new RefCounter(); private _refCounter: RefCounter = new RefCounter();
constructor( constructor(
@ -221,6 +223,7 @@ export class OpenAPIParser {
const resolved = this.deref(subSchema, forceCircular); const resolved = this.deref(subSchema, forceCircular);
const subRef = subSchema.$ref || undefined; const subRef = subSchema.$ref || undefined;
this.exitRef(subSchema);
const subMerged = this.mergeAllOf(resolved, subRef, forceCircular, used$Refs); const subMerged = this.mergeAllOf(resolved, subRef, forceCircular, used$Refs);
receiver.parentRefs!.push(...(subMerged.parentRefs || [])); receiver.parentRefs!.push(...(subMerged.parentRefs || []));
return { return {
@ -311,8 +314,8 @@ export class OpenAPIParser {
return res; return res;
} }
exitParents(shema: MergedOpenAPISchema) { exitParents(schema: MergedOpenAPISchema) {
for (const parent$ref of shema.parentRefs || []) { for (const parent$ref of schema.parentRefs || []) {
this.exitRef({ $ref: parent$ref }); this.exitRef({ $ref: parent$ref });
} }
} }

View File

@ -2,7 +2,7 @@ import { action, observable } from 'mobx';
import { OpenAPIExternalDocumentation, OpenAPISchema, Referenced } from '../../types'; import { OpenAPIExternalDocumentation, OpenAPISchema, Referenced } from '../../types';
import { OpenAPIParser } from '../OpenAPIParser'; import {OpenAPIParser, RefCounter} from '../OpenAPIParser';
import { RedocNormalizedOptions } from '../RedocNormalizedOptions'; import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
import { FieldModel } from './Field'; import { FieldModel } from './Field';
@ -21,6 +21,8 @@ import {
import { l } from '../Labels'; import { l } from '../Labels';
const schemaCounter = new RefCounter();
// TODO: refactor this model, maybe use getters instead of copying all the values // TODO: refactor this model, maybe use getters instead of copying all the values
export class SchemaModel { export class SchemaModel {
pointer: string; pointer: string;
@ -73,16 +75,26 @@ export class SchemaModel {
isChild: boolean = false, isChild: boolean = false,
) { ) {
this.pointer = schemaOrRef.$ref || pointer || ''; this.pointer = schemaOrRef.$ref || pointer || '';
this.rawSchema = parser.deref(schemaOrRef); let visited = false;
this.schema = parser.mergeAllOf(this.rawSchema, this.pointer, isChild); 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.exitRef(schemaOrRef);
parser.exitParents(this.schema); parser.exitParents(this.schema);
if (options.showExtensions) { if (options.showExtensions) {
this.extensions = extractExtensions(this.schema, options.showExtensions); this.extensions = extractExtensions(this.schema, options.showExtensions);
}
}
if (this.pointer) {
schemaCounter.exit(this.pointer);
} }
} }