From c8c668a67f5ed0414aadfa125961ccb2d3e87771 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Gorej?= Date: Mon, 3 Apr 2023 16:51:09 +0200 Subject: [PATCH] fix(resolver): fix bug in detecting cycles for OpenAPI 3.1.0 (#2911) Refs https://github.com/swagger-api/swagger-ui/issues/8537 --- package-lock.json | 142 +++++++++--------- package.json | 8 +- .../visitors/dereference.js | 118 +++++++-------- .../dereferenced.json | 40 +++++ .../root.json | 32 ++++ .../dereferenced.json | 40 +++++ .../root.json | 32 ++++ .../cycle-internal-advanced/root.json | 35 +++++ .../schema-object/index.js | 97 +++++++++++- 9 files changed, 409 insertions(+), 135 deletions(-) create mode 100644 test/resolver/apidom/reference/dereference/strategies/openapi-3-1-swagger-client/schema-object/__fixtures__/cycle-internal-advanced-circular-structures/dereferenced.json create mode 100644 test/resolver/apidom/reference/dereference/strategies/openapi-3-1-swagger-client/schema-object/__fixtures__/cycle-internal-advanced-circular-structures/root.json create mode 100644 test/resolver/apidom/reference/dereference/strategies/openapi-3-1-swagger-client/schema-object/__fixtures__/cycle-internal-advanced-http-circular-structures/dereferenced.json create mode 100644 test/resolver/apidom/reference/dereference/strategies/openapi-3-1-swagger-client/schema-object/__fixtures__/cycle-internal-advanced-http-circular-structures/root.json create mode 100644 test/resolver/apidom/reference/dereference/strategies/openapi-3-1-swagger-client/schema-object/__fixtures__/cycle-internal-advanced/root.json diff --git a/package-lock.json b/package-lock.json index f319a9168..93b966619 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,10 +10,10 @@ "license": "Apache-2.0", "dependencies": { "@babel/runtime-corejs3": "^7.20.13", - "@swagger-api/apidom-core": ">=0.69.1 <1.0.0", - "@swagger-api/apidom-json-pointer": ">=0.69.1 <1.0.0", - "@swagger-api/apidom-ns-openapi-3-1": ">=0.69.1 <1.0.0", - "@swagger-api/apidom-reference": ">=0.69.1 <1.0.0", + "@swagger-api/apidom-core": ">=0.69.2 <1.0.0", + "@swagger-api/apidom-json-pointer": ">=0.69.2 <1.0.0", + "@swagger-api/apidom-ns-openapi-3-1": ">=0.69.2 <1.0.0", + "@swagger-api/apidom-reference": ">=0.69.2 <1.0.0", "cookie": "~0.5.0", "cross-fetch": "^3.1.5", "deepmerge": "~4.3.0", @@ -3534,9 +3534,9 @@ } }, "node_modules/@swagger-api/apidom-core": { - "version": "0.69.1", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-core/-/apidom-core-0.69.1.tgz", - "integrity": "sha512-XkGEoIuk0cqsbzv03AdfcDr5suKJnLC+CkZS4rzHjs307RFG1xX722Aj6snUs5hRFQKBH+cdujpspugh7HdNmw==", + "version": "0.69.2", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-core/-/apidom-core-0.69.2.tgz", + "integrity": "sha512-av9vS1SbXxGJvCt4QggrIvS8dr3ZfL6jxrNQGr4cq1wFY/n5ruj0RsXix208c3Zp1Kua3QVOUaJvA+7RdT1VJA==", "dependencies": { "@babel/runtime-corejs3": "^7.20.7", "@swagger-api/apidom-ast": "^0.69.0", @@ -3549,12 +3549,12 @@ } }, "node_modules/@swagger-api/apidom-json-pointer": { - "version": "0.69.1", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-json-pointer/-/apidom-json-pointer-0.69.1.tgz", - "integrity": "sha512-bRSSpSdXVQQ0LTmdY/KDZ7XqxwMf1DmgOqStFq71A8UZs1cTAa83qQ7+OAPqz3/qhSVAOE1sIU5fc32j+wqtmg==", + "version": "0.69.2", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-json-pointer/-/apidom-json-pointer-0.69.2.tgz", + "integrity": "sha512-ipu94QNw8ZKWC+pfie5IyIzVImR5N0PANXkUSfFon5L4aMAtggKpZn7aUv/2Cxn51JsCvjZwkXT7PaJ8RddpxA==", "dependencies": { "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^0.69.1", + "@swagger-api/apidom-core": "^0.69.2", "@types/ramda": "=0.28.23", "ramda": "=0.28.0", "ramda-adjunct": "=3.4.0" @@ -3568,12 +3568,12 @@ "optional": true }, "node_modules/@swagger-api/apidom-ns-json-schema-draft-4": { - "version": "0.69.1", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-4/-/apidom-ns-json-schema-draft-4-0.69.1.tgz", - "integrity": "sha512-6qkUH0R2C2dxzr5Af1p4N/ccyH34Fnv9CVT4tIPFThmxLI+Ivdzy6BbZ4lT9rzFet1G0mL3hI/pdLNz8Z28Ftw==", + "version": "0.69.2", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-4/-/apidom-ns-json-schema-draft-4-0.69.2.tgz", + "integrity": "sha512-8yB4afGBAX+vN5oNRxZMWWS/2G0Q9VUzlL2AOu0Q70FQkscbjQcsb6QX9LbHFMrwi3MgOgE0ewMncpcwskuoXA==", "dependencies": { "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^0.69.1", + "@swagger-api/apidom-core": "^0.69.2", "@types/ramda": "=0.28.23", "ramda": "=0.28.0", "ramda-adjunct": "=3.4.0", @@ -3581,13 +3581,13 @@ } }, "node_modules/@swagger-api/apidom-ns-openapi-3-0": { - "version": "0.69.1", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-3-0/-/apidom-ns-openapi-3-0-0.69.1.tgz", - "integrity": "sha512-WysPvllkS36ONiDWUTfNACkgpoDNibCT8sOGaEv+NTGlUjhZuyI6GMJxw5DcaeRyIV5Tp14vyCCbacGMr+02WQ==", + "version": "0.69.2", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-3-0/-/apidom-ns-openapi-3-0-0.69.2.tgz", + "integrity": "sha512-OroXRC+Q1btrpuQ3+ZbMi9XGYiab3YQMg/Rx1wpszbW5C5IPtaa2/FtMcBGYWR5IIdxi+bAT8itbMBGSCcz/Ew==", "dependencies": { "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^0.69.1", - "@swagger-api/apidom-ns-json-schema-draft-4": "^0.69.1", + "@swagger-api/apidom-core": "^0.69.2", + "@swagger-api/apidom-ns-json-schema-draft-4": "^0.69.2", "@types/ramda": "=0.28.23", "ramda": "=0.28.0", "ramda-adjunct": "=3.4.0", @@ -3595,13 +3595,13 @@ } }, "node_modules/@swagger-api/apidom-ns-openapi-3-1": { - "version": "0.69.1", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-3-1/-/apidom-ns-openapi-3-1-0.69.1.tgz", - "integrity": "sha512-chk+UF8soH5TuvabUstPmb1Mx3IP3dUJQ7SDB3tHusg+f2xEo5S7Hx/mrSKJDX2Wp0qFshqpUFSRC8ZUnFgJaQ==", + "version": "0.69.2", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-3-1/-/apidom-ns-openapi-3-1-0.69.2.tgz", + "integrity": "sha512-2yyUmdbvkDnZuOGDduvlp4dtLL/8a1PCR+Ajk9+PR4ZTdbMFtZWcr/knGc33Rtr8eXQwd4NPypFHhTLCEHiGwg==", "dependencies": { "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^0.69.1", - "@swagger-api/apidom-ns-openapi-3-0": "^0.69.1", + "@swagger-api/apidom-core": "^0.69.2", + "@swagger-api/apidom-ns-openapi-3-0": "^0.69.2", "@types/ramda": "=0.28.23", "ramda": "=0.28.0", "ramda-adjunct": "=3.4.0", @@ -3679,12 +3679,12 @@ "optional": true }, "node_modules/@swagger-api/apidom-reference": { - "version": "0.69.1", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-reference/-/apidom-reference-0.69.1.tgz", - "integrity": "sha512-P/HD5l9zoQAR6H6IL52Y4SjxBXRCY2kzXa0CH+jKqpNNQxLEPf7penP9T6rH339BvKhATK5LanxDYtQZRCDBdw==", + "version": "0.69.2", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-reference/-/apidom-reference-0.69.2.tgz", + "integrity": "sha512-aJsgtCP71t8a+frS+qn1FW9MqjjK60c3AnB2G3cUXGVwzmEPEkvBFF0LGlPmHftVvzzBvI7AsMC7+HZPM/t7rQ==", "dependencies": { "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^0.69.1", + "@swagger-api/apidom-core": "^0.69.2", "@types/ramda": "=0.28.23", "axios": "=1.3.4", "minimatch": "=7.4.3", @@ -3694,20 +3694,20 @@ "stampit": "=4.3.2" }, "optionalDependencies": { - "@swagger-api/apidom-json-pointer": "^0.69.1", - "@swagger-api/apidom-ns-asyncapi-2": "^0.69.1", - "@swagger-api/apidom-ns-openapi-3-0": "^0.69.1", - "@swagger-api/apidom-ns-openapi-3-1": "^0.69.1", - "@swagger-api/apidom-parser-adapter-api-design-systems-json": "^0.69.1", - "@swagger-api/apidom-parser-adapter-api-design-systems-yaml": "^0.69.1", - "@swagger-api/apidom-parser-adapter-asyncapi-json-2": "^0.69.1", - "@swagger-api/apidom-parser-adapter-asyncapi-yaml-2": "^0.69.1", - "@swagger-api/apidom-parser-adapter-json": "^0.69.1", - "@swagger-api/apidom-parser-adapter-openapi-json-3-0": "^0.69.1", - "@swagger-api/apidom-parser-adapter-openapi-json-3-1": "^0.69.1", - "@swagger-api/apidom-parser-adapter-openapi-yaml-3-0": "^0.69.1", - "@swagger-api/apidom-parser-adapter-openapi-yaml-3-1": "^0.69.1", - "@swagger-api/apidom-parser-adapter-yaml-1-2": "^0.69.1" + "@swagger-api/apidom-json-pointer": "^0.69.2", + "@swagger-api/apidom-ns-asyncapi-2": "^0.69.2", + "@swagger-api/apidom-ns-openapi-3-0": "^0.69.2", + "@swagger-api/apidom-ns-openapi-3-1": "^0.69.2", + "@swagger-api/apidom-parser-adapter-api-design-systems-json": "^0.69.2", + "@swagger-api/apidom-parser-adapter-api-design-systems-yaml": "^0.69.2", + "@swagger-api/apidom-parser-adapter-asyncapi-json-2": "^0.69.2", + "@swagger-api/apidom-parser-adapter-asyncapi-yaml-2": "^0.69.2", + "@swagger-api/apidom-parser-adapter-json": "^0.69.2", + "@swagger-api/apidom-parser-adapter-openapi-json-3-0": "^0.69.2", + "@swagger-api/apidom-parser-adapter-openapi-json-3-1": "^0.69.2", + "@swagger-api/apidom-parser-adapter-openapi-yaml-3-0": "^0.69.2", + "@swagger-api/apidom-parser-adapter-openapi-yaml-3-1": "^0.69.2", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^0.69.2" } }, "node_modules/@tootallnate/once": { @@ -17485,9 +17485,9 @@ } }, "@swagger-api/apidom-core": { - "version": "0.69.1", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-core/-/apidom-core-0.69.1.tgz", - "integrity": "sha512-XkGEoIuk0cqsbzv03AdfcDr5suKJnLC+CkZS4rzHjs307RFG1xX722Aj6snUs5hRFQKBH+cdujpspugh7HdNmw==", + "version": "0.69.2", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-core/-/apidom-core-0.69.2.tgz", + "integrity": "sha512-av9vS1SbXxGJvCt4QggrIvS8dr3ZfL6jxrNQGr4cq1wFY/n5ruj0RsXix208c3Zp1Kua3QVOUaJvA+7RdT1VJA==", "requires": { "@babel/runtime-corejs3": "^7.20.7", "@swagger-api/apidom-ast": "^0.69.0", @@ -17500,12 +17500,12 @@ } }, "@swagger-api/apidom-json-pointer": { - "version": "0.69.1", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-json-pointer/-/apidom-json-pointer-0.69.1.tgz", - "integrity": "sha512-bRSSpSdXVQQ0LTmdY/KDZ7XqxwMf1DmgOqStFq71A8UZs1cTAa83qQ7+OAPqz3/qhSVAOE1sIU5fc32j+wqtmg==", + "version": "0.69.2", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-json-pointer/-/apidom-json-pointer-0.69.2.tgz", + "integrity": "sha512-ipu94QNw8ZKWC+pfie5IyIzVImR5N0PANXkUSfFon5L4aMAtggKpZn7aUv/2Cxn51JsCvjZwkXT7PaJ8RddpxA==", "requires": { "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^0.69.1", + "@swagger-api/apidom-core": "^0.69.2", "@types/ramda": "=0.28.23", "ramda": "=0.28.0", "ramda-adjunct": "=3.4.0" @@ -17518,12 +17518,12 @@ "optional": true }, "@swagger-api/apidom-ns-json-schema-draft-4": { - "version": "0.69.1", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-4/-/apidom-ns-json-schema-draft-4-0.69.1.tgz", - "integrity": "sha512-6qkUH0R2C2dxzr5Af1p4N/ccyH34Fnv9CVT4tIPFThmxLI+Ivdzy6BbZ4lT9rzFet1G0mL3hI/pdLNz8Z28Ftw==", + "version": "0.69.2", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-4/-/apidom-ns-json-schema-draft-4-0.69.2.tgz", + "integrity": "sha512-8yB4afGBAX+vN5oNRxZMWWS/2G0Q9VUzlL2AOu0Q70FQkscbjQcsb6QX9LbHFMrwi3MgOgE0ewMncpcwskuoXA==", "requires": { "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^0.69.1", + "@swagger-api/apidom-core": "^0.69.2", "@types/ramda": "=0.28.23", "ramda": "=0.28.0", "ramda-adjunct": "=3.4.0", @@ -17531,13 +17531,13 @@ } }, "@swagger-api/apidom-ns-openapi-3-0": { - "version": "0.69.1", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-3-0/-/apidom-ns-openapi-3-0-0.69.1.tgz", - "integrity": "sha512-WysPvllkS36ONiDWUTfNACkgpoDNibCT8sOGaEv+NTGlUjhZuyI6GMJxw5DcaeRyIV5Tp14vyCCbacGMr+02WQ==", + "version": "0.69.2", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-3-0/-/apidom-ns-openapi-3-0-0.69.2.tgz", + "integrity": "sha512-OroXRC+Q1btrpuQ3+ZbMi9XGYiab3YQMg/Rx1wpszbW5C5IPtaa2/FtMcBGYWR5IIdxi+bAT8itbMBGSCcz/Ew==", "requires": { "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^0.69.1", - "@swagger-api/apidom-ns-json-schema-draft-4": "^0.69.1", + "@swagger-api/apidom-core": "^0.69.2", + "@swagger-api/apidom-ns-json-schema-draft-4": "^0.69.2", "@types/ramda": "=0.28.23", "ramda": "=0.28.0", "ramda-adjunct": "=3.4.0", @@ -17545,13 +17545,13 @@ } }, "@swagger-api/apidom-ns-openapi-3-1": { - "version": "0.69.1", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-3-1/-/apidom-ns-openapi-3-1-0.69.1.tgz", - "integrity": "sha512-chk+UF8soH5TuvabUstPmb1Mx3IP3dUJQ7SDB3tHusg+f2xEo5S7Hx/mrSKJDX2Wp0qFshqpUFSRC8ZUnFgJaQ==", + "version": "0.69.2", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-3-1/-/apidom-ns-openapi-3-1-0.69.2.tgz", + "integrity": "sha512-2yyUmdbvkDnZuOGDduvlp4dtLL/8a1PCR+Ajk9+PR4ZTdbMFtZWcr/knGc33Rtr8eXQwd4NPypFHhTLCEHiGwg==", "requires": { "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^0.69.1", - "@swagger-api/apidom-ns-openapi-3-0": "^0.69.1", + "@swagger-api/apidom-core": "^0.69.2", + "@swagger-api/apidom-ns-openapi-3-0": "^0.69.2", "@types/ramda": "=0.28.23", "ramda": "=0.28.0", "ramda-adjunct": "=3.4.0", @@ -17619,16 +17619,16 @@ "optional": true }, "@swagger-api/apidom-reference": { - "version": "0.69.1", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-reference/-/apidom-reference-0.69.1.tgz", - "integrity": "sha512-P/HD5l9zoQAR6H6IL52Y4SjxBXRCY2kzXa0CH+jKqpNNQxLEPf7penP9T6rH339BvKhATK5LanxDYtQZRCDBdw==", + "version": "0.69.2", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-reference/-/apidom-reference-0.69.2.tgz", + "integrity": "sha512-aJsgtCP71t8a+frS+qn1FW9MqjjK60c3AnB2G3cUXGVwzmEPEkvBFF0LGlPmHftVvzzBvI7AsMC7+HZPM/t7rQ==", "requires": { "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^0.69.1", - "@swagger-api/apidom-json-pointer": "^0.69.1", + "@swagger-api/apidom-core": "^0.69.2", + "@swagger-api/apidom-json-pointer": "^0.69.2", "@swagger-api/apidom-ns-asyncapi-2": "npm:-@0.0.1", - "@swagger-api/apidom-ns-openapi-3-0": "^0.69.1", - "@swagger-api/apidom-ns-openapi-3-1": "^0.69.1", + "@swagger-api/apidom-ns-openapi-3-0": "^0.69.2", + "@swagger-api/apidom-ns-openapi-3-1": "^0.69.2", "@swagger-api/apidom-parser-adapter-api-design-systems-json": "npm:-@0.0.1", "@swagger-api/apidom-parser-adapter-api-design-systems-yaml": "npm:-@0.0.1", "@swagger-api/apidom-parser-adapter-asyncapi-json-2": "npm:-@0.0.1", diff --git a/package.json b/package.json index 1368f9f94..c94cc3da2 100644 --- a/package.json +++ b/package.json @@ -114,10 +114,10 @@ }, "dependencies": { "@babel/runtime-corejs3": "^7.20.13", - "@swagger-api/apidom-core": ">=0.69.1 <1.0.0", - "@swagger-api/apidom-json-pointer": ">=0.69.1 <1.0.0", - "@swagger-api/apidom-ns-openapi-3-1": ">=0.69.1 <1.0.0", - "@swagger-api/apidom-reference": ">=0.69.1 <1.0.0", + "@swagger-api/apidom-core": ">=0.69.2 <1.0.0", + "@swagger-api/apidom-json-pointer": ">=0.69.2 <1.0.0", + "@swagger-api/apidom-ns-openapi-3-1": ">=0.69.2 <1.0.0", + "@swagger-api/apidom-reference": ">=0.69.2 <1.0.0", "cookie": "~0.5.0", "cross-fetch": "^3.1.5", "deepmerge": "~4.3.0", diff --git a/src/resolver/apidom/reference/dereference/strategies/openapi-3-1-swagger-client/visitors/dereference.js b/src/resolver/apidom/reference/dereference/strategies/openapi-3-1-swagger-client/visitors/dereference.js index 63978e45a..7cffaa674 100644 --- a/src/resolver/apidom/reference/dereference/strategies/openapi-3-1-swagger-client/visitors/dereference.js +++ b/src/resolver/apidom/reference/dereference/strategies/openapi-3-1-swagger-client/visitors/dereference.js @@ -64,7 +64,7 @@ const OpenApi3_1SwaggerClientDereferenceVisitor = OpenApi3_1DereferenceVisitor.c methods: { async ReferenceElement(referenceElement, key, parent, path, ancestors) { try { - const [ancestorsLineage, directAncestors] = this.toAncestorLineage(ancestors); + const [ancestorsLineage, directAncestors] = this.toAncestorLineage([...ancestors, parent]); // skip already identified cycled Path Item Objects if (includesClasses(['cycle'], referenceElement.$ref)) { @@ -120,6 +120,24 @@ const OpenApi3_1SwaggerClientDereferenceVisitor = OpenApi3_1DereferenceVisitor.c ); } + if (!this.useCircularStructures) { + const hasCycles = ancestorsLineage.some((ancs) => ancs.has(fragment)); + if (hasCycles) { + if (url.isHttpUrl(retrievalURI) || url.isFileSystemPath(retrievalURI)) { + // make the referencing URL or file system path absolute + const cycledReferenceElement = new ReferenceElement( + { $ref: $refBaseURI }, + referenceElement.meta.clone(), + referenceElement.attributes.clone() + ); + cycledReferenceElement.get('$ref').classes.push('cycle'); + return cycledReferenceElement; + } + // skip processing this schema and all it's child schemas + return false; + } + } + // append referencing schema to ancestors lineage directAncestors.add(referenceElement); @@ -141,24 +159,6 @@ const OpenApi3_1SwaggerClientDereferenceVisitor = OpenApi3_1DereferenceVisitor.c this.indirections.pop(); - if (!this.useCircularStructures) { - const hasCycles = ancestorsLineage.some((ancs) => ancs.has(fragment)); - if (hasCycles) { - if (url.isHttpUrl(retrievalURI) || url.isFileSystemPath(retrievalURI)) { - // make the referencing URL or file system path absolute - const cycledReferenceElement = new ReferenceElement( - { $ref: $refBaseURI }, - referenceElement.meta.clone(), - referenceElement.attributes.clone() - ); - cycledReferenceElement.get('$ref').classes.push('cycle'); - return cycledReferenceElement; - } - // skip processing this schema but traverse all it's child schemas - return false; - } - } - fragment = fragment.clone(); fragment.setMetaProperty('ref-fields', { $ref: referenceElement.$ref?.toValue(), @@ -206,7 +206,7 @@ const OpenApi3_1SwaggerClientDereferenceVisitor = OpenApi3_1DereferenceVisitor.c async PathItemElement(pathItemElement, key, parent, path, ancestors) { try { - const [ancestorsLineage, directAncestors] = this.toAncestorLineage(ancestors); + const [ancestorsLineage, directAncestors] = this.toAncestorLineage([...ancestors, parent]); // ignore PathItemElement without $ref field if (!isStringElement(pathItemElement.$ref)) { @@ -257,6 +257,24 @@ const OpenApi3_1SwaggerClientDereferenceVisitor = OpenApi3_1DereferenceVisitor.c ); } + if (!this.useCircularStructures) { + const hasCycles = ancestorsLineage.some((ancs) => ancs.has(referencedElement)); + if (hasCycles) { + if (url.isHttpUrl(retrievalURI) || url.isFileSystemPath(retrievalURI)) { + // make the referencing URL or file system path absolute + const cycledPathItemElement = new PathItemElement( + { $ref: $refBaseURI }, + pathItemElement.meta.clone(), + pathItemElement.attributes.clone() + ); + cycledPathItemElement.get('$ref').classes.push('cycle'); + return cycledPathItemElement; + } + // skip processing this schema and all it's child schemas + return false; + } + } + // append referencing schema to ancestors lineage directAncestors.add(pathItemElement); @@ -281,24 +299,6 @@ const OpenApi3_1SwaggerClientDereferenceVisitor = OpenApi3_1DereferenceVisitor.c this.indirections.pop(); - if (!this.useCircularStructures) { - const hasCycles = ancestorsLineage.some((ancs) => ancs.has(referencedElement)); - if (hasCycles) { - if (url.isHttpUrl(retrievalURI) || url.isFileSystemPath(retrievalURI)) { - // make the referencing URL or file system path absolute - const cycledPathItemElement = new PathItemElement( - { $ref: $refBaseURI }, - pathItemElement.meta.clone(), - pathItemElement.attributes.clone() - ); - cycledPathItemElement.get('$ref').classes.push('cycle'); - return cycledPathItemElement; - } - // skip processing this schema but traverse all it's child schemas - return false; - } - } - // merge fields from referenced Path Item with referencing one const mergedPathItemElement = new PathItemElement( [...referencedElement.content], @@ -346,7 +346,7 @@ const OpenApi3_1SwaggerClientDereferenceVisitor = OpenApi3_1DereferenceVisitor.c async SchemaElement(referencingElement, key, parent, path, ancestors) { try { - const [ancestorsLineage, directAncestors] = this.toAncestorLineage(ancestors); + const [ancestorsLineage, directAncestors] = this.toAncestorLineage([...ancestors, parent]); // skip current referencing schema as $ref keyword was not defined if (!isStringElement(referencingElement.$ref)) { @@ -444,6 +444,26 @@ const OpenApi3_1SwaggerClientDereferenceVisitor = OpenApi3_1DereferenceVisitor.c ); } + // useCircularStructures option processing + if (!this.useCircularStructures) { + const hasCycles = ancestorsLineage.some((ancs) => ancs.has(referencedElement)); + if (hasCycles) { + if (url.isHttpUrl(retrievalURI) || url.isFileSystemPath(retrievalURI)) { + // make the referencing URL or file system path absolute + const baseURI = url.resolve(retrievalURI, $refBaseURI); + const cycledSchemaElement = new SchemaElement( + { $ref: baseURI }, + referencingElement.meta.clone(), + referencingElement.attributes.clone() + ); + cycledSchemaElement.get('$ref').classes.push('cycle'); + return cycledSchemaElement; + } + // skip processing this schema and all it's child schemas + return false; + } + } + // append referencing schema to ancestors lineage directAncestors.add(referencingElement); @@ -484,26 +504,6 @@ const OpenApi3_1SwaggerClientDereferenceVisitor = OpenApi3_1DereferenceVisitor.c return jsonSchemaBooleanElement; } - // useCircularStructures option processing - if (!this.useCircularStructures) { - const hasCycles = ancestorsLineage.some((ancs) => ancs.has(referencedElement)); - if (hasCycles) { - if (url.isHttpUrl(retrievalURI) || url.isFileSystemPath(retrievalURI)) { - // make the referencing URL or file system path absolute - const baseURI = url.resolve(retrievalURI, $refBaseURI); - const cycledSchemaElement = new SchemaElement( - { $ref: baseURI }, - referencingElement.meta.clone(), - referencingElement.attributes.clone() - ); - cycledSchemaElement.get('$ref').classes.push('cycle'); - return cycledSchemaElement; - } - // skip processing this schema but traverse all it's child schemas - return false; - } - } - // Schema Object - merge keywords from referenced schema with referencing schema const mergedSchemaElement = new SchemaElement( [...referencedElement.content], diff --git a/test/resolver/apidom/reference/dereference/strategies/openapi-3-1-swagger-client/schema-object/__fixtures__/cycle-internal-advanced-circular-structures/dereferenced.json b/test/resolver/apidom/reference/dereference/strategies/openapi-3-1-swagger-client/schema-object/__fixtures__/cycle-internal-advanced-circular-structures/dereferenced.json new file mode 100644 index 000000000..833250df3 --- /dev/null +++ b/test/resolver/apidom/reference/dereference/strategies/openapi-3-1-swagger-client/schema-object/__fixtures__/cycle-internal-advanced-circular-structures/dereferenced.json @@ -0,0 +1,40 @@ +[ + { + "openapi": "3.1.0", + "components": { + "schemas": { + "PlatformMenuTreeNode": { + "properties": { + "children": { + "type": "array", + "items": { + "$ref": "/home/smartbear/root.json#/components/schemas/PlatformMenuTreeNode" + } + }, + "resources": { + "type": "array", + "items": { + "properties": { + "id": { + "type": "integer", + "format": "int64", + "description": "ID" + } + } + } + } + } + }, + "PlatformMenuTreeResourceNode": { + "properties": { + "id": { + "type": "integer", + "format": "int64", + "description": "ID" + } + } + } + } + } + } +] diff --git a/test/resolver/apidom/reference/dereference/strategies/openapi-3-1-swagger-client/schema-object/__fixtures__/cycle-internal-advanced-circular-structures/root.json b/test/resolver/apidom/reference/dereference/strategies/openapi-3-1-swagger-client/schema-object/__fixtures__/cycle-internal-advanced-circular-structures/root.json new file mode 100644 index 000000000..c53d98ac5 --- /dev/null +++ b/test/resolver/apidom/reference/dereference/strategies/openapi-3-1-swagger-client/schema-object/__fixtures__/cycle-internal-advanced-circular-structures/root.json @@ -0,0 +1,32 @@ +{ + "openapi": "3.1.0", + "components": { + "schemas": { + "PlatformMenuTreeNode": { + "properties": { + "children": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PlatformMenuTreeNode" + } + }, + "resources": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PlatformMenuTreeResourceNode" + } + } + } + }, + "PlatformMenuTreeResourceNode": { + "properties": { + "id": { + "type": "integer", + "format": "int64", + "description": "ID" + } + } + } + } + } +} diff --git a/test/resolver/apidom/reference/dereference/strategies/openapi-3-1-swagger-client/schema-object/__fixtures__/cycle-internal-advanced-http-circular-structures/dereferenced.json b/test/resolver/apidom/reference/dereference/strategies/openapi-3-1-swagger-client/schema-object/__fixtures__/cycle-internal-advanced-http-circular-structures/dereferenced.json new file mode 100644 index 000000000..50ff931fc --- /dev/null +++ b/test/resolver/apidom/reference/dereference/strategies/openapi-3-1-swagger-client/schema-object/__fixtures__/cycle-internal-advanced-http-circular-structures/dereferenced.json @@ -0,0 +1,40 @@ +[ + { + "openapi": "3.1.0", + "components": { + "schemas": { + "PlatformMenuTreeNode": { + "properties": { + "children": { + "type": "array", + "items": { + "$ref": "http://localhost:8123/root.json#/components/schemas/PlatformMenuTreeNode" + } + }, + "resources": { + "type": "array", + "items": { + "properties": { + "id": { + "type": "integer", + "format": "int64", + "description": "ID" + } + } + } + } + } + }, + "PlatformMenuTreeResourceNode": { + "properties": { + "id": { + "type": "integer", + "format": "int64", + "description": "ID" + } + } + } + } + } + } +] diff --git a/test/resolver/apidom/reference/dereference/strategies/openapi-3-1-swagger-client/schema-object/__fixtures__/cycle-internal-advanced-http-circular-structures/root.json b/test/resolver/apidom/reference/dereference/strategies/openapi-3-1-swagger-client/schema-object/__fixtures__/cycle-internal-advanced-http-circular-structures/root.json new file mode 100644 index 000000000..c53d98ac5 --- /dev/null +++ b/test/resolver/apidom/reference/dereference/strategies/openapi-3-1-swagger-client/schema-object/__fixtures__/cycle-internal-advanced-http-circular-structures/root.json @@ -0,0 +1,32 @@ +{ + "openapi": "3.1.0", + "components": { + "schemas": { + "PlatformMenuTreeNode": { + "properties": { + "children": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PlatformMenuTreeNode" + } + }, + "resources": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PlatformMenuTreeResourceNode" + } + } + } + }, + "PlatformMenuTreeResourceNode": { + "properties": { + "id": { + "type": "integer", + "format": "int64", + "description": "ID" + } + } + } + } + } +} diff --git a/test/resolver/apidom/reference/dereference/strategies/openapi-3-1-swagger-client/schema-object/__fixtures__/cycle-internal-advanced/root.json b/test/resolver/apidom/reference/dereference/strategies/openapi-3-1-swagger-client/schema-object/__fixtures__/cycle-internal-advanced/root.json new file mode 100644 index 000000000..41d545981 --- /dev/null +++ b/test/resolver/apidom/reference/dereference/strategies/openapi-3-1-swagger-client/schema-object/__fixtures__/cycle-internal-advanced/root.json @@ -0,0 +1,35 @@ +{ + "openapi": "3.1.0", + "components": { + "schemas": { + "PlatformMenuTree": { + "$ref": "#/components/schemas/PlatformMenuTreeNode" + }, + "PlatformMenuTreeNode": { + "properties": { + "children": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PlatformMenuTreeNode" + } + }, + "resources": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PlatformMenuTreeResourceNode" + } + } + } + }, + "PlatformMenuTreeResourceNode": { + "properties": { + "id": { + "type": "integer", + "format": "int64", + "description": "ID" + } + } + } + } + } +} diff --git a/test/resolver/apidom/reference/dereference/strategies/openapi-3-1-swagger-client/schema-object/index.js b/test/resolver/apidom/reference/dereference/strategies/openapi-3-1-swagger-client/schema-object/index.js index 7ad6ea71b..52aa8d637 100644 --- a/test/resolver/apidom/reference/dereference/strategies/openapi-3-1-swagger-client/schema-object/index.js +++ b/test/resolver/apidom/reference/dereference/strategies/openapi-3-1-swagger-client/schema-object/index.js @@ -1,5 +1,5 @@ import path from 'node:path'; -import { toValue } from '@swagger-api/apidom-core'; +import { toValue, toJSON } from '@swagger-api/apidom-core'; import { isSchemaElement, mediaTypes } from '@swagger-api/apidom-ns-openapi-3-1'; import { evaluate } from '@swagger-api/apidom-json-pointer'; import { dereference, resolve } from '@swagger-api/apidom-reference/configuration/empty'; @@ -124,6 +124,7 @@ describe('dereference', () => { }); const expected = globalThis.loadJsonFile(path.join(fixturePath, 'dereferenced.json')); + expect(typeof toJSON(actual)).toBe('string'); expect(toValue(actual)).toEqual(expected); }); @@ -163,6 +164,87 @@ describe('dereference', () => { }); }); + describe('given Schema Objects with advanced internal cycles', () => { + test('should dereference', async () => { + const fixturePath = path.join(rootFixturePath, 'cycle-internal-advanced'); + const rootFilePath = path.join(fixturePath, 'root.json'); + const dereferenced = await dereference(rootFilePath, { + parse: { mediaType: mediaTypes.latest('json') }, + }); + const parent = evaluate( + '/0/components/schemas/PlatformMenuTreeNode/properties/children/items', + dereferenced + ); + const cyclicParent = evaluate( + '/0/components/schemas/PlatformMenuTreeNode/properties/children/items/properties/children/items', + dereferenced + ); + + expect(parent).toStrictEqual(cyclicParent); + }); + + describe('and useCircularStructures=false', () => { + test('should avoid cycles by skipping transclusion', async () => { + const fixturePath = path.join( + rootFixturePath, + 'cycle-internal-advanced-circular-structures' + ); + const rootFilePath = path.join(fixturePath, 'root.json'); + const refSet = await resolve(rootFilePath, { + parse: { mediaType: mediaTypes.latest('json') }, + }); + refSet.refs[0].uri = '/home/smartbear/root.json'; + const actual = await dereference(refSet.refs[0].uri, { + parse: { mediaType: mediaTypes.latest('json') }, + dereference: { + refSet, + strategies: [ + OpenApi3_1SwaggerClientDereferenceStrategy({ useCircularStructures: false }), + ], + }, + }); + const expected = globalThis.loadJsonFile(path.join(fixturePath, 'dereferenced.json')); + + expect(typeof toJSON(actual)).toBe('string'); + expect(toValue(actual)).toEqual(expected); + }); + + describe('and using HTTP protocol', () => { + test('should make JSON Pointer absolute', async () => { + const fixturePath = path.join( + rootFixturePath, + 'cycle-internal-advanced-http-circular-structures' + ); + const dereferenceThunk = async () => { + const httpServer = globalThis.createHTTPServer({ port: 8123, cwd: fixturePath }); + + try { + return toValue( + await dereference('http://localhost:8123/root.json', { + parse: { mediaType: mediaTypes.latest('json') }, + dereference: { + strategies: [ + OpenApi3_1SwaggerClientDereferenceStrategy({ + useCircularStructures: false, + }), + ], + }, + }) + ); + } finally { + await httpServer.terminate(); + } + }; + const expected = globalThis.loadJsonFile( + path.join(fixturePath, 'dereferenced.json') + ); + + await expect(dereferenceThunk()).resolves.toEqual(expected); + }); + }); + }); + }); + describe('given Schema Objects with external cycles', () => { test('should dereference', async () => { const fixturePath = path.join(rootFixturePath, 'cycle-external'); @@ -202,6 +284,7 @@ describe('dereference', () => { }); const expected = globalThis.loadJsonFile(path.join(fixturePath, 'dereferenced.json')); + expect(typeof toJSON(actual)).toBe('string'); expect(toValue(actual)).toEqual(expected); }); }); @@ -544,6 +627,7 @@ describe('dereference', () => { }); const expected = globalThis.loadJsonFile(path.join(fixturePath, 'dereferenced.json')); + expect(typeof toJSON(actual)).toBe('string'); expect(toValue(actual)).toEqual(expected); }); }); @@ -608,6 +692,7 @@ describe('dereference', () => { }); const expected = globalThis.loadJsonFile(path.join(fixturePath, 'dereferenced.json')); + expect(typeof toJSON(actual)).toBe('string'); expect(toValue(actual)).toEqual(expected); }); }); @@ -674,6 +759,7 @@ describe('dereference', () => { }); const expected = globalThis.loadJsonFile(path.join(fixturePath, 'dereferenced.json')); + expect(typeof toJSON(actual)).toBe('string'); expect(toValue(actual)).toEqual(expected); }); }); @@ -763,6 +849,7 @@ describe('dereference', () => { }); const expected = globalThis.loadJsonFile(path.join(fixturePath, 'dereferenced.json')); + expect(typeof toJSON(actual)).toBe('string'); expect(toValue(actual)).toEqual(expected); }); }); @@ -827,6 +914,7 @@ describe('dereference', () => { }); const expected = globalThis.loadJsonFile(path.join(fixturePath, 'dereferenced.json')); + expect(typeof toJSON(actual)).toBe('string'); expect(toValue(actual)).toEqual(expected); }); }); @@ -889,6 +977,7 @@ describe('dereference', () => { }); const expected = globalThis.loadJsonFile(path.join(fixturePath, 'dereferenced.json')); + expect(typeof toJSON(actual)).toBe('string'); expect(toValue(actual)).toEqual(expected); }); }); @@ -989,6 +1078,7 @@ describe('dereference', () => { }); const expected = globalThis.loadJsonFile(path.join(fixturePath, 'dereferenced.json')); + expect(typeof toJSON(actual)).toBe('string'); expect(toValue(actual)).toEqual(expected); }); }); @@ -1078,6 +1168,7 @@ describe('dereference', () => { }); const expected = globalThis.loadJsonFile(path.join(fixturePath, 'dereferenced.json')); + expect(typeof toJSON(actual)).toBe('string'); expect(toValue(actual)).toEqual(expected); }); }); @@ -1224,6 +1315,7 @@ describe('dereference', () => { }); const expected = globalThis.loadJsonFile(path.join(fixturePath, 'dereferenced.json')); + expect(typeof toJSON(actual)).toBe('string'); expect(toValue(actual)).toEqual(expected); }); }); @@ -1290,6 +1382,7 @@ describe('dereference', () => { }); const expected = globalThis.loadJsonFile(path.join(fixturePath, 'dereferenced.json')); + expect(typeof toJSON(actual)).toBe('string'); expect(toValue(actual)).toEqual(expected); }); }); @@ -1637,6 +1730,7 @@ describe('dereference', () => { }); const expected = globalThis.loadJsonFile(path.join(fixturePath, 'dereferenced.json')); + expect(typeof toJSON(actual)).toBe('string'); expect(toValue(actual)).toEqual(expected); }); @@ -1705,6 +1799,7 @@ describe('dereference', () => { }); const expected = globalThis.loadJsonFile(path.join(fixturePath, 'dereferenced.json')); + expect(typeof toJSON(actual)).toBe('string'); expect(toValue(actual)).toEqual(expected); });