diff --git a/src/json-schema-parser.test.ts b/src/json-schema-parser.test.ts index 4de9662..da6f811 100644 --- a/src/json-schema-parser.test.ts +++ b/src/json-schema-parser.test.ts @@ -99,4 +99,39 @@ describe('parser', () => { // ASSERT expect(errors).toEqual([]); }); + + it('parses a schema with a circular reference', async () => { + // ARRANGE + const schema = { + $schema: 'http://json-schema.org/draft-07/schema#', + $id: 'https://example.com/mutual.schema.json', + $ref: '#/definitions/A', + definitions: { + A: { + type: 'object', + properties: { + b: { + $ref: '#/definitions/B', + }, + }, + }, + B: { + type: 'object', + properties: { + a: { + $ref: '#/definitions/A', + }, + }, + }, + }, + }; + + const sourceContent = JSON.stringify(schema); + + // ACT + const service = (await parser(sourceContent, absoluteSourcePath)).service; + + // ASSERT + expect(service).toBeTruthy(); + }); }); diff --git a/src/json-schema-parser.ts b/src/json-schema-parser.ts index ea2228c..8f92317 100644 --- a/src/json-schema-parser.ts +++ b/src/json-schema-parser.ts @@ -49,6 +49,7 @@ class JsonSchemaParser { private readonly types = new Map(); private readonly enums = new Map(); private readonly unions = new Map(); + private readonly resolvingRefs = new Set(); private readonly violations: Violation[] = []; parse(): { service: Service; violations: Violation[] } { @@ -208,25 +209,48 @@ class JsonSchemaParser { loc: string | undefined, ): { memberValue: MemberValue; inheritedDescription: StringLiteral[] } { if (schema.ref) { - const resolved = resolve( - this.source.node, - schema.ref.value, - AST.SchemaNode, - ); + if (this.resolvingRefs.has(schema.ref.value)) { + const resolved = resolve( + this.source.node, + schema.ref.value, + AST.SchemaNode, + ); + const typeName = this.parseTypeName(resolved); - if (resolved) { - return this.parseType(resolved, loc); - } else { - const { range, sourceIndex } = decodeRange( - encodeRange(0, schema.ref.loc), + if (typeName) { + return { + memberValue: { kind: 'ComplexValue', typeName, rules: [] }, + inheritedDescription: [], + }; + } + + return { memberValue: untyped(), inheritedDescription: [] }; + } + + this.resolvingRefs.add(schema.ref.value); + try { + const resolved = resolve( + this.source.node, + schema.ref.value, + AST.SchemaNode, ); - this.violations.push({ - code: 'PARSER_ERROR', - message: `Cannot resolve ref '${schema.ref.value}'`, - severity: 'error', - range, - sourcePath: this.sourcePaths[sourceIndex], - }); + + if (resolved) { + return this.parseType(resolved, loc); + } else { + const { range, sourceIndex } = decodeRange( + encodeRange(0, schema.ref.loc), + ); + this.violations.push({ + code: 'PARSER_ERROR', + message: `Cannot resolve ref '${schema.ref.value}'`, + severity: 'error', + range, + sourcePath: this.sourcePaths[sourceIndex], + }); + } + } finally { + this.resolvingRefs.delete(schema.ref.value); } }