From aa3710be00fef5ecc177c7d9e2ae570a077e8b68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Gorej?= Date: Wed, 22 Nov 2023 17:30:19 +0100 Subject: [PATCH] feat(reference): add OpenAPI 2.0 dereference strategy (#3435) Refs #3102 --- .../src/refractor/predicates.ts | 11 +- packages/apidom-ns-openapi-2/src/index.ts | 4 + .../apidom-ns-openapi-2/src/predicates.ts | 56 +- packages/apidom-reference/README.md | 43 +- packages/apidom-reference/package.json | 5 + .../src/configuration/saturated.ts | 2 + .../dereference/strategies/openapi-2/index.ts | 74 +++ .../strategies/openapi-2/visitor.ts | 479 ++++++++++++++++++ .../asyncapi-2/reference-object/index.ts | 5 +- .../reference-object/dereference-apidom.ts | 256 ++++++++++ .../dereferenced.json | 41 ++ .../additional-ignored-fields/ex.json | 8 + .../additional-ignored-fields/root.json | 40 ++ .../fixtures/direct-external-circular/ex.json | 5 + .../direct-external-circular/root.json | 12 + .../direct-internal-circular/root.json | 12 + .../dereferenced.json | 23 + .../external-circular-dependency/ex.json | 19 + .../external-circular-dependency/root.json | 19 + .../external-indirections/dereferenced.json | 17 + .../fixtures/external-indirections/ex1.json | 5 + .../fixtures/external-indirections/ex2.json | 5 + .../fixtures/external-indirections/ex3.json | 8 + .../fixtures/external-indirections/root.json | 12 + .../dereferenced.json | 17 + .../external-only-absolute-url/ex.json | 8 + .../external-only-absolute-url/root.json | 12 + .../fixtures/external-only/dereferenced.json | 17 + .../fixtures/external-only/ex.json | 8 + .../fixtures/external-only/root.json | 12 + .../ignore-arbitrary-$refs/dereferenced.json | 16 + .../fixtures/ignore-arbitrary-$refs/ex.json | 15 + .../fixtures/ignore-arbitrary-$refs/root.json | 12 + .../ignore-external/dereferenced.json | 39 ++ .../fixtures/ignore-external/ex.json | 8 + .../fixtures/ignore-external/root.json | 27 + .../indirect-external-circular/ex1.json | 5 + .../indirect-external-circular/ex2.json | 5 + .../indirect-external-circular/ex3.json | 5 + .../indirect-external-circular/root.json | 12 + .../indirect-internal-circular/root.json | 21 + .../internal-external/dereferenced.json | 35 ++ .../fixtures/internal-external/ex.json | 8 + .../fixtures/internal-external/root.json | 25 + .../fixtures/internal-only/dereferenced.json | 35 ++ .../fixtures/internal-only/root.json | 24 + .../fixtures/invalid-pointer/root.json | 18 + .../fixtures/max-depth/ex1.json | 5 + .../fixtures/max-depth/ex2.json | 5 + .../fixtures/max-depth/ex3.json | 8 + .../fixtures/max-depth/root.json | 12 + .../path with spaces/dereferenced.json | 17 + .../path-encoding/path with spaces/ex1.json | 5 + .../path-encoding/path with spaces/ex2.json | 5 + .../path-encoding/path with spaces/ex3.json | 8 + .../path-encoding/path with spaces/root.json | 12 + .../refset-as-option/dereferenced.json | 17 + .../fixtures/refset-as-option/ex1.json | 5 + .../fixtures/refset-as-option/ex2.json | 5 + .../fixtures/refset-as-option/ex3.json | 8 + .../fixtures/refset-as-option/root.json | 12 + .../fixtures/unresolvable-reference/root.json | 12 + .../openapi-2/reference-object/index.ts | 346 +++++++++++++ .../reference-object/dereference-apidom.ts | 2 +- .../openapi-3-0/reference-object/index.ts | 5 +- .../openapi-3-1/reference-object/index.ts | 5 +- 66 files changed, 2019 insertions(+), 20 deletions(-) create mode 100644 packages/apidom-reference/src/dereference/strategies/openapi-2/index.ts create mode 100644 packages/apidom-reference/src/dereference/strategies/openapi-2/visitor.ts create mode 100644 packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/dereference-apidom.ts create mode 100644 packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/additional-ignored-fields/dereferenced.json create mode 100644 packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/additional-ignored-fields/ex.json create mode 100644 packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/additional-ignored-fields/root.json create mode 100644 packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/direct-external-circular/ex.json create mode 100644 packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/direct-external-circular/root.json create mode 100644 packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/direct-internal-circular/root.json create mode 100644 packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/external-circular-dependency/dereferenced.json create mode 100644 packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/external-circular-dependency/ex.json create mode 100644 packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/external-circular-dependency/root.json create mode 100644 packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/external-indirections/dereferenced.json create mode 100644 packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/external-indirections/ex1.json create mode 100644 packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/external-indirections/ex2.json create mode 100644 packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/external-indirections/ex3.json create mode 100644 packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/external-indirections/root.json create mode 100644 packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/external-only-absolute-url/dereferenced.json create mode 100644 packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/external-only-absolute-url/ex.json create mode 100644 packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/external-only-absolute-url/root.json create mode 100644 packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/external-only/dereferenced.json create mode 100644 packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/external-only/ex.json create mode 100644 packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/external-only/root.json create mode 100644 packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/ignore-arbitrary-$refs/dereferenced.json create mode 100644 packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/ignore-arbitrary-$refs/ex.json create mode 100644 packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/ignore-arbitrary-$refs/root.json create mode 100644 packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/ignore-external/dereferenced.json create mode 100644 packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/ignore-external/ex.json create mode 100644 packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/ignore-external/root.json create mode 100644 packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/indirect-external-circular/ex1.json create mode 100644 packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/indirect-external-circular/ex2.json create mode 100644 packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/indirect-external-circular/ex3.json create mode 100644 packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/indirect-external-circular/root.json create mode 100644 packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/indirect-internal-circular/root.json create mode 100644 packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/internal-external/dereferenced.json create mode 100644 packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/internal-external/ex.json create mode 100644 packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/internal-external/root.json create mode 100644 packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/internal-only/dereferenced.json create mode 100644 packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/internal-only/root.json create mode 100644 packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/invalid-pointer/root.json create mode 100644 packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/max-depth/ex1.json create mode 100644 packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/max-depth/ex2.json create mode 100644 packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/max-depth/ex3.json create mode 100644 packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/max-depth/root.json create mode 100644 packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/path-encoding/path with spaces/dereferenced.json create mode 100644 packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/path-encoding/path with spaces/ex1.json create mode 100644 packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/path-encoding/path with spaces/ex2.json create mode 100644 packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/path-encoding/path with spaces/ex3.json create mode 100644 packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/path-encoding/path with spaces/root.json create mode 100644 packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/refset-as-option/dereferenced.json create mode 100644 packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/refset-as-option/ex1.json create mode 100644 packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/refset-as-option/ex2.json create mode 100644 packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/refset-as-option/ex3.json create mode 100644 packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/refset-as-option/root.json create mode 100644 packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/unresolvable-reference/root.json create mode 100644 packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/index.ts diff --git a/packages/apidom-ns-json-schema-draft-4/src/refractor/predicates.ts b/packages/apidom-ns-json-schema-draft-4/src/refractor/predicates.ts index 7392a29512..36b02359ba 100644 --- a/packages/apidom-ns-json-schema-draft-4/src/refractor/predicates.ts +++ b/packages/apidom-ns-json-schema-draft-4/src/refractor/predicates.ts @@ -1,7 +1,12 @@ -import { isObjectElement, Element } from '@swagger-api/apidom-core'; +import { isObjectElement, ObjectElement } from '@swagger-api/apidom-core'; + +export interface JSONReferenceLikeElement extends ObjectElement { + hasKey: (value: '$ref') => true; +} // eslint-disable-next-line import/prefer-default-export -export const isJSONReferenceLikeElement = (element: T): boolean => { - // @ts-ignore +export const isJSONReferenceLikeElement = ( + element: unknown, +): element is JSONReferenceLikeElement => { return isObjectElement(element) && element.hasKey('$ref'); }; diff --git a/packages/apidom-ns-openapi-2/src/index.ts b/packages/apidom-ns-openapi-2/src/index.ts index 2cf6a7601d..aad0783a9a 100644 --- a/packages/apidom-ns-openapi-2/src/index.ts +++ b/packages/apidom-ns-openapi-2/src/index.ts @@ -12,6 +12,7 @@ export { } from '@swagger-api/apidom-core'; export { isJSONReferenceElement, + isJSONReferenceLikeElement, JSONReferenceElement, } from '@swagger-api/apidom-ns-json-schema-draft-4'; @@ -33,6 +34,7 @@ export { isLicenseElement, isPathsElement, isPathItemElement, + isPathItemElementExternal, isOperationElement, isExternalDocumentationElement, isParameterElement, @@ -44,7 +46,9 @@ export { isHeaderElement, isTagElement, isReferenceElement, + isReferenceElementExternal, isSchemaElement, + isJSONReferenceElementExternal, isXmlElement, isDefinitionsElement, isParametersDefinitionsElement, diff --git a/packages/apidom-ns-openapi-2/src/predicates.ts b/packages/apidom-ns-openapi-2/src/predicates.ts index f2564227ef..d4b024a63d 100644 --- a/packages/apidom-ns-openapi-2/src/predicates.ts +++ b/packages/apidom-ns-openapi-2/src/predicates.ts @@ -1,4 +1,13 @@ -import { createPredicate } from '@swagger-api/apidom-core'; +import { + createPredicate, + ElementPredicate, + isStringElement, + toValue, +} from '@swagger-api/apidom-core'; +import { + isJSONReferenceElement, + JSONReferenceElement, +} from '@swagger-api/apidom-ns-json-schema-draft-4'; import SwaggerElement from './elements/Swagger'; import SwaggerVersionElement from './elements/SwaggerVersion'; @@ -98,6 +107,21 @@ export const isPathItemElement = createPredicate( }, ); +export const isPathItemElementExternal: ElementPredicate = ( + element: unknown, +): element is PathItemElement => { + if (!isPathItemElement(element)) { + return false; + } + if (!isStringElement(element.$ref)) { + return false; + } + + const value = toValue(element.$ref); + + return typeof value === 'string' && value.length > 0 && !value.startsWith('#'); +}; + export const isOperationElement = createPredicate( ({ hasBasicElementProps, isElementType, primitiveEq }) => { return (element: unknown): element is OperationElement => @@ -208,6 +232,21 @@ export const isReferenceElement = createPredicate( }, ); +export const isReferenceElementExternal: ElementPredicate = ( + element: unknown, +): element is ReferenceElement => { + if (!isReferenceElement(element)) { + return false; + } + if (!isStringElement(element.$ref)) { + return false; + } + + const value = toValue(element.$ref); + + return typeof value === 'string' && value.length > 0 && !value.startsWith('#'); +}; + export const isSchemaElement = createPredicate( ({ hasBasicElementProps, isElementType, primitiveEq }) => { return (element: unknown): element is SchemaElement => @@ -218,6 +257,21 @@ export const isSchemaElement = createPredicate( }, ); +export const isJSONReferenceElementExternal: ElementPredicate = ( + element: unknown, +): element is PathItemElement => { + if (!isJSONReferenceElement(element)) { + return false; + } + if (!isStringElement(element.$ref)) { + return false; + } + + const value = toValue(element.$ref); + + return typeof value === 'string' && value.length > 0 && !value.startsWith('#'); +}; + export const isXmlElement = createPredicate( ({ hasBasicElementProps, isElementType, primitiveEq }) => { return (element: unknown): element is XmlElement => diff --git a/packages/apidom-reference/README.md b/packages/apidom-reference/README.md index 50916a6ab8..a61bc27656 100644 --- a/packages/apidom-reference/README.md +++ b/packages/apidom-reference/README.md @@ -1099,9 +1099,9 @@ It's possible to **change** strategies **order globally** by mutating global `re ```js import { options } from '@swagger-api/apidom-reference'; -import { AsyncApi2ResolveStrategy } from '@swagger-api/apidom-reference/resolve/strategies/asyncapi-2'; -import { OpenApi3_0ResolveStrategy } from '@swagger-api/apidom-reference/resolve/strategies/openapi-3-0'; -import { OpenApi3_1ResolveStrategy } from '@swagger-api/apidom-reference/resolve/strategies/openapi-3-1'; +import AsyncApi2ResolveStrategy from '@swagger-api/apidom-reference/resolve/strategies/asyncapi-2'; +import OpenApi3_0ResolveStrategy from '@swagger-api/apidom-reference/resolve/strategies/openapi-3-0'; +import OpenApi3_1ResolveStrategy from '@swagger-api/apidom-reference/resolve/strategies/openapi-3-1'; options.resolve.strategies = [ OpenApi3_0ResolveStrategy(), @@ -1114,9 +1114,9 @@ To **change** the strategies **order** on ad-hoc basis: ```js import { resolve } from '@swagger-api/apidom-reference'; -import { AsyncApi2ResolveStrategy } from '@swagger-api/apidom-reference/resolve/strategies/asyncapi-2'; -import { OpenApi3_0ResolveStrategy } from '@swagger-api/apidom-reference/resolve/strategies/openapi-3-0'; -import { OpenApi3_1ResolveStrategy } from '@swagger-api/apidom-reference/resolve/strategies/openapi-3-1'; +import AsyncApi2ResolveStrategy from '@swagger-api/apidom-reference/resolve/strategies/asyncapi-2'; +import OpenApi3_0ResolveStrategy from '@swagger-api/apidom-reference/resolve/strategies/openapi-3-0'; +import OpenApi3_1ResolveStrategy from '@swagger-api/apidom-reference/resolve/strategies/openapi-3-1'; await resolve('/home/user/oas.json', { @@ -1361,6 +1361,20 @@ Supported media types: ] ``` +##### [openapi-2](https://github.com/swagger-api/apidom/tree/main/packages/apidom-reference/src/dereference/strategies/openapi-2) + +Dereference strategy for dereferencing [OpenApi 2.0](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md) definitions. + +Supported media types: + +```js +[ + 'application/vnd.oai.openapi;version=2.0', + 'application/vnd.oai.openapi+json;version=2.0', + 'application/vnd.oai.openapi+yaml;version=2.0', +] +``` + ##### [openapi-3-0](https://github.com/swagger-api/apidom/tree/main/packages/apidom-reference/src/dereference/strategies/openapi-3-0) Dereference strategy for dereferencing [OpenApi 3.0.x](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md) definitions. @@ -1410,6 +1424,7 @@ returns `true` or until entire list of strategies is exhausted (throws error). ```js [ + OpenApi2DereferenceStrategy(), OpenApi3_0DereferenceStrategy(), OpenApi3_1DereferenceStrategy(), AsyncApi2DereferenceStrategy(), @@ -1421,11 +1436,13 @@ It's possible to **change** strategies **order globally** by mutating global `de ```js import { options } from '@swagger-api/apidom-reference'; -import { AsyncApi2DereferenceStrategy } from '@swagger-api/apidom-reference/dereference/strategies/asyncapi-2' -import { OpenApi3_0DereferenceStrategy } from '@swagger-api/apidom-reference/dereference/strategies/openapi-3-0' -import { OpenApi3_1DereferenceStrategy } from '@swagger-api/apidom-reference/dereference/strategies/openapi-3-1' +import AsyncApi2DereferenceStrategy from '@swagger-api/apidom-reference/dereference/strategies/asyncapi-2' +import OpenApi2DereferenceStrategy from '@swagger-api/apidom-reference/dereference/strategies/openapi-2' +import OpenApi3_0DereferenceStrategy from '@swagger-api/apidom-reference/dereference/strategies/openapi-3-0' +import OpenApi3_1DereferenceStrategy from '@swagger-api/apidom-reference/dereference/strategies/openapi-3-1' options.dereference.strategies = [ + OpenApi2DereferenceStrategy(), OpenApi3_0DereferenceStrategy(), OpenApi3_1DereferenceStrategy(), AsyncApi2DereferenceStrategy(), @@ -1436,9 +1453,10 @@ To **change** the strategies **order** on ad-hoc basis: ```js import { dereference } from '@swagger-api/apidom-reference'; -import { AsyncApi2DereferenceStrategy } from '@swagger-api/apidom-reference/dereference/strategies/asyncapi-2' -import { OpenApi3_0DereferenceStrategy } from '@swagger-api/apidom-reference/dereference/strategies/openapi-3-0' -import { OpenApi3_1DereferenceStrategy } from '@swagger-api/apidom-reference/dereference/strategies/openapi-3-1' +import AsyncApi2DereferenceStrategy from '@swagger-api/apidom-reference/dereference/strategies/asyncapi-2' +import OpenApi2DereferenceStrategy from '@swagger-api/apidom-reference/dereference/strategies/openapi-2' +import OpenApi3_0DereferenceStrategy from '@swagger-api/apidom-reference/dereference/strategies/openapi-3-0' +import OpenApi3_1DereferenceStrategy from '@swagger-api/apidom-reference/dereference/strategies/openapi-3-1' await dereference('/home/user/oas.json', { parse: { @@ -1447,6 +1465,7 @@ await dereference('/home/user/oas.json', { dereference: { strategies: [ AsyncApi2DereferenceStrategy(), + OpenApi2DereferenceStrategy(), OpenApi3_0DereferenceStrategy(), OpenApi3_1DereferenceStrategy(), ] diff --git a/packages/apidom-reference/package.json b/packages/apidom-reference/package.json index 7c0ebebfe9..cf50326562 100644 --- a/packages/apidom-reference/package.json +++ b/packages/apidom-reference/package.json @@ -145,6 +145,11 @@ "require": "./cjs/dereference/strategies/asyncapi-2/index.cjs", "types": "./types/dereference/strategies/asyncapi-2/index.d.ts" }, + "./dereference/strategies/openapi-2": { + "import": "./es/dereference/strategies/openapi-2/index.mjs", + "require": "./cjs/dereference/strategies/openapi-2/index.cjs", + "types": "./types/dereference/strategies/openapi-2/index.d.ts" + }, "./dereference/strategies/openapi-3-0": { "import": "./es/dereference/strategies/openapi-3-0/index.mjs", "require": "./cjs/dereference/strategies/openapi-3-0/index.cjs", diff --git a/packages/apidom-reference/src/configuration/saturated.ts b/packages/apidom-reference/src/configuration/saturated.ts index c5f934bd42..076a5c1643 100644 --- a/packages/apidom-reference/src/configuration/saturated.ts +++ b/packages/apidom-reference/src/configuration/saturated.ts @@ -16,6 +16,7 @@ import AsyncApiYaml2Parser from '../parse/parsers/asyncapi-yaml-2'; import JsonParser from '../parse/parsers/json'; import YamlParser from '../parse/parsers/yaml-1-2'; import BinaryParser from '../parse/parsers/binary/index-node'; +import OpenApi2DereferenceStrategy from '../dereference/strategies/openapi-2'; import OpenApi3_0DereferenceStrategy from '../dereference/strategies/openapi-3-0'; import OpenApi3_1DereferenceStrategy from '../dereference/strategies/openapi-3-1'; import AsyncApi2DereferenceStrategy from '../dereference/strategies/asyncapi-2'; @@ -49,6 +50,7 @@ options.resolve.strategies = [ ]; options.dereference.strategies = [ + OpenApi2DereferenceStrategy(), OpenApi3_0DereferenceStrategy(), OpenApi3_1DereferenceStrategy(), AsyncApi2DereferenceStrategy(), diff --git a/packages/apidom-reference/src/dereference/strategies/openapi-2/index.ts b/packages/apidom-reference/src/dereference/strategies/openapi-2/index.ts new file mode 100644 index 0000000000..e98102c2bd --- /dev/null +++ b/packages/apidom-reference/src/dereference/strategies/openapi-2/index.ts @@ -0,0 +1,74 @@ +import stampit from 'stampit'; +import { defaultTo, propEq } from 'ramda'; +import { createNamespace, visit, Element } from '@swagger-api/apidom-core'; +import openApi2Namespace, { + getNodeType, + isSwaggerElement, + keyMap, + mediaTypes, +} from '@swagger-api/apidom-ns-openapi-2'; + +import DereferenceStrategy from '../DereferenceStrategy'; +import { + DereferenceStrategy as IDereferenceStrategy, + File as IFile, + ReferenceOptions as IReferenceOptions, +} from '../../../types'; +import Reference from '../../../Reference'; +import ReferenceSet from '../../../ReferenceSet'; +import OpenApi2DereferenceVisitor from './visitor'; + +// @ts-ignore +const visitAsync = visit[Symbol.for('nodejs.util.promisify.custom')]; + +const OpenApi2DereferenceStrategy: stampit.Stamp = stampit( + DereferenceStrategy, + { + init() { + this.name = 'openapi-2'; + }, + methods: { + canDereference(file: IFile): boolean { + // assert by media type + if (file.mediaType !== 'text/plain') { + return mediaTypes.includes(file.mediaType); + } + + // assert by inspecting ApiDOM + return isSwaggerElement(file.parseResult?.api); + }, + + async dereference(file: IFile, options: IReferenceOptions): Promise { + const namespace = createNamespace(openApi2Namespace); + const refSet = defaultTo(ReferenceSet(), options.dereference.refSet); + let reference; + + if (!refSet.has(file.uri)) { + reference = Reference({ uri: file.uri, value: file.parseResult }); + refSet.add(reference); + } else { + // pre-computed refSet was provided as configuration option + reference = refSet.find(propEq(file.uri, 'uri')); + } + + const visitor = OpenApi2DereferenceVisitor({ reference, namespace, options }); + const dereferencedElement = await visitAsync(refSet.rootRef.value, visitor, { + keyMap, + nodeTypeGetter: getNodeType, + }); + + /** + * Release all memory if this refSet was not provided as an configuration option. + * If provided as configuration option, then provider is responsible for cleanup. + */ + if (options.dereference.refSet === null) { + refSet.clean(); + } + + return dereferencedElement; + }, + }, + }, +); + +export default OpenApi2DereferenceStrategy; diff --git a/packages/apidom-reference/src/dereference/strategies/openapi-2/visitor.ts b/packages/apidom-reference/src/dereference/strategies/openapi-2/visitor.ts new file mode 100644 index 0000000000..5e4109b686 --- /dev/null +++ b/packages/apidom-reference/src/dereference/strategies/openapi-2/visitor.ts @@ -0,0 +1,479 @@ +import stampit from 'stampit'; +import { propEq } from 'ramda'; +import { + isPrimitiveElement, + isStringElement, + isMemberElement, + isElement, + IdentityManager, + visit, + cloneShallow, + cloneDeep, + toValue, + Element, +} from '@swagger-api/apidom-core'; +import { ApiDOMError } from '@swagger-api/apidom-error'; +import { evaluate, uriToPointer } from '@swagger-api/apidom-json-pointer'; +import { + getNodeType, + isReferenceLikeElement, + isJSONReferenceLikeElement, + keyMap, + ReferenceElement, + PathItemElement, + JSONReferenceElement, + isReferenceElementExternal, + isPathItemElementExternal, + isJSONReferenceElementExternal, +} from '@swagger-api/apidom-ns-openapi-2'; + +import { Reference as IReference } from '../../../types'; +import MaximumDereferenceDepthError from '../../../errors/MaximumDereferenceDepthError'; +import MaximumResolverDepthError from '../../../errors/MaximumResolverDepthError'; +import { AncestorLineage } from '../../util'; +import * as url from '../../../util/url'; +import parse from '../../../parse'; +import Reference from '../../../Reference'; + +// @ts-ignore +const visitAsync = visit[Symbol.for('nodejs.util.promisify.custom')]; + +// initialize element identity manager +const identityManager = IdentityManager(); + +/** + * Predicate for detecting if element was created by merging referencing + * element with particular element identity with a referenced element. + */ +const wasReferencedBy = + (referencingElement: T) => + (element: U) => + element.meta.hasKey('ref-referencing-element-id') && + element.meta + .get('ref-referencing-element-id') + .equals(toValue(identityManager.identify(referencingElement))); + +const OpenApi2DereferenceVisitor = stampit({ + props: { + indirections: [], + namespace: null, + reference: null, + options: null, + ancestors: null, + }, + init({ indirections = [], reference, namespace, options, ancestors = new AncestorLineage() }) { + this.indirections = indirections; + this.namespace = namespace; + this.reference = reference; + this.options = options; + this.ancestors = new AncestorLineage(...ancestors); + }, + methods: { + async toReference(uri: string): Promise { + // detect maximum depth of resolution + if (this.reference.depth >= this.options.resolve.maxDepth) { + throw new MaximumResolverDepthError( + `Maximum resolution depth of ${this.options.resolve.maxDepth} has been exceeded by file "${this.reference.uri}"`, + ); + } + + const baseURI = url.resolve(this.reference.uri, url.sanitize(url.stripHash(uri))); + + const { refSet } = this.reference; + + // we've already processed this Reference in past + if (refSet.has(baseURI)) { + return refSet.find(propEq(baseURI, 'uri')); + } + + const parseResult = await parse(url.unsanitize(baseURI), { + ...this.options, + parse: { ...this.options.parse, mediaType: 'text/plain' }, + }); + + // register new Reference with ReferenceSet + const reference = Reference({ + uri: baseURI, + value: parseResult, + depth: this.reference.depth + 1, + }); + + refSet.add(reference); + + return reference; + }, + + toAncestorLineage(ancestors) { + /** + * Compute full ancestors lineage. + * Ancestors are flatten to unwrap all Element instances. + */ + const directAncestors = new Set(ancestors.filter(isElement)); + const ancestorsLineage = new AncestorLineage(...this.ancestors, directAncestors); + + return [ancestorsLineage, directAncestors]; + }, + + async ReferenceElement( + referencingElement: ReferenceElement, + key: any, + parent: any, + path: any, + ancestors: any[], + ) { + const [ancestorsLineage, directAncestors] = this.toAncestorLineage([...ancestors, parent]); + + // detect possible cycle in traversal and avoid it + if (ancestorsLineage.includesCycle(referencingElement)) { + return false; + } + + // ignore resolving external Reference Objects + if (!this.options.resolve.external && isReferenceElementExternal(referencingElement)) { + // skip traversing this schema but traverse all it's child schemas + return undefined; + } + + const reference = await this.toReference(toValue(referencingElement.$ref)); + const { uri: retrievalURI } = reference; + const $refBaseURI = url.resolve(retrievalURI, toValue(referencingElement.$ref)); + + this.indirections.push(referencingElement); + + const jsonPointer = uriToPointer($refBaseURI); + + // possibly non-semantic fragment + let referencedElement = evaluate(jsonPointer, reference.value.result); + + // applying semantics to a fragment + if (isPrimitiveElement(referencedElement)) { + const referencedElementType = toValue(referencingElement.meta.get('referenced-element')); + + if (isReferenceLikeElement(referencedElement)) { + // handling indirect references + referencedElement = ReferenceElement.refract(referencedElement); + referencedElement.setMetaProperty('referenced-element', referencedElementType); + } else { + // handling direct references + const ElementClass = this.namespace.getElementClass(referencedElementType); + referencedElement = ElementClass.refract(referencedElement); + } + } + + // detect direct or circular reference + if (this.indirections.includes(referencedElement)) { + throw new ApiDOMError('Recursive Reference Object detected'); + } + + // detect maximum depth of dereferencing + if (this.indirections.length > this.options.dereference.maxDepth) { + throw new MaximumDereferenceDepthError( + `Maximum dereference depth of "${this.options.dereference.maxDepth}" has been exceeded in file "${this.reference.uri}"`, + ); + } + + // append referencing reference to ancestors lineage + directAncestors.add(referencingElement); + + // dive deep into the fragment + const visitor = OpenApi2DereferenceVisitor({ + reference, + namespace: this.namespace, + indirections: [...this.indirections], + options: this.options, + ancestors: ancestorsLineage, + }); + referencedElement = await visitAsync(referencedElement, visitor, { + keyMap, + nodeTypeGetter: getNodeType, + }); + + // remove referencing reference from ancestors lineage + directAncestors.delete(referencingElement); + + this.indirections.pop(); + + const mergeAndAnnotateReferencedElement = (refedElement: T): T => { + const copy = cloneShallow(refedElement); + + // annotate referenced element with info about original referencing element + copy.setMetaProperty('ref-fields', { + // @ts-ignore + $ref: toValue(referencingElement.$ref), + }); + // annotate fragment with info about origin + copy.setMetaProperty('ref-origin', reference.uri); + // annotate fragment with info about referencing element + copy.setMetaProperty( + 'ref-referencing-element-id', + cloneDeep(identityManager.identify(referencingElement)), + ); + + return copy; + }; + + // attempting to create cycle + if ( + ancestorsLineage.includes(referencingElement) || + ancestorsLineage.includes(referencedElement) + ) { + const replaceWith = + ancestorsLineage.findItem(wasReferencedBy(referencingElement)) ?? + mergeAndAnnotateReferencedElement(referencedElement); + if (isMemberElement(parent)) { + parent.value = replaceWith; // eslint-disable-line no-param-reassign + } else if (Array.isArray(parent)) { + parent[key] = replaceWith; // eslint-disable-line no-param-reassign + } + return false; + } + + // transclude referencing element with merged referenced element + return mergeAndAnnotateReferencedElement(referencedElement); + }, + + async PathItemElement( + referencingElement: PathItemElement, + key: any, + parent: any, + path: any, + ancestors: any[], + ) { + const [ancestorsLineage, directAncestors] = this.toAncestorLineage([...ancestors, parent]); + + // ignore PathItemElement without $ref field + if (!isStringElement(referencingElement.$ref)) { + return undefined; + } + + // detect possible cycle in traversal and avoid it + if (ancestorsLineage.includesCycle(referencingElement)) { + return false; + } + + // ignore resolving external Path Item Elements + if (!this.options.resolve.external && isPathItemElementExternal(referencingElement)) { + return undefined; + } + + const reference = await this.toReference(toValue(referencingElement.$ref)); + const retrievalURI = reference.uri; + const $refBaseURI = url.resolve(retrievalURI, toValue(referencingElement.$ref)); + + this.indirections.push(referencingElement); + + const jsonPointer = uriToPointer($refBaseURI); + + // possibly non-semantic referenced element + let referencedElement = evaluate(jsonPointer, reference.value.result); + + // applying semantics to a referenced element + if (isPrimitiveElement(referencedElement)) { + referencedElement = PathItemElement.refract(referencedElement); + } + + // detect direct or indirect reference + if (this.indirections.includes(referencedElement)) { + throw new ApiDOMError('Recursive Path Item Object reference detected'); + } + + // detect maximum depth of dereferencing + if (this.indirections.length > this.options.dereference.maxDepth) { + throw new MaximumDereferenceDepthError( + `Maximum dereference depth of "${this.options.dereference.maxDepth}" has been exceeded in file "${this.reference.uri}"`, + ); + } + + // append referencing path item to ancestors lineage + directAncestors.add(referencingElement); + + // dive deep into the referenced element + const visitor: any = OpenApi2DereferenceVisitor({ + reference, + namespace: this.namespace, + indirections: [...this.indirections], + options: this.options, + ancestors: ancestorsLineage, + }); + referencedElement = await visitAsync(referencedElement, visitor, { + keyMap, + nodeTypeGetter: getNodeType, + }); + + // remove referencing path item from ancestors lineage + directAncestors.delete(referencingElement); + + this.indirections.pop(); + + const mergeAndAnnotateReferencedElement = ( + refedElement: T, + ): PathItemElement => { + // merge fields from referenced Path Item with referencing one + const mergedElement = new PathItemElement( + [...refedElement.content] as any, + cloneDeep(referencedElement.meta), + cloneDeep(referencedElement.attributes), + ); + // existing keywords from referencing PathItemElement overrides ones from referenced element + referencingElement.forEach((value: Element, keyElement: Element, item: Element) => { + mergedElement.remove(toValue(keyElement)); + mergedElement.content.push(item); + }); + mergedElement.remove('$ref'); + + // annotate referenced element with info about original referencing element + mergedElement.setMetaProperty('ref-fields', { + $ref: toValue(referencingElement.$ref), + }); + // annotate referenced element with info about origin + mergedElement.setMetaProperty('ref-origin', reference.uri); + // annotate fragment with info about referencing element + mergedElement.setMetaProperty( + 'ref-referencing-element-id', + cloneDeep(identityManager.identify(referencingElement)), + ); + + return mergedElement; + }; + + // attempting to create cycle + if ( + ancestorsLineage.includes(referencingElement) || + ancestorsLineage.includes(referencedElement) + ) { + const replaceWith = + ancestorsLineage.findItem(wasReferencedBy(referencingElement)) ?? + mergeAndAnnotateReferencedElement(referencedElement); + if (isMemberElement(parent)) { + parent.value = replaceWith; // eslint-disable-line no-param-reassign + } else if (Array.isArray(parent)) { + parent[key] = replaceWith; // eslint-disable-line no-param-reassign + } + return false; + } + + // transclude referencing element with merged referenced element + return mergeAndAnnotateReferencedElement(referencedElement); + }, + + async JSONReferenceElement( + referencingElement: JSONReferenceElement, + key: any, + parent: any, + path: any, + ancestors: any[], + ) { + const [ancestorsLineage, directAncestors] = this.toAncestorLineage([...ancestors, parent]); + + // detect possible cycle in traversal and avoid it + if (ancestorsLineage.includesCycle(referencingElement)) { + return false; + } + + // ignore resolving external Reference Objects + if (!this.options.resolve.external && isJSONReferenceElementExternal(referencingElement)) { + // skip traversing this schema but traverse all it's child schemas + return undefined; + } + + const reference = await this.toReference(toValue(referencingElement.$ref)); + const { uri: retrievalURI } = reference; + const $refBaseURI = url.resolve(retrievalURI, toValue(referencingElement.$ref)); + + this.indirections.push(referencingElement); + + const jsonPointer = uriToPointer($refBaseURI); + + // possibly non-semantic fragment + let referencedElement = evaluate(jsonPointer, reference.value.result); + + // applying semantics to a fragment + if (isPrimitiveElement(referencedElement)) { + const referencedElementType = toValue(referencingElement.meta.get('referenced-element')); + + if (isJSONReferenceLikeElement(referencedElement)) { + // handling indirect references + referencedElement = ReferenceElement.refract(referencedElement); + referencedElement.setMetaProperty('referenced-element', referencedElementType); + } else { + // handling direct references + const ElementClass = this.namespace.getElementClass(referencedElementType); + referencedElement = ElementClass.refract(referencedElement); + } + } + + // detect direct or circular reference + if (this.indirections.includes(referencedElement)) { + throw new ApiDOMError('Recursive Reference Object detected'); + } + + // detect maximum depth of dereferencing + if (this.indirections.length > this.options.dereference.maxDepth) { + throw new MaximumDereferenceDepthError( + `Maximum dereference depth of "${this.options.dereference.maxDepth}" has been exceeded in file "${this.reference.uri}"`, + ); + } + + // append referencing reference to ancestors lineage + directAncestors.add(referencingElement); + + // dive deep into the fragment + const visitor = OpenApi2DereferenceVisitor({ + reference, + namespace: this.namespace, + indirections: [...this.indirections], + options: this.options, + ancestors: ancestorsLineage, + }); + referencedElement = await visitAsync(referencedElement, visitor, { + keyMap, + nodeTypeGetter: getNodeType, + }); + + // remove referencing reference from ancestors lineage + directAncestors.delete(referencingElement); + + this.indirections.pop(); + + const mergeAndAnnotateReferencedElement = (refedElement: T): T => { + const copy = cloneShallow(refedElement); + + // annotate referenced element with info about original referencing element + copy.setMetaProperty('ref-fields', { + // @ts-ignore + $ref: toValue(referencingElement.$ref), + }); + // annotate fragment with info about origin + copy.setMetaProperty('ref-origin', reference.uri); + // annotate fragment with info about referencing element + copy.setMetaProperty( + 'ref-referencing-element-id', + cloneDeep(identityManager.identify(referencingElement)), + ); + + return copy; + }; + + // attempting to create cycle + if ( + ancestorsLineage.includes(referencingElement) || + ancestorsLineage.includes(referencedElement) + ) { + const replaceWith = + ancestorsLineage.findItem(wasReferencedBy(referencingElement)) ?? + mergeAndAnnotateReferencedElement(referencedElement); + if (isMemberElement(parent)) { + parent.value = replaceWith; // eslint-disable-line no-param-reassign + } else if (Array.isArray(parent)) { + parent[key] = replaceWith; // eslint-disable-line no-param-reassign + } + return false; + } + + // transclude referencing element with merged referenced element + return mergeAndAnnotateReferencedElement(referencedElement); + }, + }, +}); + +export default OpenApi2DereferenceVisitor; diff --git a/packages/apidom-reference/test/dereference/strategies/asyncapi-2/reference-object/index.ts b/packages/apidom-reference/test/dereference/strategies/asyncapi-2/reference-object/index.ts index bcc8f752d9..2a6464b5e8 100644 --- a/packages/apidom-reference/test/dereference/strategies/asyncapi-2/reference-object/index.ts +++ b/packages/apidom-reference/test/dereference/strategies/asyncapi-2/reference-object/index.ts @@ -352,7 +352,10 @@ describe('dereference', function () { const refSet = await resolve(uri, { parse: { mediaType: mediaTypes.latest('json') }, }); - const actual = await dereference(uri, { dereference: { refSet } }); + const actual = await dereference(uri, { + dereference: { refSet }, + resolve: { resolvers: [] }, + }); const expected = loadJsonFile(path.join(fixturePath, 'dereferenced.json')); assert.deepEqual(toValue(actual), expected); diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/dereference-apidom.ts b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/dereference-apidom.ts new file mode 100644 index 0000000000..f0419c2370 --- /dev/null +++ b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/dereference-apidom.ts @@ -0,0 +1,256 @@ +import path from 'node:path'; +import { assert } from 'chai'; +import { mediaTypes, isParameterElement, SwaggerElement } from '@swagger-api/apidom-ns-openapi-2'; +import { toValue } from '@swagger-api/apidom-core'; +import { evaluate } from '@swagger-api/apidom-json-pointer'; + +import { parse, dereferenceApiDOM } from '../../../../../src'; +import { ServerTerminable, createHTTPServer } from '../../../../helpers'; + +describe('dereference', function () { + context('strategies', function () { + context('openapi-2', function () { + context('Reference Object', function () { + context( + 'given single ReferenceElement passed to dereferenceApiDOM with internal references', + function () { + context('given dereferencing using local file system', function () { + const fixturePath = path.join(__dirname, 'fixtures', 'internal-only', 'root.json'); + + specify('should dereference', async function () { + const parseResult = await parse(fixturePath, { + parse: { mediaType: mediaTypes.latest('json') }, + }); + const referenceElement = evaluate( + '/paths/~1/parameters/0', + parseResult.api as SwaggerElement, + ); + const dereferenced = await dereferenceApiDOM(referenceElement, { + parse: { mediaType: mediaTypes.latest('json') }, + resolve: { + baseURI: `${fixturePath}#/paths/~1/parameters/0`, + }, + }); + + assert.isTrue(isParameterElement(dereferenced)); + }); + + specify('should dereference and contain metadata about origin', async function () { + const parseResult = await parse(fixturePath, { + parse: { mediaType: mediaTypes.latest('json') }, + }); + const referenceElement = evaluate( + '/paths/~1/parameters/0', + parseResult.api as SwaggerElement, + ); + const dereferenced = await dereferenceApiDOM(referenceElement, { + parse: { mediaType: mediaTypes.latest('json') }, + resolve: { baseURI: `${fixturePath}#/paths/~1/parameters/0` }, + }); + + assert.match( + toValue(dereferenced.meta.get('ref-origin')), + /internal-only\/root\.json$/, + ); + }); + }); + + context('given dereferencing using HTTP protocol', function () { + const fixturePath = path.join(__dirname, 'fixtures', 'internal-only', 'root.json'); + const httpPort = 8123; + let httpServer: ServerTerminable; + + beforeEach(function () { + const cwd = path.join(__dirname, 'fixtures', 'internal-only'); + httpServer = createHTTPServer({ port: httpPort, cwd }); + }); + + afterEach(async function () { + await httpServer.terminate(); + }); + + specify('should dereference', async function () { + const parseResult = await parse(fixturePath, { + parse: { mediaType: mediaTypes.latest('json') }, + }); + const referenceElement = evaluate( + '/paths/~1/parameters/0', + parseResult.api as SwaggerElement, + ); + const dereferenced = await dereferenceApiDOM(referenceElement, { + parse: { mediaType: mediaTypes.latest('json') }, + resolve: { + baseURI: `http://localhost:${httpPort}/root.json#/paths/~1/parameters/0`, + }, + }); + + assert.isTrue(isParameterElement(dereferenced)); + }); + + specify('should dereference and contain metadata about origin', async function () { + const parseResult = await parse(fixturePath, { + parse: { mediaType: mediaTypes.latest('json') }, + }); + const referenceElement = evaluate( + '/paths/~1/parameters/0', + parseResult.api as SwaggerElement, + ); + const dereferenced = await dereferenceApiDOM(referenceElement, { + parse: { mediaType: mediaTypes.latest('json') }, + resolve: { + baseURI: `http://localhost:${httpPort}/root.json#/paths/~1/parameters/0`, + }, + }); + + assert.match(toValue(dereferenced.meta.get('ref-origin')), /\/root\.json$/); + }); + }); + }, + ); + + context( + 'given single ReferenceElement passed to dereferenceApiDOM with external references', + function () { + context('given dereferencing using local file system', function () { + const fixturePath = path.join(__dirname, 'fixtures', 'external-only', 'root.json'); + + specify('should dereference', async function () { + const parseResult = await parse(fixturePath, { + parse: { mediaType: mediaTypes.latest('json') }, + }); + const referenceElement = evaluate( + '/paths/~1/parameters/0', + parseResult.api as SwaggerElement, + ); + const dereferenced = await dereferenceApiDOM(referenceElement, { + parse: { mediaType: mediaTypes.latest('json') }, + resolve: { baseURI: fixturePath }, + }); + + assert.isTrue(isParameterElement(dereferenced)); + }); + + specify('should dereference and contain metadata about origin', async function () { + const parseResult = await parse(fixturePath, { + parse: { mediaType: mediaTypes.latest('json') }, + }); + const referenceElement = evaluate( + '/paths/~1/parameters/0', + parseResult.api as SwaggerElement, + ); + const dereferenced = await dereferenceApiDOM(referenceElement, { + parse: { mediaType: mediaTypes.latest('json') }, + resolve: { baseURI: fixturePath }, + }); + + assert.match( + toValue(dereferenced.meta.get('ref-origin')), + /external-only\/ex\.json$/, + ); + }); + }); + + context('given dereferencing using HTTP protocol', function () { + const fixturePath = path.join(__dirname, 'fixtures', 'external-only', 'root.json'); + const httpPort = 8123; + let httpServer: ServerTerminable; + + beforeEach(function () { + const cwd = path.join(__dirname, 'fixtures', 'external-only'); + httpServer = createHTTPServer({ port: httpPort, cwd }); + }); + + afterEach(async function () { + await httpServer.terminate(); + }); + + specify('should dereference', async function () { + const parseResult = await parse(fixturePath, { + parse: { mediaType: mediaTypes.latest('json') }, + }); + const referenceElement = evaluate( + '/paths/~1/parameters/0', + parseResult.api as SwaggerElement, + ); + const dereferenced = await dereferenceApiDOM(referenceElement, { + parse: { mediaType: mediaTypes.latest('json') }, + resolve: { baseURI: `http://localhost:${httpPort}/root.json` }, + }); + + assert.isTrue(isParameterElement(dereferenced)); + }); + + specify('should dereference and contain metadata about origin', async function () { + const parseResult = await parse(fixturePath, { + parse: { mediaType: mediaTypes.latest('json') }, + }); + const referenceElement = evaluate( + '/paths/~1/parameters/0', + parseResult.api as SwaggerElement, + ); + const dereferenced = await dereferenceApiDOM(referenceElement, { + parse: { mediaType: mediaTypes.latest('json') }, + resolve: { baseURI: `http://localhost:${httpPort}/root.json` }, + }); + + assert.match(toValue(dereferenced.meta.get('ref-origin')), /\/ex\.json$/); + }); + }); + + context('given dereferencing using HTTP protocol and absolute URLs', function () { + const fixturePath = path.join( + __dirname, + 'fixtures', + 'external-only-absolute-url', + 'root.json', + ); + const httpPort = 8123; + let httpServer: ServerTerminable; + + beforeEach(function () { + const cwd = path.join(__dirname, 'fixtures', 'external-only-absolute-url'); + httpServer = createHTTPServer({ port: httpPort, cwd }); + }); + + afterEach(async function () { + await httpServer.terminate(); + }); + + specify('should dereference', async function () { + const parseResult = await parse(fixturePath, { + parse: { mediaType: mediaTypes.latest('json') }, + }); + const referenceElement = evaluate( + '/paths/~1/parameters/0', + parseResult.api as SwaggerElement, + ); + const dereferenced = await dereferenceApiDOM(referenceElement, { + parse: { mediaType: mediaTypes.latest('json') }, + resolve: { baseURI: `http://localhost:${httpPort}/root.json` }, + }); + + assert.isTrue(isParameterElement(dereferenced)); + }); + + specify('should dereference and contain metadata about origin', async function () { + const parseResult = await parse(fixturePath, { + parse: { mediaType: mediaTypes.latest('json') }, + }); + const referenceElement = evaluate( + '/paths/~1/parameters/0', + parseResult.api as SwaggerElement, + ); + const dereferenced = await dereferenceApiDOM(referenceElement, { + parse: { mediaType: mediaTypes.latest('json') }, + resolve: { baseURI: `http://localhost:${httpPort}/root.json` }, + }); + + assert.match(toValue(dereferenced.meta.get('ref-origin')), /\/ex\.json$/); + }); + }); + }, + ); + }); + }); + }); +}); diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/additional-ignored-fields/dereferenced.json b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/additional-ignored-fields/dereferenced.json new file mode 100644 index 0000000000..8c98259617 --- /dev/null +++ b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/additional-ignored-fields/dereferenced.json @@ -0,0 +1,41 @@ +[ + { + "swagger": "2.0", + "paths": { + "/": { + "parameters": [ + { + "name": "userId", + "in": "query", + "description": "ID of the user", + "required": true + }, + { + "name": "userId", + "in": "query", + "description": "ID of the user", + "required": true + }, + { + "name": "userId", + "in": "query", + "description": "ID of the user", + "required": true + }, + { + "name": "userId", + "in": "query", + "description": "ID of the user", + "required": true + }, + { + "name": "externalParameter", + "in": "query", + "description": "this is parameter stored in external file", + "required": true + } + ] + } + } + } +] diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/additional-ignored-fields/ex.json b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/additional-ignored-fields/ex.json new file mode 100644 index 0000000000..fc8a07edcd --- /dev/null +++ b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/additional-ignored-fields/ex.json @@ -0,0 +1,8 @@ +{ + "externalParameter": { + "name": "externalParameter", + "in": "query", + "description": "this is parameter stored in external file", + "required": true + } +} diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/additional-ignored-fields/root.json b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/additional-ignored-fields/root.json new file mode 100644 index 0000000000..a786dab8d4 --- /dev/null +++ b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/additional-ignored-fields/root.json @@ -0,0 +1,40 @@ +{ + "swagger": "2.0", + "paths": { + "/": { + "parameters": [ + { + "$ref": "#/paths/~1/parameters/1", + "description": "description 1", + "prop1": "value1", + "prop2": "value2" + }, + { + "$ref": "#/paths/~1/parameters/2", + "summary": "indirect summary 1", + "prop1": "value1", + "prop2": "value2" + }, + { + "$ref": "#/paths/~1/parameters/3", + "description": "indirect description 1", + "summary": "indirect summary 2", + "prop1": "value1", + "prop2": "value2" + }, + { + "name": "userId", + "in": "query", + "description": "ID of the user", + "required": true + }, + { + "$ref": "./ex.json#/externalParameter", + "description": "pulled from external source", + "prop1": "value1", + "prop2": "value2" + } + ] + } + } +} diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/direct-external-circular/ex.json b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/direct-external-circular/ex.json new file mode 100644 index 0000000000..3a2f0069fb --- /dev/null +++ b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/direct-external-circular/ex.json @@ -0,0 +1,5 @@ +{ + "externalParameter": { + "$ref": "./root.json#/paths/~1/parameters/0" + } +} diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/direct-external-circular/root.json b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/direct-external-circular/root.json new file mode 100644 index 0000000000..ea215061c9 --- /dev/null +++ b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/direct-external-circular/root.json @@ -0,0 +1,12 @@ +{ + "swagger": "2.0", + "paths": { + "/": { + "parameters": [ + { + "$ref": "./ex.json#/externalParameter" + } + ] + } + } +} diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/direct-internal-circular/root.json b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/direct-internal-circular/root.json new file mode 100644 index 0000000000..080d080d33 --- /dev/null +++ b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/direct-internal-circular/root.json @@ -0,0 +1,12 @@ +{ + "swagger": "2.0", + "paths": { + "/": { + "parameters": [ + { + "$ref": "#/paths/~1/parameters/0" + } + ] + } + } +} diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/external-circular-dependency/dereferenced.json b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/external-circular-dependency/dereferenced.json new file mode 100644 index 0000000000..88a306d6ea --- /dev/null +++ b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/external-circular-dependency/dereferenced.json @@ -0,0 +1,23 @@ +[ + { + "swagger": "2.0", + "paths": { + "/": { + "parameters": [ + { + "name": "param2", + "in": "query" + }, + { + "name": "param2", + "in": "query" + }, + { + "name": "ex-param3", + "in": "query" + } + ] + } + } + } +] diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/external-circular-dependency/ex.json b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/external-circular-dependency/ex.json new file mode 100644 index 0000000000..e817e99f41 --- /dev/null +++ b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/external-circular-dependency/ex.json @@ -0,0 +1,19 @@ +{ + "swagger": "2.0", + "paths": { + "/": { + "parameters": [ + { + "$ref": "./root.json#/paths/~1/parameters/1" + }, + { + "$ref": "./root.json#/paths/~1/parameters/1" + }, + { + "name": "ex-param3", + "in": "query" + } + ] + } + } +} diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/external-circular-dependency/root.json b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/external-circular-dependency/root.json new file mode 100644 index 0000000000..033e2097ff --- /dev/null +++ b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/external-circular-dependency/root.json @@ -0,0 +1,19 @@ +{ + "swagger": "2.0", + "paths": { + "/": { + "parameters": [ + { + "$ref": "./ex.json#/paths/~1/parameters/0" + }, + { + "name": "param2", + "in": "query" + }, + { + "$ref": "./ex.json#/paths/~1/parameters/2" + } + ] + } + } +} diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/external-indirections/dereferenced.json b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/external-indirections/dereferenced.json new file mode 100644 index 0000000000..3f23a8c2e9 --- /dev/null +++ b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/external-indirections/dereferenced.json @@ -0,0 +1,17 @@ +[ + { + "swagger": "2.0", + "paths": { + "/": { + "parameters": [ + { + "name": "externalParameter", + "in": "query", + "description": "this is parameter stored in external file", + "required": true + } + ] + } + } + } +] diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/external-indirections/ex1.json b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/external-indirections/ex1.json new file mode 100644 index 0000000000..f46bebc5f4 --- /dev/null +++ b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/external-indirections/ex1.json @@ -0,0 +1,5 @@ +{ + "indirection": { + "$ref": "./ex2.json#/indirection" + } +} diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/external-indirections/ex2.json b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/external-indirections/ex2.json new file mode 100644 index 0000000000..091689f702 --- /dev/null +++ b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/external-indirections/ex2.json @@ -0,0 +1,5 @@ +{ + "indirection": { + "$ref": "./ex3.json#/externalParameter" + } +} diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/external-indirections/ex3.json b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/external-indirections/ex3.json new file mode 100644 index 0000000000..fc8a07edcd --- /dev/null +++ b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/external-indirections/ex3.json @@ -0,0 +1,8 @@ +{ + "externalParameter": { + "name": "externalParameter", + "in": "query", + "description": "this is parameter stored in external file", + "required": true + } +} diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/external-indirections/root.json b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/external-indirections/root.json new file mode 100644 index 0000000000..48945bc04b --- /dev/null +++ b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/external-indirections/root.json @@ -0,0 +1,12 @@ +{ + "swagger": "2.0", + "paths": { + "/": { + "parameters": [ + { + "$ref": "./ex1.json#/indirection" + } + ] + } + } +} diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/external-only-absolute-url/dereferenced.json b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/external-only-absolute-url/dereferenced.json new file mode 100644 index 0000000000..3f23a8c2e9 --- /dev/null +++ b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/external-only-absolute-url/dereferenced.json @@ -0,0 +1,17 @@ +[ + { + "swagger": "2.0", + "paths": { + "/": { + "parameters": [ + { + "name": "externalParameter", + "in": "query", + "description": "this is parameter stored in external file", + "required": true + } + ] + } + } + } +] diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/external-only-absolute-url/ex.json b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/external-only-absolute-url/ex.json new file mode 100644 index 0000000000..fc8a07edcd --- /dev/null +++ b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/external-only-absolute-url/ex.json @@ -0,0 +1,8 @@ +{ + "externalParameter": { + "name": "externalParameter", + "in": "query", + "description": "this is parameter stored in external file", + "required": true + } +} diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/external-only-absolute-url/root.json b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/external-only-absolute-url/root.json new file mode 100644 index 0000000000..dd2b11dc32 --- /dev/null +++ b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/external-only-absolute-url/root.json @@ -0,0 +1,12 @@ +{ + "swagger": "2.0", + "paths": { + "/": { + "parameters": [ + { + "$ref": "http://localhost:8123/ex.json#/externalParameter" + } + ] + } + } +} diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/external-only/dereferenced.json b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/external-only/dereferenced.json new file mode 100644 index 0000000000..3f23a8c2e9 --- /dev/null +++ b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/external-only/dereferenced.json @@ -0,0 +1,17 @@ +[ + { + "swagger": "2.0", + "paths": { + "/": { + "parameters": [ + { + "name": "externalParameter", + "in": "query", + "description": "this is parameter stored in external file", + "required": true + } + ] + } + } + } +] diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/external-only/ex.json b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/external-only/ex.json new file mode 100644 index 0000000000..fc8a07edcd --- /dev/null +++ b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/external-only/ex.json @@ -0,0 +1,8 @@ +{ + "externalParameter": { + "name": "externalParameter", + "in": "query", + "description": "this is parameter stored in external file", + "required": true + } +} diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/external-only/root.json b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/external-only/root.json new file mode 100644 index 0000000000..ea215061c9 --- /dev/null +++ b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/external-only/root.json @@ -0,0 +1,12 @@ +{ + "swagger": "2.0", + "paths": { + "/": { + "parameters": [ + { + "$ref": "./ex.json#/externalParameter" + } + ] + } + } +} diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/ignore-arbitrary-$refs/dereferenced.json b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/ignore-arbitrary-$refs/dereferenced.json new file mode 100644 index 0000000000..3b4cb4c90b --- /dev/null +++ b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/ignore-arbitrary-$refs/dereferenced.json @@ -0,0 +1,16 @@ +[ + { + "swagger": "2.0", + "paths": { + "/": { + "parameters": [ + { + "a": { + "$ref": "#/three" + } + } + ] + } + } + } +] diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/ignore-arbitrary-$refs/ex.json b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/ignore-arbitrary-$refs/ex.json new file mode 100644 index 0000000000..2e53208cfc --- /dev/null +++ b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/ignore-arbitrary-$refs/ex.json @@ -0,0 +1,15 @@ +{ + "one": { + "$ref": "#/two" + }, + "two": { + "a": { + "$ref": "#/three" + } + }, + "three": { + "b": { + "$ref": "#/two" + } + } +} diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/ignore-arbitrary-$refs/root.json b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/ignore-arbitrary-$refs/root.json new file mode 100644 index 0000000000..c1dc60d62f --- /dev/null +++ b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/ignore-arbitrary-$refs/root.json @@ -0,0 +1,12 @@ +{ + "swagger": "2.0", + "paths": { + "/": { + "parameters": [ + { + "$ref": "./ex.json#/one" + } + ] + } + } +} diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/ignore-external/dereferenced.json b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/ignore-external/dereferenced.json new file mode 100644 index 0000000000..5b2f91dd67 --- /dev/null +++ b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/ignore-external/dereferenced.json @@ -0,0 +1,39 @@ +[ + { + "swagger": "2.0", + "paths": { + "/": { + "parameters": [ + { + "name": "userId", + "in": "query", + "description": "ID of the user", + "required": true + }, + { + "name": "userId", + "in": "query", + "description": "ID of the user", + "required": true + }, + { + "name": "userId", + "in": "query", + "description": "ID of the user", + "required": true + }, + { + "name": "userId", + "in": "query", + "description": "ID of the user", + "required": true + }, + { + "$ref": "./ex.json#/externalParameter" + } + ] + } + } + } + +] diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/ignore-external/ex.json b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/ignore-external/ex.json new file mode 100644 index 0000000000..fc8a07edcd --- /dev/null +++ b/packages/apidom-reference/test/dereference/strategies/openapi-2/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/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/ignore-external/root.json b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/ignore-external/root.json new file mode 100644 index 0000000000..f30e9bc36d --- /dev/null +++ b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/ignore-external/root.json @@ -0,0 +1,27 @@ +{ + "swagger": "2.0", + "paths": { + "/": { + "parameters": [ + { + "$ref": "#/paths/~1/parameters/1" + }, + { + "$ref": "#/paths/~1/parameters/2" + }, + { + "$ref": "#/paths/~1/parameters/3" + }, + { + "name": "userId", + "in": "query", + "description": "ID of the user", + "required": true + }, + { + "$ref": "./ex.json#/externalParameter" + } + ] + } + } +} diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/indirect-external-circular/ex1.json b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/indirect-external-circular/ex1.json new file mode 100644 index 0000000000..f46bebc5f4 --- /dev/null +++ b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/indirect-external-circular/ex1.json @@ -0,0 +1,5 @@ +{ + "indirection": { + "$ref": "./ex2.json#/indirection" + } +} diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/indirect-external-circular/ex2.json b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/indirect-external-circular/ex2.json new file mode 100644 index 0000000000..d44c814227 --- /dev/null +++ b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/indirect-external-circular/ex2.json @@ -0,0 +1,5 @@ +{ + "indirection": { + "$ref": "./ex3.json#/indirection" + } +} diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/indirect-external-circular/ex3.json b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/indirect-external-circular/ex3.json new file mode 100644 index 0000000000..3316ecff10 --- /dev/null +++ b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/indirect-external-circular/ex3.json @@ -0,0 +1,5 @@ +{ + "indirection": { + "$ref": "./root.json#/paths/~1/parameters/0" + } +} diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/indirect-external-circular/root.json b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/indirect-external-circular/root.json new file mode 100644 index 0000000000..48945bc04b --- /dev/null +++ b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/indirect-external-circular/root.json @@ -0,0 +1,12 @@ +{ + "swagger": "2.0", + "paths": { + "/": { + "parameters": [ + { + "$ref": "./ex1.json#/indirection" + } + ] + } + } +} diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/indirect-internal-circular/root.json b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/indirect-internal-circular/root.json new file mode 100644 index 0000000000..48280b2954 --- /dev/null +++ b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/indirect-internal-circular/root.json @@ -0,0 +1,21 @@ +{ + "swagger": "2.0", + "paths": { + "/": { + "parameters": [ + { + "$ref": "#/paths/~1/parameters/1" + }, + { + "$ref": "#/paths/~1/parameters/2" + }, + { + "$ref": "#/paths/~1/parameters/3" + }, + { + "$ref": "#/paths/~1/parameters/0" + } + ] + } + } +} diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/internal-external/dereferenced.json b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/internal-external/dereferenced.json new file mode 100644 index 0000000000..77d334b3fd --- /dev/null +++ b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/internal-external/dereferenced.json @@ -0,0 +1,35 @@ +[ + { + "swagger": "2.0", + "paths": { + "/": { + "parameters": [ + { + "name": "skip", + "in": "query", + "description": "number of items to skip", + "required": true, + "type": "integer", + "format": "int32" + }, + { + "name": "externalParameter", + "in": "query", + "description": "this is parameter stored in external file", + "required": true + } + ] + } + }, + "parameters": { + "userIdRef": { + "name": "skip", + "in": "query", + "description": "number of items to skip", + "required": true, + "type": "integer", + "format": "int32" + } + } + } +] diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/internal-external/ex.json b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/internal-external/ex.json new file mode 100644 index 0000000000..fc8a07edcd --- /dev/null +++ b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/internal-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/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/internal-external/root.json b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/internal-external/root.json new file mode 100644 index 0000000000..b5af1aeebe --- /dev/null +++ b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/internal-external/root.json @@ -0,0 +1,25 @@ +{ + "swagger": "2.0", + "paths": { + "/": { + "parameters": [ + { + "$ref": "#/parameters/userIdRef" + }, + { + "$ref": "./ex.json#/externalParameter" + } + ] + } + }, + "parameters": { + "userIdRef": { + "name": "skip", + "in": "query", + "description": "number of items to skip", + "required": true, + "type": "integer", + "format": "int32" + } + } +} diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/internal-only/dereferenced.json b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/internal-only/dereferenced.json new file mode 100644 index 0000000000..b8a293fffa --- /dev/null +++ b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/internal-only/dereferenced.json @@ -0,0 +1,35 @@ +[ + { + "swagger": "2.0", + "paths": { + "/": { + "parameters": [ + { + "name": "userId", + "in": "query", + "description": "ID of the user", + "required": true + }, + { + "name": "userId", + "in": "query", + "description": "ID of the user", + "required": true + }, + { + "name": "userId", + "in": "query", + "description": "ID of the user", + "required": true + }, + { + "name": "userId", + "in": "query", + "description": "ID of the user", + "required": true + } + ] + } + } + } +] diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/internal-only/root.json b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/internal-only/root.json new file mode 100644 index 0000000000..b86001d607 --- /dev/null +++ b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/internal-only/root.json @@ -0,0 +1,24 @@ +{ + "swagger": "2.0", + "paths": { + "/": { + "parameters": [ + { + "$ref": "#/paths/~1/parameters/1" + }, + { + "$ref": "#/paths/~1/parameters/2" + }, + { + "$ref": "#/paths/~1/parameters/3" + }, + { + "name": "userId", + "in": "query", + "description": "ID of the user", + "required": true + } + ] + } + } +} diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/invalid-pointer/root.json b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/invalid-pointer/root.json new file mode 100644 index 0000000000..f590b5cfdb --- /dev/null +++ b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/invalid-pointer/root.json @@ -0,0 +1,18 @@ +{ + "swagger": "2.0", + "paths": { + "/": { + "parameters": [ + { + "$ref": "invalid-pointer" + }, + { + "name": "userId", + "in": "query", + "description": "ID of the user", + "required": true + } + ] + } + } +} diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/max-depth/ex1.json b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/max-depth/ex1.json new file mode 100644 index 0000000000..f46bebc5f4 --- /dev/null +++ b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/max-depth/ex1.json @@ -0,0 +1,5 @@ +{ + "indirection": { + "$ref": "./ex2.json#/indirection" + } +} diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/max-depth/ex2.json b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/max-depth/ex2.json new file mode 100644 index 0000000000..091689f702 --- /dev/null +++ b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/max-depth/ex2.json @@ -0,0 +1,5 @@ +{ + "indirection": { + "$ref": "./ex3.json#/externalParameter" + } +} diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/max-depth/ex3.json b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/max-depth/ex3.json new file mode 100644 index 0000000000..fc8a07edcd --- /dev/null +++ b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/max-depth/ex3.json @@ -0,0 +1,8 @@ +{ + "externalParameter": { + "name": "externalParameter", + "in": "query", + "description": "this is parameter stored in external file", + "required": true + } +} diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/max-depth/root.json b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/max-depth/root.json new file mode 100644 index 0000000000..48945bc04b --- /dev/null +++ b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/max-depth/root.json @@ -0,0 +1,12 @@ +{ + "swagger": "2.0", + "paths": { + "/": { + "parameters": [ + { + "$ref": "./ex1.json#/indirection" + } + ] + } + } +} diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/path-encoding/path with spaces/dereferenced.json b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/path-encoding/path with spaces/dereferenced.json new file mode 100644 index 0000000000..3f23a8c2e9 --- /dev/null +++ b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/path-encoding/path with spaces/dereferenced.json @@ -0,0 +1,17 @@ +[ + { + "swagger": "2.0", + "paths": { + "/": { + "parameters": [ + { + "name": "externalParameter", + "in": "query", + "description": "this is parameter stored in external file", + "required": true + } + ] + } + } + } +] diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/path-encoding/path with spaces/ex1.json b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/path-encoding/path with spaces/ex1.json new file mode 100644 index 0000000000..f46bebc5f4 --- /dev/null +++ b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/path-encoding/path with spaces/ex1.json @@ -0,0 +1,5 @@ +{ + "indirection": { + "$ref": "./ex2.json#/indirection" + } +} diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/path-encoding/path with spaces/ex2.json b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/path-encoding/path with spaces/ex2.json new file mode 100644 index 0000000000..091689f702 --- /dev/null +++ b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/path-encoding/path with spaces/ex2.json @@ -0,0 +1,5 @@ +{ + "indirection": { + "$ref": "./ex3.json#/externalParameter" + } +} diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/path-encoding/path with spaces/ex3.json b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/path-encoding/path with spaces/ex3.json new file mode 100644 index 0000000000..380368df4f --- /dev/null +++ b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/path-encoding/path with spaces/ex3.json @@ -0,0 +1,8 @@ + { + "externalParameter": { + "name": "externalParameter", + "in": "query", + "description": "this is parameter stored in external file", + "required": true + } +} diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/path-encoding/path with spaces/root.json b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/path-encoding/path with spaces/root.json new file mode 100644 index 0000000000..48945bc04b --- /dev/null +++ b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/path-encoding/path with spaces/root.json @@ -0,0 +1,12 @@ +{ + "swagger": "2.0", + "paths": { + "/": { + "parameters": [ + { + "$ref": "./ex1.json#/indirection" + } + ] + } + } +} diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/refset-as-option/dereferenced.json b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/refset-as-option/dereferenced.json new file mode 100644 index 0000000000..3f23a8c2e9 --- /dev/null +++ b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/refset-as-option/dereferenced.json @@ -0,0 +1,17 @@ +[ + { + "swagger": "2.0", + "paths": { + "/": { + "parameters": [ + { + "name": "externalParameter", + "in": "query", + "description": "this is parameter stored in external file", + "required": true + } + ] + } + } + } +] diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/refset-as-option/ex1.json b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/refset-as-option/ex1.json new file mode 100644 index 0000000000..f46bebc5f4 --- /dev/null +++ b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/refset-as-option/ex1.json @@ -0,0 +1,5 @@ +{ + "indirection": { + "$ref": "./ex2.json#/indirection" + } +} diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/refset-as-option/ex2.json b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/refset-as-option/ex2.json new file mode 100644 index 0000000000..091689f702 --- /dev/null +++ b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/refset-as-option/ex2.json @@ -0,0 +1,5 @@ +{ + "indirection": { + "$ref": "./ex3.json#/externalParameter" + } +} diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/refset-as-option/ex3.json b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/refset-as-option/ex3.json new file mode 100644 index 0000000000..fc8a07edcd --- /dev/null +++ b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/refset-as-option/ex3.json @@ -0,0 +1,8 @@ +{ + "externalParameter": { + "name": "externalParameter", + "in": "query", + "description": "this is parameter stored in external file", + "required": true + } +} diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/refset-as-option/root.json b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/refset-as-option/root.json new file mode 100644 index 0000000000..48945bc04b --- /dev/null +++ b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/refset-as-option/root.json @@ -0,0 +1,12 @@ +{ + "swagger": "2.0", + "paths": { + "/": { + "parameters": [ + { + "$ref": "./ex1.json#/indirection" + } + ] + } + } +} diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/unresolvable-reference/root.json b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/unresolvable-reference/root.json new file mode 100644 index 0000000000..ea215061c9 --- /dev/null +++ b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/fixtures/unresolvable-reference/root.json @@ -0,0 +1,12 @@ +{ + "swagger": "2.0", + "paths": { + "/": { + "parameters": [ + { + "$ref": "./ex.json#/externalParameter" + } + ] + } + } +} diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/index.ts b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/index.ts new file mode 100644 index 0000000000..632366d464 --- /dev/null +++ b/packages/apidom-reference/test/dereference/strategies/openapi-2/reference-object/index.ts @@ -0,0 +1,346 @@ +import path from 'node:path'; +import { assert } from 'chai'; +import { toValue } from '@swagger-api/apidom-core'; +import { isParameterElement, mediaTypes } from '@swagger-api/apidom-ns-openapi-2'; +import { evaluate } from '@swagger-api/apidom-json-pointer'; + +import { loadJsonFile } from '../../../../helpers'; +import { parse, dereference, Reference, ReferenceSet } from '../../../../../src'; +import DereferenceError from '../../../../../src/errors/DereferenceError'; +import MaximumDereferenceDepthError from '../../../../../src/errors/MaximumDereferenceDepthError'; +import MaximumResolverDepthError from '../../../../../src/errors/MaximumResolverDepthError'; + +const rootFixturePath = path.join(__dirname, 'fixtures'); + +describe('dereference', function () { + context('strategies', function () { + context('openapi-2', function () { + context('Reference Object', function () { + context('given Reference Objects pointing internally and externally', function () { + const fixturePath = path.join(rootFixturePath, 'internal-external'); + + specify('should dereference', async function () { + const rootFilePath = path.join(fixturePath, 'root.json'); + const actual = await dereference(rootFilePath, { + parse: { mediaType: mediaTypes.latest('json') }, + }); + const expected = loadJsonFile(path.join(fixturePath, 'dereferenced.json')); + + assert.deepEqual(toValue(actual), expected); + }); + + specify('should apply semantics to external fragment', async function () { + const rootFilePath = path.join(fixturePath, 'root.json'); + const dereferenced = await dereference(rootFilePath, { + parse: { mediaType: mediaTypes.latest('json') }, + }); + const fragment = evaluate('/0/paths/~1/parameters/1', dereferenced); + + assert.isTrue(isParameterElement(fragment)); + }); + + specify( + 'should annotate transcluded element with additional metadata', + async function () { + const rootFilePath = path.join(fixturePath, 'root.json'); + const dereferenced = await dereference(rootFilePath, { + parse: { mediaType: mediaTypes.latest('json') }, + }); + const fragment = evaluate('/0/paths/~1/parameters/0', dereferenced); + + assert.strictEqual( + toValue(fragment.meta.get('ref-fields').get('$ref')), + '#/parameters/userIdRef', + ); + }, + ); + }); + + context('given Reference Objects pointing internally only', function () { + const fixturePath = path.join(rootFixturePath, 'internal-only'); + + specify('should dereference', async function () { + const rootFilePath = path.join(fixturePath, 'root.json'); + const actual = await dereference(rootFilePath, { + parse: { mediaType: mediaTypes.latest('json') }, + }); + const expected = loadJsonFile(path.join(fixturePath, 'dereferenced.json')); + + assert.deepEqual(toValue(actual), expected); + }); + }); + + context('given Reference Objects pointing externally only', function () { + const fixturePath = path.join(rootFixturePath, 'external-only'); + + specify('should dereference', async function () { + const rootFilePath = path.join(fixturePath, 'root.json'); + const actual = await dereference(rootFilePath, { + parse: { mediaType: mediaTypes.latest('json') }, + }); + const expected = loadJsonFile(path.join(fixturePath, 'dereferenced.json')); + + assert.deepEqual(toValue(actual), expected); + }); + }); + + context('given Reference Objects pointing to external indirections', function () { + const fixturePath = path.join(rootFixturePath, 'external-indirections'); + + specify('should dereference', async function () { + const rootFilePath = path.join(fixturePath, 'root.json'); + const actual = await dereference(rootFilePath, { + parse: { mediaType: mediaTypes.latest('json') }, + }); + const expected = loadJsonFile(path.join(fixturePath, 'dereferenced.json')); + + assert.deepEqual(toValue(actual), expected); + }); + + specify('should apply semantics to eventual external fragment', async function () { + const rootFilePath = path.join(fixturePath, 'root.json'); + const dereferenced = await dereference(rootFilePath, { + parse: { mediaType: mediaTypes.latest('json') }, + }); + const fragment = evaluate('/0/paths/~1/parameters/0', dereferenced); + + assert.isTrue(isParameterElement(fragment)); + }); + }); + + context('given Reference Objects with additional ignored fields', function () { + const fixturePath = path.join(rootFixturePath, 'additional-ignored-fields'); + + specify('should dereference', async function () { + const rootFilePath = path.join(fixturePath, 'root.json'); + const actual = await dereference(rootFilePath, { + parse: { mediaType: mediaTypes.latest('json') }, + }); + const expected = loadJsonFile(path.join(fixturePath, 'dereferenced.json')); + + assert.deepEqual(toValue(actual), expected); + }); + }); + + 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: mediaTypes.latest('json') }, + 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'); + + specify('should throw error', async function () { + const rootFilePath = path.join(fixturePath, 'root.json'); + try { + await dereference(rootFilePath, { + parse: { mediaType: mediaTypes.latest('json') }, + }); + } catch (e) { + assert.instanceOf(e, DereferenceError); + } + }); + }); + + context('given Reference Objects with indirect circular internal reference', function () { + const fixturePath = path.join(rootFixturePath, 'indirect-internal-circular'); + + specify('should throw error', async function () { + const rootFilePath = path.join(fixturePath, 'root.json'); + try { + await dereference(rootFilePath, { + parse: { mediaType: mediaTypes.latest('json') }, + }); + } catch (e) { + assert.instanceOf(e, DereferenceError); + } + }); + }); + + context('given Reference Objects with direct circular external reference', function () { + const fixturePath = path.join(rootFixturePath, 'direct-external-circular'); + + specify('should throw error', async function () { + const rootFilePath = path.join(fixturePath, 'root.json'); + try { + await dereference(rootFilePath, { + parse: { mediaType: mediaTypes.latest('json') }, + }); + } catch (e) { + assert.instanceOf(e, DereferenceError); + } + }); + }); + + context('given Reference Objects with indirect circular external reference', function () { + const fixturePath = path.join(rootFixturePath, 'indirect-external-circular'); + + specify('should throw error', async function () { + const rootFilePath = path.join(fixturePath, 'root.json'); + try { + await dereference(rootFilePath, { + parse: { mediaType: mediaTypes.latest('json') }, + }); + } catch (e) { + assert.instanceOf(e, DereferenceError); + } + }); + }); + + context('given Reference Objects with unresolvable reference', function () { + const fixturePath = path.join(rootFixturePath, 'unresolvable-reference'); + + specify('should throw error', async function () { + const rootFilePath = path.join(fixturePath, 'root.json'); + try { + await dereference(rootFilePath, { + parse: { mediaType: mediaTypes.latest('json') }, + }); + } catch (e) { + assert.instanceOf(e, DereferenceError); + } + }); + }); + + context('given Reference Objects with invalid JSON Pointer', function () { + const fixturePath = path.join(rootFixturePath, 'invalid-pointer'); + + specify('should throw error', async function () { + const rootFilePath = path.join(fixturePath, 'root.json'); + try { + await dereference(rootFilePath, { + parse: { mediaType: mediaTypes.latest('json') }, + }); + } catch (e) { + assert.instanceOf(e, DereferenceError); + } + }); + }); + + context('given Reference Objects with arbitrary circular references', function () { + const fixturePath = path.join(rootFixturePath, 'ignore-arbitrary-$refs'); + + specify('should dereference', async function () { + const rootFilePath = path.join(fixturePath, 'root.json'); + const actual = await dereference(rootFilePath, { + parse: { mediaType: mediaTypes.latest('json') }, + }); + const expected = loadJsonFile(path.join(fixturePath, 'dereferenced.json')); + + assert.deepEqual(toValue(actual), expected); + }); + }); + + context('given Reference Objects with external circular dependency', function () { + const fixturePath = path.join(rootFixturePath, 'external-circular-dependency'); + + specify('should dereference', async function () { + const rootFilePath = path.join(fixturePath, 'root.json'); + const actual = await dereference(rootFilePath, { + parse: { mediaType: mediaTypes.latest('json') }, + }); + const expected = loadJsonFile(path.join(fixturePath, 'dereferenced.json')); + + assert.deepEqual(toValue(actual), expected); + }); + }); + + context('given Reference Objects and maxDepth of dereference', function () { + const fixturePath = path.join(rootFixturePath, 'max-depth'); + + specify('should throw error', async function () { + const rootFilePath = path.join(fixturePath, 'root.json'); + + try { + await dereference(rootFilePath, { + parse: { mediaType: mediaTypes.latest('json') }, + dereference: { maxDepth: 2 }, + }); + assert.fail('should throw MaximumDereferenceDepthError'); + } catch (error: any) { + assert.instanceOf(error, DereferenceError); + assert.instanceOf(error.cause.cause, MaximumDereferenceDepthError); + assert.match(error.cause.cause.message, /fixtures\/max-depth\/ex2.json"$/); + } + }); + }); + + context('given Reference Objects and maxDepth of resolution', function () { + const fixturePath = path.join(rootFixturePath, 'max-depth'); + + specify('should throw error', async function () { + const rootFilePath = path.join(fixturePath, 'root.json'); + + try { + await dereference(rootFilePath, { + parse: { mediaType: mediaTypes.latest('json') }, + resolve: { maxDepth: 2 }, + }); + assert.fail('should throw MaximumResolverDepthError'); + } catch (error: any) { + assert.instanceOf(error, DereferenceError); + assert.instanceOf(error.cause.cause, MaximumResolverDepthError); + assert.match(error.cause.cause.message, /fixtures\/max-depth\/ex2.json"$/); + } + }); + }); + + context('given refSet is provided as an option', function () { + specify('should dereference without external resolution', async function () { + const fixturePath = path.join(__dirname, 'fixtures', 'refset-as-option'); + + const rootURI = path.join(fixturePath, 'root.json'); + const rootParseResult = await parse(rootURI, { mediaType: mediaTypes.latest('json') }); + const rootRef = Reference({ uri: rootURI, value: rootParseResult }); + + const ex1URI = path.join(fixturePath, 'ex1.json'); + const ex1ParseResult = await parse(ex1URI, { mediaType: 'application/json' }); + const ex1Ref = Reference({ uri: ex1URI, value: ex1ParseResult }); + + const ex2URI = path.join(fixturePath, 'ex2.json'); + const ex2ParseResult = await parse(ex2URI, { mediaType: 'application/json' }); + const ex2Ref = Reference({ uri: ex2URI, value: ex2ParseResult }); + + const ex3URI = path.join(fixturePath, 'ex3.json'); + const ex3ParseResult = await parse(ex3URI, { mediaType: 'application/json' }); + const ex3Ref = Reference({ uri: ex3URI, value: ex3ParseResult }); + + const refSet = ReferenceSet({ refs: [rootRef, ex1Ref, ex2Ref, ex3Ref] }); + + const actual = await dereference(rootURI, { + dereference: { refSet }, + resolve: { resolvers: [] }, + }); + const expected = loadJsonFile(path.join(fixturePath, 'dereferenced.json')); + + assert.deepEqual(toValue(actual), expected); + }); + }); + + context('given path with invalid URL characters - spaces', function () { + const fixturePath = path.join(rootFixturePath, 'path-encoding', 'path with spaces'); + + specify('should dereference', async function () { + const rootFilePath = path.join(fixturePath, 'root.json'); + const actual = await dereference(rootFilePath, { + parse: { mediaType: mediaTypes.latest('json') }, + }); + const expected = loadJsonFile(path.join(fixturePath, 'dereferenced.json')); + + assert.deepEqual(toValue(actual), expected); + }); + }); + }); + }); + }); +}); diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-3-0/reference-object/dereference-apidom.ts b/packages/apidom-reference/test/dereference/strategies/openapi-3-0/reference-object/dereference-apidom.ts index 53e5362044..4d5c9dfa9b 100644 --- a/packages/apidom-reference/test/dereference/strategies/openapi-3-0/reference-object/dereference-apidom.ts +++ b/packages/apidom-reference/test/dereference/strategies/openapi-3-0/reference-object/dereference-apidom.ts @@ -13,7 +13,7 @@ import { ServerTerminable, createHTTPServer } from '../../../../helpers'; describe('dereference', function () { context('strategies', function () { - context('openapi-3-o', function () { + context('openapi-3-0', function () { context('Reference Object', function () { context( 'given single ReferenceElement passed to dereferenceApiDOM with internal references', diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-3-0/reference-object/index.ts b/packages/apidom-reference/test/dereference/strategies/openapi-3-0/reference-object/index.ts index 6313fd1939..b9304a357d 100644 --- a/packages/apidom-reference/test/dereference/strategies/openapi-3-0/reference-object/index.ts +++ b/packages/apidom-reference/test/dereference/strategies/openapi-3-0/reference-object/index.ts @@ -334,7 +334,10 @@ describe('dereference', function () { const refSet = await resolve(uri, { parse: { mediaType: mediaTypes.latest('json') }, }); - const actual = await dereference(uri, { dereference: { refSet } }); + const actual = await dereference(uri, { + dereference: { refSet }, + resolve: { resolvers: [] }, + }); const expected = loadJsonFile(path.join(fixturePath, 'dereferenced.json')); assert.deepEqual(toValue(actual), expected); diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-3-1/reference-object/index.ts b/packages/apidom-reference/test/dereference/strategies/openapi-3-1/reference-object/index.ts index 06ff46c785..7d01d2f646 100644 --- a/packages/apidom-reference/test/dereference/strategies/openapi-3-1/reference-object/index.ts +++ b/packages/apidom-reference/test/dereference/strategies/openapi-3-1/reference-object/index.ts @@ -346,7 +346,10 @@ describe('dereference', function () { const refSet = await resolve(uri, { parse: { mediaType: mediaTypes.latest('json') }, }); - const actual = await dereference(uri, { dereference: { refSet } }); + const actual = await dereference(uri, { + dereference: { refSet }, + resolve: { resolvers: [] }, + }); const expected = loadJsonFile(path.join(fixturePath, 'dereferenced.json')); assert.deepEqual(toValue(actual), expected);