From 50c257280f1f71e830d6a75437d19b25cc0fc9dd Mon Sep 17 00:00:00 2001 From: Karl von Randow Date: Fri, 12 Mar 2021 11:16:28 +1300 Subject: [PATCH 1/3] Convert relative refs when resolving an external schema MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Possibly corrects #200. This however causes tests to fail as they’re not expecting the references to be resolved. I’m not sure yet how to fix the tests. I thought I’d wait to see if the fix is valid! --- lib/resolve-external.js | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/lib/resolve-external.js b/lib/resolve-external.js index c7238fbd..222d238d 100644 --- a/lib/resolve-external.js +++ b/lib/resolve-external.js @@ -51,7 +51,7 @@ function resolveExternal (parser, options) { * If any of the JSON references point to files that contain additional JSON references, * then the corresponding promise will internally reference an array of promises. */ -function crawl (obj, path, $refs, options) { +function crawl (obj, path, $refs, options, external) { let promises = []; if (obj && typeof obj === "object" && !ArrayBuffer.isView(obj)) { @@ -59,16 +59,17 @@ function crawl (obj, path, $refs, options) { promises.push(resolve$Ref(obj, path, $refs, options)); } else { + if (external && $Ref.is$Ref(obj)) { + /* Correct the reference in the external document so we can resolve it */ + let withoutHash = url.stripHash(path); + obj.$ref = withoutHash + obj.$ref; + } + for (let key of Object.keys(obj)) { let keyPath = Pointer.join(path, key); let value = obj[key]; - if ($Ref.isExternal$Ref(value)) { - promises.push(resolve$Ref(value, keyPath, $refs, options)); - } - else { - promises = promises.concat(crawl(value, keyPath, $refs, options)); - } + promises = promises.concat(crawl(value, keyPath, $refs, options, external)); } } } @@ -107,7 +108,7 @@ async function resolve$Ref ($ref, path, $refs, options) { // Crawl the parsed value // console.log('Resolving $ref pointers in %s', withoutHash); - let promises = crawl(result, withoutHash + "#", $refs, options); + let promises = crawl(result, withoutHash + "#", $refs, options, true); return Promise.all(promises); } From bacf842ef9dbb4ae92e74a0e999fc7a98e4dadd3 Mon Sep 17 00:00:00 2001 From: Karl von Randow Date: Wed, 24 Mar 2021 18:58:08 +1300 Subject: [PATCH 2/3] Document external argument on crawl function --- lib/resolve-external.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/resolve-external.js b/lib/resolve-external.js index 222d238d..293241f9 100644 --- a/lib/resolve-external.js +++ b/lib/resolve-external.js @@ -44,6 +44,7 @@ function resolveExternal (parser, options) { * @param {string} path - The full path of `obj`, possibly with a JSON Pointer in the hash * @param {$Refs} $refs * @param {$RefParserOptions} options + * @param {boolean} external - Whether `obj` was found in an external document. * * @returns {Promise[]} * Returns an array of promises. There will be one promise for each JSON reference in `obj`. @@ -61,7 +62,7 @@ function crawl (obj, path, $refs, options, external) { else { if (external && $Ref.is$Ref(obj)) { /* Correct the reference in the external document so we can resolve it */ - let withoutHash = url.stripHash(path); + const withoutHash = url.stripHash(path); obj.$ref = withoutHash + obj.$ref; } From 770a0a5682897d5fb361a0e458ba4bcbd99d0add Mon Sep 17 00:00:00 2001 From: Karl von Randow Date: Wed, 7 Apr 2021 14:46:36 +1200 Subject: [PATCH 3/3] Correct transitive external relative refs --- lib/bundle.js | 2 +- lib/resolve-external.js | 6 ++++++ lib/util/url.js | 21 +++++++++++++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/lib/bundle.js b/lib/bundle.js index d5578bf1..179d7d7a 100644 --- a/lib/bundle.js +++ b/lib/bundle.js @@ -95,7 +95,7 @@ function crawl (parent, key, path, pathFromRoot, indirections, inventory, $refs, */ function inventory$Ref ($refParent, $refKey, path, pathFromRoot, indirections, inventory, $refs, options) { let $ref = $refKey === null ? $refParent : $refParent[$refKey]; - let $refPath = url.resolve(path, $ref.$ref); + let $refPath = url.resolve(pathFromRoot, $ref.$ref); let pointer = $refs._resolve($refPath, pathFromRoot, options); if (pointer === null) { return; diff --git a/lib/resolve-external.js b/lib/resolve-external.js index 293241f9..01f1ccd6 100644 --- a/lib/resolve-external.js +++ b/lib/resolve-external.js @@ -96,6 +96,12 @@ async function resolve$Ref ($ref, path, $refs, options) { let resolvedPath = url.resolve(path, $ref.$ref); let withoutHash = url.stripHash(resolvedPath); + /* Correct the $ref to use a path relative to the root, so that $Refs._resolve can resolve it, + otherwise transitive relative external references will be incorrect if the second external + relative ref doesn't work relative to the root document. + */ + $ref.$ref = url.relative($refs._root$Ref.path, resolvedPath); + // Do we already have this $ref? $ref = $refs._$refs[withoutHash]; if ($ref) { diff --git a/lib/util/url.js b/lib/util/url.js index 81b3a0d7..5e0a62a6 100644 --- a/lib/util/url.js +++ b/lib/util/url.js @@ -1,5 +1,7 @@ "use strict"; +const pathModule = require("path"); + let isWindows = /^win/.test(process.platform), forwardSlashPattern = /\//g, protocolPattern = /^(\w{2,}):\/\//i, @@ -269,3 +271,22 @@ exports.safePointerToPath = function safePointerToPath (pointer) { .replace(jsonPointerTilde, "~"); }); }; + +/** + * Like path.relative(from, to) but for URLs. It will return a relative + * URL if it can, otherwise an absolute URL is returned. + * @param {string} from + * @param {string} to + * @returns {string} + */ +exports.relative = function relative (from, to) { + if (!exports.isFileSystemPath(from) || !exports.isFileSystemPath(to)) { + return exports.resolve(from, to); + } + + const fromDir = pathModule.dirname(exports.stripHash(from)); + const toPath = exports.stripHash(to); + + const result = pathModule.relative(fromDir, toPath); + return result + exports.getHash(to); +};