From aef1b0aecd5d27aa7e5fb911907d39507a099ef6 Mon Sep 17 00:00:00 2001 From: Vladimir Gorej Date: Tue, 30 Mar 2021 12:17:52 +0200 Subject: [PATCH] feat(reference): ignore external references by configration Closes #324 --- apidom/package-lock.json | 3 +- .../apidom-ns-openapi-3-1/package.json | 1 + .../apidom-ns-openapi-3-1/src/index.ts | 1 + .../apidom-ns-openapi-3-1/src/predicates.ts | 13 ++++++- .../strategies/openapi-3-1/visitor.ts | 6 +++ .../resolve/strategies/openapi-3-1/visitor.ts | 8 ++++ .../ignore-external/dereferenced.json | 37 +++++++++++++++++++ .../fixtures/ignore-external/ex.json | 8 ++++ .../fixtures/ignore-external/root.json | 29 +++++++++++++++ .../openapi-3-1/reference-object/index.ts | 15 ++++++++ .../fixtures/ignore-external/ex.json | 8 ++++ .../fixtures/ignore-external/root.json | 29 +++++++++++++++ .../openapi-3-1/reference-object/index.ts | 14 +++++++ 13 files changed, 170 insertions(+), 2 deletions(-) create mode 100644 apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/reference-object/fixtures/ignore-external/dereferenced.json create mode 100644 apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/reference-object/fixtures/ignore-external/ex.json create mode 100644 apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/reference-object/fixtures/ignore-external/root.json create mode 100644 apidom/packages/apidom-reference/test/resolve/strategies/openapi-3-1/reference-object/fixtures/ignore-external/ex.json create mode 100644 apidom/packages/apidom-reference/test/resolve/strategies/openapi-3-1/reference-object/fixtures/ignore-external/root.json diff --git a/apidom/package-lock.json b/apidom/package-lock.json index e84b25008a..aeb516bbfd 100644 --- a/apidom/package-lock.json +++ b/apidom/package-lock.json @@ -5229,7 +5229,8 @@ "@types/ramda": "=0.27.6", "apidom": "file:packages/apidom", "minim": "=0.23.8", - "ramda": "=0.27.0" + "ramda": "=0.27.0", + "ramda-adjunct": "=2.27.0" } }, "apidom-parser": { diff --git a/apidom/packages/apidom-ns-openapi-3-1/package.json b/apidom/packages/apidom-ns-openapi-3-1/package.json index c55bf74625..6eceefbdd0 100644 --- a/apidom/packages/apidom-ns-openapi-3-1/package.json +++ b/apidom/packages/apidom-ns-openapi-3-1/package.json @@ -25,6 +25,7 @@ "@babel/runtime-corejs3": "=7.12.5", "minim": "=0.23.8", "ramda": "=0.27.0", + "ramda-adjunct": "=2.27.0", "@types/ramda": "=0.27.6", "apidom": "file:../apidom" } diff --git a/apidom/packages/apidom-ns-openapi-3-1/src/index.ts b/apidom/packages/apidom-ns-openapi-3-1/src/index.ts index 650b7dcf2f..7b74a2fbda 100644 --- a/apidom/packages/apidom-ns-openapi-3-1/src/index.ts +++ b/apidom/packages/apidom-ns-openapi-3-1/src/index.ts @@ -29,6 +29,7 @@ export { isPathItemElement, isPathsElement, isReferenceElement, + isReferenceElementExternal, isRequestBodyElement, isResponseElement, isResponsesElement, diff --git a/apidom/packages/apidom-ns-openapi-3-1/src/predicates.ts b/apidom/packages/apidom-ns-openapi-3-1/src/predicates.ts index f8305bab79..01dda20aee 100644 --- a/apidom/packages/apidom-ns-openapi-3-1/src/predicates.ts +++ b/apidom/packages/apidom-ns-openapi-3-1/src/predicates.ts @@ -1,4 +1,5 @@ -import { allPass, either, is } from 'ramda'; +import { allPass, either, is, startsWith } from 'ramda'; +import { isNonEmptyString } from 'ramda-adjunct'; import { createPredicate } from 'apidom'; import CallbackElement from './elements/Callback'; @@ -180,6 +181,16 @@ export const isReferenceElement = createPredicate( }, ); +export const isReferenceElementExternal = (element: any): element is ReferenceElement => { + if (!isReferenceElement(element)) { + return false; + } + + const value = element.$ref.toValue(); + + return isNonEmptyString(value) && !startsWith('#', value); +}; + export const isRequestBodyElement = createPredicate( ({ hasBasicElementProps, isElementType, primitiveEq }) => { const isElementTypeRequestBody = isElementType('requestBody'); diff --git a/apidom/packages/apidom-reference/src/dereference/strategies/openapi-3-1/visitor.ts b/apidom/packages/apidom-reference/src/dereference/strategies/openapi-3-1/visitor.ts index 1b28cfc265..c5473fa859 100644 --- a/apidom/packages/apidom-reference/src/dereference/strategies/openapi-3-1/visitor.ts +++ b/apidom/packages/apidom-reference/src/dereference/strategies/openapi-3-1/visitor.ts @@ -7,6 +7,7 @@ import { isReferenceLikeElement, keyMap, ReferenceElement, + isReferenceElementExternal, } from 'apidom-ns-openapi-3-1'; import { Reference as IReference } from '../../../types'; @@ -68,6 +69,11 @@ const OpenApi3_1DereferenceVisitor = stampit({ }, async ReferenceElement(referenceElement: ReferenceElement) { + // ignore resolving external Reference Objects + if (!this.options.resolve.external && isReferenceElementExternal(referenceElement)) { + return false; + } + // @ts-ignore const reference = await this.toReference(referenceElement.$ref.toValue()); diff --git a/apidom/packages/apidom-reference/src/resolve/strategies/openapi-3-1/visitor.ts b/apidom/packages/apidom-reference/src/resolve/strategies/openapi-3-1/visitor.ts index bd988d57c7..e30507cbe0 100644 --- a/apidom/packages/apidom-reference/src/resolve/strategies/openapi-3-1/visitor.ts +++ b/apidom/packages/apidom-reference/src/resolve/strategies/openapi-3-1/visitor.ts @@ -8,6 +8,7 @@ import { isReferenceLikeElement, keyMap, ReferenceElement, + isReferenceElementExternal, } from 'apidom-ns-openapi-3-1'; import { Reference as IReference } from '../../../types'; @@ -78,6 +79,11 @@ const OpenApi3_1ResolveVisitor = stampit({ }, ReferenceElement(referenceElement: ReferenceElement) { + // ignore resolving external Reference Objects + if (!this.options.resolve.external && isReferenceElementExternal(referenceElement)) { + return false; + } + const uri = referenceElement.$ref.toValue(); const baseURI = this.toBaseURI(uri); @@ -85,6 +91,8 @@ const OpenApi3_1ResolveVisitor = stampit({ this.crawlingMap[baseURI] = this.toReference(uri); } this.crawledElements.push(referenceElement); + + return undefined; }, async crawlReferenceElement(referenceElement: ReferenceElement) { diff --git a/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/reference-object/fixtures/ignore-external/dereferenced.json b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/reference-object/fixtures/ignore-external/dereferenced.json new file mode 100644 index 0000000000..995356d521 --- /dev/null +++ b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/reference-object/fixtures/ignore-external/dereferenced.json @@ -0,0 +1,37 @@ +[ + { + "openapi": "3.1.0", + "components": { + "parameters": { + "userId": { + "name": "userId", + "in": "query", + "description": "override", + "required": true + }, + "indirection1": { + "name": "userId", + "in": "query", + "description": "ID of the user", + "required": true + }, + "indirection2": { + "name": "userId", + "in": "query", + "description": "ID of the user", + "required": true + }, + "userIdRef": { + "name": "userId", + "in": "query", + "description": "ID of the user", + "required": true + }, + "externalRef": { + "$ref": "./ex.json#/externalParameter", + "description": "another ref" + } + } + } + } +] diff --git a/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/reference-object/fixtures/ignore-external/ex.json b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/reference-object/fixtures/ignore-external/ex.json new file mode 100644 index 0000000000..fc8a07edcd --- /dev/null +++ b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/reference-object/fixtures/ignore-external/ex.json @@ -0,0 +1,8 @@ +{ + "externalParameter": { + "name": "externalParameter", + "in": "query", + "description": "this is parameter stored in external file", + "required": true + } +} diff --git a/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/reference-object/fixtures/ignore-external/root.json b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/reference-object/fixtures/ignore-external/root.json new file mode 100644 index 0000000000..802a6c0b39 --- /dev/null +++ b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/reference-object/fixtures/ignore-external/root.json @@ -0,0 +1,29 @@ +{ + "openapi": "3.1.0", + "components": { + "parameters": { + "userId": { + "$ref": "#/components/parameters/indirection1", + "description": "override" + }, + "indirection1": { + "$ref": "#/components/parameters/indirection2", + "summary": "indirect summary" + }, + "indirection2": { + "$ref": "#/components/parameters/userIdRef", + "summary": "indirect summary" + }, + "userIdRef": { + "name": "userId", + "in": "query", + "description": "ID of the user", + "required": true + }, + "externalRef": { + "$ref": "./ex.json#/externalParameter", + "description": "another ref" + } + } + } +} diff --git a/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/reference-object/index.ts b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/reference-object/index.ts index 2b84e5e507..50e544fbf8 100644 --- a/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/reference-object/index.ts +++ b/apidom/packages/apidom-reference/test/dereference/strategies/openapi-3-1/reference-object/index.ts @@ -123,6 +123,21 @@ describe('dereference', function () { }); }); + context('given Reference Objects with external resolution disabled', function () { + const fixturePath = path.join(rootFixturePath, 'ignore-external'); + + specify('should dereference', async function () { + const rootFilePath = path.join(fixturePath, 'root.json'); + const actual = await dereference(rootFilePath, { + parse: { mediaType: 'application/vnd.oai.openapi+json;version=3.1.0' }, + resolve: { external: false }, + }); + const expected = loadJsonFile(path.join(fixturePath, 'dereferenced.json')); + + assert.deepEqual(toValue(actual), expected); + }); + }); + context('given Reference Objects with direct circular internal reference', function () { const fixturePath = path.join(rootFixturePath, 'direct-internal-circular'); diff --git a/apidom/packages/apidom-reference/test/resolve/strategies/openapi-3-1/reference-object/fixtures/ignore-external/ex.json b/apidom/packages/apidom-reference/test/resolve/strategies/openapi-3-1/reference-object/fixtures/ignore-external/ex.json new file mode 100644 index 0000000000..fc8a07edcd --- /dev/null +++ b/apidom/packages/apidom-reference/test/resolve/strategies/openapi-3-1/reference-object/fixtures/ignore-external/ex.json @@ -0,0 +1,8 @@ +{ + "externalParameter": { + "name": "externalParameter", + "in": "query", + "description": "this is parameter stored in external file", + "required": true + } +} diff --git a/apidom/packages/apidom-reference/test/resolve/strategies/openapi-3-1/reference-object/fixtures/ignore-external/root.json b/apidom/packages/apidom-reference/test/resolve/strategies/openapi-3-1/reference-object/fixtures/ignore-external/root.json new file mode 100644 index 0000000000..802a6c0b39 --- /dev/null +++ b/apidom/packages/apidom-reference/test/resolve/strategies/openapi-3-1/reference-object/fixtures/ignore-external/root.json @@ -0,0 +1,29 @@ +{ + "openapi": "3.1.0", + "components": { + "parameters": { + "userId": { + "$ref": "#/components/parameters/indirection1", + "description": "override" + }, + "indirection1": { + "$ref": "#/components/parameters/indirection2", + "summary": "indirect summary" + }, + "indirection2": { + "$ref": "#/components/parameters/userIdRef", + "summary": "indirect summary" + }, + "userIdRef": { + "name": "userId", + "in": "query", + "description": "ID of the user", + "required": true + }, + "externalRef": { + "$ref": "./ex.json#/externalParameter", + "description": "another ref" + } + } + } +} diff --git a/apidom/packages/apidom-reference/test/resolve/strategies/openapi-3-1/reference-object/index.ts b/apidom/packages/apidom-reference/test/resolve/strategies/openapi-3-1/reference-object/index.ts index 3a84e1f1d6..89886dad62 100644 --- a/apidom/packages/apidom-reference/test/resolve/strategies/openapi-3-1/reference-object/index.ts +++ b/apidom/packages/apidom-reference/test/resolve/strategies/openapi-3-1/reference-object/index.ts @@ -62,6 +62,20 @@ describe('resolve', function () { }); }); + context('given Reference Objects with external resolution disable', function () { + const fixturePath = path.join(rootFixturePath, 'ignore-external'); + + specify('should resolve', async function () { + const rootFilePath = path.join(fixturePath, 'root.json'); + const refSet = await resolve(rootFilePath, { + parse: { mediaType: 'application/vnd.oai.openapi+json;version=3.1.0' }, + resolve: { external: false }, + }); + + assert.strictEqual(refSet.size, 1); + }); + }); + context('given Reference Objects with direct circular internal reference', function () { const fixturePath = path.join(rootFixturePath, 'direct-internal-circular');