From c62e206a27cd6a0f2dcfb6aadd10acebec400465 Mon Sep 17 00:00:00 2001 From: Vladimir Gorej Date: Thu, 28 Mar 2024 14:04:27 +0100 Subject: [PATCH] fix(reference): fix regression in OpenAPI 3.0.x dereferencing Refs #3974 --- .../strategies/openapi-3-0/visitor.ts | 20 +++++++---- .../reference-object/dereference-apidom.ts | 13 ++++---- .../fixtures/external-only/dereferenced.json | 33 +++++++++++++++---- .../fixtures/external-only/ex.json | 25 +++++++++++--- .../fixtures/external-only/root.json | 6 ++-- 5 files changed, 71 insertions(+), 26 deletions(-) diff --git a/packages/apidom-reference/src/dereference/strategies/openapi-3-0/visitor.ts b/packages/apidom-reference/src/dereference/strategies/openapi-3-0/visitor.ts index 3d3aef449f..3bd3649f5c 100644 --- a/packages/apidom-reference/src/dereference/strategies/openapi-3-0/visitor.ts +++ b/packages/apidom-reference/src/dereference/strategies/openapi-3-0/visitor.ts @@ -234,13 +234,17 @@ const OpenApi3_0DereferenceVisitor = stampit({ * * Cases to consider: * 1. We're crossing document boundary - * 2. Fragment is a Reference Object. We need to follow it to get the eventual value - * 3. We are dereferencing the fragment lazily/eagerly depending on circular mode + * 2. Fragment is from non-root document + * 3. Fragment is a Reference Object. We need to follow it to get the eventual value + * 4. We are dereferencing the fragment lazily/eagerly depending on circular mode */ + const isNonRootDocument = reference.refSet.rootRef.uri !== reference.uri; + const shouldDetectCircular = ['error', 'replace'].includes(this.options.dereference.circular); if ( (isExternalReference || + isNonRootDocument || isReferenceElement(referencedElement) || - ['error', 'replace'].includes(this.options.dereference.circular)) && + shouldDetectCircular) && !ancestorsLineage.includesCycle(referencedElement) ) { // append referencing reference to ancestors lineage @@ -401,13 +405,17 @@ const OpenApi3_0DereferenceVisitor = stampit({ * * Cases to consider: * 1. We're crossing document boundary - * 2. Fragment is a Path Item Object with $ref field. We need to follow it to get the eventual value - * 3. We are dereferencing the fragment lazily/eagerly depending on circular mode + * 2. Fragment is from non-root document + * 3. Fragment is a Path Item Object with $ref field. We need to follow it to get the eventual value + * 4. We are dereferencing the fragment lazily/eagerly depending on circular mode */ + const isNonRootDocument = reference.refSet.rootRef.uri !== reference.uri; + const shouldDetectCircular = ['error', 'replace'].includes(this.options.dereference.circular); if ( (isExternalReference || + isNonRootDocument || (isPathItemElement(referencedElement) && isStringElement(referencedElement.$ref)) || - ['error', 'replace'].includes(this.options.dereference.circular)) && + shouldDetectCircular) && !ancestorsLineage.includesCycle(referencedElement) ) { // append referencing reference to ancestors lineage 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 4d5c9dfa9b..c0b0e5e68e 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 @@ -3,6 +3,7 @@ import { assert } from 'chai'; import { mediaTypes, isParameterElement, + isSchemaElement, OpenApi3_0Element, } from '@swagger-api/apidom-ns-openapi-3-0'; import { toValue } from '@swagger-api/apidom-core'; @@ -123,7 +124,7 @@ describe('dereference', function () { parse: { mediaType: mediaTypes.latest('json') }, }); const referenceElement = evaluate( - '/components/parameters/externalRef', + '/components/schemas/externalSchema', parseResult.api as OpenApi3_0Element, ); const dereferenced = await dereferenceApiDOM(referenceElement, { @@ -131,7 +132,7 @@ describe('dereference', function () { resolve: { baseURI: fixturePath }, }); - assert.isTrue(isParameterElement(dereferenced)); + assert.isTrue(isSchemaElement(dereferenced)); }); specify('should dereference and contain metadata about origin', async function () { @@ -139,7 +140,7 @@ describe('dereference', function () { parse: { mediaType: mediaTypes.latest('json') }, }); const referenceElement = evaluate( - '/components/parameters/externalRef', + '/components/schemas/externalSchema', parseResult.api as OpenApi3_0Element, ); const dereferenced = await dereferenceApiDOM(referenceElement, { @@ -173,7 +174,7 @@ describe('dereference', function () { parse: { mediaType: mediaTypes.latest('json') }, }); const referenceElement = evaluate( - '/components/parameters/externalRef', + '/components/schemas/externalSchema', parseResult.api as OpenApi3_0Element, ); const dereferenced = await dereferenceApiDOM(referenceElement, { @@ -181,7 +182,7 @@ describe('dereference', function () { resolve: { baseURI: `http://localhost:${httpPort}/root.json` }, }); - assert.isTrue(isParameterElement(dereferenced)); + assert.isTrue(isSchemaElement(dereferenced)); }); specify('should dereference and contain metadata about origin', async function () { @@ -189,7 +190,7 @@ describe('dereference', function () { parse: { mediaType: mediaTypes.latest('json') }, }); const referenceElement = evaluate( - '/components/parameters/externalRef', + '/components/schemas/externalSchema', parseResult.api as OpenApi3_0Element, ); const dereferenced = await dereferenceApiDOM(referenceElement, { diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-3-0/reference-object/fixtures/external-only/dereferenced.json b/packages/apidom-reference/test/dereference/strategies/openapi-3-0/reference-object/fixtures/external-only/dereferenced.json index cd4128ce28..4f79943589 100644 --- a/packages/apidom-reference/test/dereference/strategies/openapi-3-0/reference-object/fixtures/external-only/dereferenced.json +++ b/packages/apidom-reference/test/dereference/strategies/openapi-3-0/reference-object/fixtures/external-only/dereferenced.json @@ -2,12 +2,33 @@ { "openapi": "3.0.3", "components": { - "parameters": { - "externalRef": { - "name": "externalParameter", - "in": "query", - "description": "this is parameter stored in external file", - "required": true + "schemas": { + "externalSchema": { + "schema1": { + "type": "object", + "properties": { + "sentAt": { + "type": "string", + "format": "date-time", + "description": "Date and time when the message was sent." + } + } + }, + "somePayload": { + "type": "object", + "properties": { + "sentAt": { + "type": "string", + "format": "date-time", + "description": "Date and time when the message was sent." + } + } + }, + "sentAt": { + "type": "string", + "format": "date-time", + "description": "Date and time when the message was sent." + } } } } diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-3-0/reference-object/fixtures/external-only/ex.json b/packages/apidom-reference/test/dereference/strategies/openapi-3-0/reference-object/fixtures/external-only/ex.json index fc8a07edcd..940d341af2 100644 --- a/packages/apidom-reference/test/dereference/strategies/openapi-3-0/reference-object/fixtures/external-only/ex.json +++ b/packages/apidom-reference/test/dereference/strategies/openapi-3-0/reference-object/fixtures/external-only/ex.json @@ -1,8 +1,23 @@ { - "externalParameter": { - "name": "externalParameter", - "in": "query", - "description": "this is parameter stored in external file", - "required": true + "openapi": "3.0.3", + "components": { + "schemas": { + "schema1": { + "$ref": "#/components/schemas/somePayload" + }, + "somePayload": { + "type": "object", + "properties": { + "sentAt": { + "$ref": "#/components/schemas/sentAt" + } + } + }, + "sentAt": { + "type": "string", + "format": "date-time", + "description": "Date and time when the message was sent." + } + } } } diff --git a/packages/apidom-reference/test/dereference/strategies/openapi-3-0/reference-object/fixtures/external-only/root.json b/packages/apidom-reference/test/dereference/strategies/openapi-3-0/reference-object/fixtures/external-only/root.json index 905bec17b3..8a625faceb 100644 --- a/packages/apidom-reference/test/dereference/strategies/openapi-3-0/reference-object/fixtures/external-only/root.json +++ b/packages/apidom-reference/test/dereference/strategies/openapi-3-0/reference-object/fixtures/external-only/root.json @@ -1,9 +1,9 @@ { "openapi": "3.0.3", "components": { - "parameters": { - "externalRef": { - "$ref": "./ex.json#/externalParameter" + "schemas": { + "externalSchema": { + "$ref": "./ex.json#/components/schemas" } } }