diff --git a/src/parser.ts b/src/parser.ts index 443ce7b..2f3f0eb 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -18,12 +18,18 @@ export async function parse( options: BundlerOptions = {} ) { /* eslint-disable indent */ + + let circular: boolean | 'ignore' = false; + if (options.dereference === 'ignore-circular') { + circular = 'ignore'; + } + // It is assumed that there will be major Spec versions 4, 5 and on. switch (specVersion) { case 2: RefParserOptions = { dereference: { - circular: false, + circular, // prettier-ignore excludedPathMatcher: (path: string): any => { // eslint-disable-line return; @@ -39,7 +45,7 @@ export async function parse( case 3: RefParserOptions = { dereference: { - circular: false, + circular, excludedPathMatcher: (path: string): any => { return ( // prettier-ignore @@ -69,5 +75,9 @@ export async function parse( ); } - return await $RefParser.dereference(JSONSchema, RefParserOptions) as AsyncAPIObject; + let bundled = JSONSchema; + if (circular !== false) { + bundled = await $RefParser.bundle(bundled, RefParserOptions); + } + return await $RefParser.dereference(bundled, RefParserOptions) as AsyncAPIObject; } diff --git a/src/spec-types.ts b/src/spec-types.ts index 59c8520..adbabea 100644 --- a/src/spec-types.ts +++ b/src/spec-types.ts @@ -455,4 +455,5 @@ export interface Options { base?: string; baseDir?: string; xOrigin?: boolean; + dereference?: string; } diff --git a/tests/circular.yml b/tests/circular.yml new file mode 100644 index 0000000..f4fec28 --- /dev/null +++ b/tests/circular.yml @@ -0,0 +1,20 @@ +asyncapi: 3.0.0 +info: + title: Test model + version: 1.0.0 +components: + messages: + btree: + payload: + type: object + properties: + root: + $ref: '#/components/messages/node' + node: + payload: + type: object + properties: + left: + $ref: '#/components/messages/node' + right: + $ref: '#/components/messages/node' diff --git a/tests/circular/asyncapi.yaml b/tests/circular/asyncapi.yaml new file mode 100644 index 0000000..56228ea --- /dev/null +++ b/tests/circular/asyncapi.yaml @@ -0,0 +1,10 @@ +asyncapi: 3.0.0 +info: + title: Test model + version: 1.0.0 +components: + messages: + btree: + title: BTree + payload: + $ref: "./btree.yml" diff --git a/tests/circular/btree.yml b/tests/circular/btree.yml new file mode 100644 index 0000000..5c1aa1f --- /dev/null +++ b/tests/circular/btree.yml @@ -0,0 +1,6 @@ +$schema: "https://json-schema.org/draft-07/schema#" +title: BTree +type: object +properties: + root: + $ref: "./node.yml" diff --git a/tests/circular/node.yml b/tests/circular/node.yml new file mode 100644 index 0000000..15f260e --- /dev/null +++ b/tests/circular/node.yml @@ -0,0 +1,8 @@ +$schema: "https://json-schema.org/draft-07/schema#" +title: Node +type: object +properties: + left: + $ref: "#" + right: + $ref: "#" diff --git a/tests/lib/index.spec.ts b/tests/lib/index.spec.ts index c2329e8..564959f 100644 --- a/tests/lib/index.spec.ts +++ b/tests/lib/index.spec.ts @@ -378,6 +378,88 @@ describe('[integration testing] bundler should ', () => { expect(document.json()).toMatchObject(resultingObject); }); + + test('should throw if circular `$ref` exists and derefrence for circular references is disabled for single file', async () => { + const files = ['circular.yml']; + + await expect(async () => { + await bundle(files, { + xOrigin: true, + baseDir: path.resolve(process.cwd(), './tests'), + dereference: 'strict', + }) + }).rejects.toThrow(Error); + }); + + test('should not throw if circular `$ref` exists and derefrence for circular references is ignored for single file', async () => { + const files = ['circular.yml']; + + const response = await bundle(files, { + xOrigin: true, + baseDir: path.resolve(process.cwd(), './tests'), + dereference: 'ignore-circular', + }); + expect(response).resolves; + }); + + test('should throw if circular `$ref` exists and derefrence for circular references is disabled for multiple files', async () => { + const files = ['asyncapi.yaml']; + + await expect(async () => { + await bundle(files, { + xOrigin: true, + baseDir: path.resolve(process.cwd(), './tests/circular'), + dereference: 'strict', + }) + }).rejects.toThrow(Error); + }); + + test('should not throw if circular `$ref` exists and derefrence for circular references is ignored for multiple files', async () => { + const resultingObject = { + asyncapi: '3.0.0', + info: { + title: 'Test model', + version: '1.0.0', + }, + components: { + messages: { + btree: { + title: 'BTree', + payload: { + $schema: 'https://json-schema.org/draft-07/schema#', + title: 'BTree', + type: 'object', + properties: { + root: { + $schema: 'https://json-schema.org/draft-07/schema#', + title: 'Node', + type: 'object', + properties: { + left: { + $ref: '#/components/messages/btree/payload/properties/root' + }, + right: { + $ref: '#/components/messages/btree/payload/properties/root' + }, + }, + }, + }, + }, + }, + }, + }, + }; + + const files = ['asyncapi.yaml']; + + const response = await bundle(files, { + xOrigin: true, + baseDir: path.resolve(process.cwd(), './tests/circular'), + dereference: 'ignore-circular', + }); + + expect(response.json()).toMatchObject(resultingObject); + }); }); describe('[unit testing]', () => {