diff --git a/packages/apidom-reference/src/dereference/strategies/openapi-3-1-swagger-client/index.ts b/packages/apidom-reference/src/dereference/strategies/openapi-3-1-swagger-client/index.ts index 1a50f03868..5f08a26467 100644 --- a/packages/apidom-reference/src/dereference/strategies/openapi-3-1-swagger-client/index.ts +++ b/packages/apidom-reference/src/dereference/strategies/openapi-3-1-swagger-client/index.ts @@ -22,11 +22,23 @@ import OpenApi3_1DereferenceVisitor from './visitor'; const visitAsync = visit[Symbol.for('nodejs.util.promisify.custom')]; // eslint-disable-next-line @typescript-eslint/naming-convention -const OpenApi3_1SwaggerClientDereferenceStrategy: stampit.Stamp = stampit( - DereferenceStrategy, - { - init() { - this.name = 'openapi-3-1'; +interface IOpenApi3_1SwaggerClientDereferenceStrategy extends IDereferenceStrategy { + useCircularStructures: boolean; +} + +// eslint-disable-next-line @typescript-eslint/naming-convention +const OpenApi3_1SwaggerClientDereferenceStrategy: stampit.Stamp = + stampit(DereferenceStrategy, { + props: { + useCircularStructures: true, + }, + init( + this: IOpenApi3_1SwaggerClientDereferenceStrategy, + { useCircularStructures = this.useCircularStructures } = {}, + ) { + // @ts-ignore + this.name = 'openapi-3-1-swagger-client'; + this.useCircularStructures = useCircularStructures; }, methods: { canDereference(file: IFile): boolean { @@ -52,14 +64,19 @@ const OpenApi3_1SwaggerClientDereferenceStrategy: stampit.Stamp { // configure custom parser plugins globally @@ -43,9 +45,20 @@ export const before = () => { return resolver; }); + + // configure custom dereference strategy globally + options.dereference.strategies = options.dereference.strategies.map((strategy) => { + // @ts-ignore + if (strategy.name === 'openapi-3-1') { + return OpenApi3_1SwaggerClientDereferenceStrategy(); + } + + return strategy; + }); }; export const after = () => { options.parse.parsers = originalParsers; options.resolve.resolvers = originalResolvers; + options.dereference.strategies = originalDereferenceStrategies; }; diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-3-1-swagger-client/schema-object/fixtures/cycle-external-disabled-http/dereferenced.json b/packages/apidom-reference/test/dereference/strategies/openapi-3-1-swagger-client/schema-object/fixtures/cycle-external-disabled-http/dereferenced.json new file mode 100644 index 0000000000..325bce9102 --- /dev/null +++ b/packages/apidom-reference/test/dereference/strategies/openapi-3-1-swagger-client/schema-object/fixtures/cycle-external-disabled-http/dereferenced.json @@ -0,0 +1,21 @@ +[ + { + "openapi": "3.1.0", + "components": { + "schemas": { + "User": { + "type": "object", + "properties": { + "profile": { + "properties": { + "parent": { + "$ref": "http://localhost:8123/ex.json#/$defs/UserProfile" + } + } + } + } + } + } + } + } +] diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-3-1-swagger-client/schema-object/fixtures/cycle-external-disabled-http/ex.json b/packages/apidom-reference/test/dereference/strategies/openapi-3-1-swagger-client/schema-object/fixtures/cycle-external-disabled-http/ex.json new file mode 100644 index 0000000000..3867c3eeb0 --- /dev/null +++ b/packages/apidom-reference/test/dereference/strategies/openapi-3-1-swagger-client/schema-object/fixtures/cycle-external-disabled-http/ex.json @@ -0,0 +1,11 @@ +{ + "$defs": { + "UserProfile": { + "properties": { + "parent": { + "$ref": "#/$defs/UserProfile" + } + } + } + } +} diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-3-1-swagger-client/schema-object/fixtures/cycle-external-disabled-http/root.json b/packages/apidom-reference/test/dereference/strategies/openapi-3-1-swagger-client/schema-object/fixtures/cycle-external-disabled-http/root.json new file mode 100644 index 0000000000..15ee3edf1c --- /dev/null +++ b/packages/apidom-reference/test/dereference/strategies/openapi-3-1-swagger-client/schema-object/fixtures/cycle-external-disabled-http/root.json @@ -0,0 +1,15 @@ +{ + "openapi": "3.1.0", + "components": { + "schemas": { + "User": { + "type": "object", + "properties": { + "profile": { + "$ref": "./ex.json#/$defs/UserProfile" + } + } + } + } + } +} diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-3-1-swagger-client/schema-object/fixtures/cycle-external-disabled/dereferenced.json b/packages/apidom-reference/test/dereference/strategies/openapi-3-1-swagger-client/schema-object/fixtures/cycle-external-disabled/dereferenced.json new file mode 100644 index 0000000000..05b2a3dd83 --- /dev/null +++ b/packages/apidom-reference/test/dereference/strategies/openapi-3-1-swagger-client/schema-object/fixtures/cycle-external-disabled/dereferenced.json @@ -0,0 +1,21 @@ +[ + { + "openapi": "3.1.0", + "components": { + "schemas": { + "User": { + "type": "object", + "properties": { + "profile": { + "properties": { + "parent": { + "$ref": "#/$defs/UserProfile" + } + } + } + } + } + } + } + } +] diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-3-1-swagger-client/schema-object/fixtures/cycle-external-disabled/ex.json b/packages/apidom-reference/test/dereference/strategies/openapi-3-1-swagger-client/schema-object/fixtures/cycle-external-disabled/ex.json new file mode 100644 index 0000000000..3867c3eeb0 --- /dev/null +++ b/packages/apidom-reference/test/dereference/strategies/openapi-3-1-swagger-client/schema-object/fixtures/cycle-external-disabled/ex.json @@ -0,0 +1,11 @@ +{ + "$defs": { + "UserProfile": { + "properties": { + "parent": { + "$ref": "#/$defs/UserProfile" + } + } + } + } +} diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-3-1-swagger-client/schema-object/fixtures/cycle-external-disabled/root.json b/packages/apidom-reference/test/dereference/strategies/openapi-3-1-swagger-client/schema-object/fixtures/cycle-external-disabled/root.json new file mode 100644 index 0000000000..15ee3edf1c --- /dev/null +++ b/packages/apidom-reference/test/dereference/strategies/openapi-3-1-swagger-client/schema-object/fixtures/cycle-external-disabled/root.json @@ -0,0 +1,15 @@ +{ + "openapi": "3.1.0", + "components": { + "schemas": { + "User": { + "type": "object", + "properties": { + "profile": { + "$ref": "./ex.json#/$defs/UserProfile" + } + } + } + } + } +} diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-3-1-swagger-client/schema-object/fixtures/cycle-internal-disabled-http/dereferenced.json b/packages/apidom-reference/test/dereference/strategies/openapi-3-1-swagger-client/schema-object/fixtures/cycle-internal-disabled-http/dereferenced.json new file mode 100644 index 0000000000..9f25cf0878 --- /dev/null +++ b/packages/apidom-reference/test/dereference/strategies/openapi-3-1-swagger-client/schema-object/fixtures/cycle-internal-disabled-http/dereferenced.json @@ -0,0 +1,17 @@ +[ + { + "openapi": "3.1.0", + "components": { + "schemas": { + "User": { + "type": "object", + "properties": { + "parent": { + "$ref": "http://localhost:8123/root.json#/components/schemas/User" + } + } + } + } + } + } +] diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-3-1-swagger-client/schema-object/fixtures/cycle-internal-disabled-http/root.json b/packages/apidom-reference/test/dereference/strategies/openapi-3-1-swagger-client/schema-object/fixtures/cycle-internal-disabled-http/root.json new file mode 100644 index 0000000000..badb77703d --- /dev/null +++ b/packages/apidom-reference/test/dereference/strategies/openapi-3-1-swagger-client/schema-object/fixtures/cycle-internal-disabled-http/root.json @@ -0,0 +1,15 @@ +{ + "openapi": "3.1.0", + "components": { + "schemas": { + "User": { + "type": "object", + "properties": { + "parent": { + "$ref": "#/components/schemas/User" + } + } + } + } + } +} diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-3-1-swagger-client/schema-object/fixtures/cycle-internal-disabled/dereferenced.json b/packages/apidom-reference/test/dereference/strategies/openapi-3-1-swagger-client/schema-object/fixtures/cycle-internal-disabled/dereferenced.json new file mode 100644 index 0000000000..eece862a09 --- /dev/null +++ b/packages/apidom-reference/test/dereference/strategies/openapi-3-1-swagger-client/schema-object/fixtures/cycle-internal-disabled/dereferenced.json @@ -0,0 +1,17 @@ +[ + { + "openapi": "3.1.0", + "components": { + "schemas": { + "User": { + "type": "object", + "properties": { + "parent": { + "$ref": "#/components/schemas/User" + } + } + } + } + } + } +] diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-3-1-swagger-client/schema-object/fixtures/cycle-internal-disabled/root.json b/packages/apidom-reference/test/dereference/strategies/openapi-3-1-swagger-client/schema-object/fixtures/cycle-internal-disabled/root.json new file mode 100644 index 0000000000..badb77703d --- /dev/null +++ b/packages/apidom-reference/test/dereference/strategies/openapi-3-1-swagger-client/schema-object/fixtures/cycle-internal-disabled/root.json @@ -0,0 +1,15 @@ +{ + "openapi": "3.1.0", + "components": { + "schemas": { + "User": { + "type": "object", + "properties": { + "parent": { + "$ref": "#/components/schemas/User" + } + } + } + } + } +} diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-3-1-swagger-client/schema-object/index.ts b/packages/apidom-reference/test/dereference/strategies/openapi-3-1-swagger-client/schema-object/index.ts index dace965b45..ed6b84d254 100644 --- a/packages/apidom-reference/test/dereference/strategies/openapi-3-1-swagger-client/schema-object/index.ts +++ b/packages/apidom-reference/test/dereference/strategies/openapi-3-1-swagger-client/schema-object/index.ts @@ -11,10 +11,11 @@ import { MaximumResolverDepthError, ResolverError, } from '../../../../../src/util/errors'; -import { loadJsonFile } from '../../../../helpers'; +import { createHTTPServer, loadJsonFile } from '../../../../helpers'; import { EvaluationJsonSchema$anchorError } from '../../../../../src/dereference/strategies/openapi-3-1/selectors/$anchor/errors'; import { EvaluationJsonSchemaUriError } from '../../../../../src/dereference/strategies/openapi-3-1/selectors/uri/errors'; import * as bootstrap from '../bootstrap'; +import OpenApi3_1SwaggerClientDereferenceStrategy from '../../../../../src/dereference/strategies/openapi-3-1-swagger-client'; const rootFixturePath = path.join(__dirname, 'fixtures'); @@ -88,9 +89,8 @@ describe('dereference', function () { }); context('given Schema Objects with internal cycles', function () { - const fixturePath = path.join(rootFixturePath, 'cycle-internal'); - specify('should dereference', async function () { + const fixturePath = path.join(rootFixturePath, 'cycle-internal'); const rootFilePath = path.join(fixturePath, 'root.json'); const dereferenced = await dereference(rootFilePath, { parse: { mediaType: mediaTypes.latest('json') }, @@ -103,12 +103,55 @@ describe('dereference', function () { assert.strictEqual(parent, cyclicParent); }); + + context('and useCircularStructures=false', function () { + specify('should avoid cycles by skipping transclusion', async function () { + const fixturePath = path.join(rootFixturePath, 'cycle-internal-disabled'); + const rootFilePath = path.join(fixturePath, 'root.json'); + const actual = await dereference(rootFilePath, { + parse: { mediaType: mediaTypes.latest('json') }, + dereference: { + strategies: [ + OpenApi3_1SwaggerClientDereferenceStrategy({ useCircularStructures: false }), + ], + }, + }); + const expected = loadJsonFile(path.join(fixturePath, 'dereferenced.json')); + + assert.deepEqual(toValue(actual), expected); + }); + + context('and using HTTP protocol', function () { + specify('should make JSON Pointer absolute', async function () { + let httpServer: any; + + try { + const fixturePath = path.join(rootFixturePath, 'cycle-external-disabled-http'); + httpServer = createHTTPServer({ port: 8123, cwd: fixturePath }); + const actual = await dereference('http://localhost:8123/root.json', { + parse: { mediaType: mediaTypes.latest('json') }, + dereference: { + strategies: [ + OpenApi3_1SwaggerClientDereferenceStrategy({ + useCircularStructures: false, + }), + ], + }, + }); + const expected = loadJsonFile(path.join(fixturePath, 'dereferenced.json')); + + assert.deepEqual(toValue(actual), expected); + } finally { + await httpServer?.terminate(); + } + }); + }); + }); }); context('given Schema Objects with external cycles', function () { - const fixturePath = path.join(rootFixturePath, 'cycle-external'); - specify('should dereference', async function () { + const fixturePath = path.join(rootFixturePath, 'cycle-external'); const rootFilePath = path.join(fixturePath, 'root.json'); const dereferenced = await dereference(rootFilePath, { parse: { mediaType: mediaTypes.latest('json') }, @@ -124,6 +167,24 @@ describe('dereference', function () { assert.strictEqual(parent, cyclicParent); }); + + context('and useCircularStructures=false', function () { + specify('should avoid cycles by skipping transclusion', async function () { + const fixturePath = path.join(rootFixturePath, 'cycle-external-disabled'); + const rootFilePath = path.join(fixturePath, 'root.json'); + const actual = await dereference(rootFilePath, { + parse: { mediaType: mediaTypes.latest('json') }, + dereference: { + strategies: [ + OpenApi3_1SwaggerClientDereferenceStrategy({ useCircularStructures: false }), + ], + }, + }); + const expected = loadJsonFile(path.join(fixturePath, 'dereferenced.json')); + + assert.deepEqual(toValue(actual), expected); + }); + }); }); context('given Schema Objects with internal and external cycles', function () { @@ -777,6 +838,7 @@ describe('dereference', function () { specify('should throw error', async function () { const rootFilePath = path.join(fixturePath, 'root.json'); + try { await dereference(rootFilePath, { parse: { mediaType: mediaTypes.latest('json') }, @@ -786,6 +848,26 @@ describe('dereference', function () { assert.instanceOf(e, DereferenceError); } }); + + context('and useCircularStructures=false', function () { + specify('should throw error', async function () { + const rootFilePath = path.join(fixturePath, 'root.json'); + + try { + await dereference(rootFilePath, { + parse: { mediaType: mediaTypes.latest('json') }, + dereference: { + strategies: [ + OpenApi3_1SwaggerClientDereferenceStrategy({ useCircularStructures: false }), + ], + }, + }); + assert.fail('should throw DereferenceError'); + } catch (e) { + assert.instanceOf(e, DereferenceError); + } + }); + }); }); context('given Schema Objects with indirect circular internal reference', function () { @@ -793,6 +875,7 @@ describe('dereference', function () { specify('should throw error', async function () { const rootFilePath = path.join(fixturePath, 'root.json'); + try { await dereference(rootFilePath, { parse: { mediaType: mediaTypes.latest('json') }, @@ -802,6 +885,25 @@ describe('dereference', function () { assert.instanceOf(e, DereferenceError); } }); + + context('and useCircularStructures=false', function () { + specify('should throw error', async function () { + const rootFilePath = path.join(fixturePath, 'root.json'); + try { + await dereference(rootFilePath, { + parse: { mediaType: mediaTypes.latest('json') }, + dereference: { + strategies: [ + OpenApi3_1SwaggerClientDereferenceStrategy({ useCircularStructures: false }), + ], + }, + }); + assert.fail('should throw DereferenceError'); + } catch (e) { + assert.instanceOf(e, DereferenceError); + } + }); + }); }); }); });