json-schema-ref-parser: Fails to resolve correct path/filename for extended $ref

I recently ran into an issue testing some of my more complex schema documents where combination schema $refs cannot be resolved properly if they are part of an “extended” $ref. It is a little hard to explain the case, but I think I have found a simple enough schema that causes the problem to occur.

// base.json
{
  "type": "object",
  "properties": {
    "some_value": { "$ref": "defs.json#/definitions/astring" }
  }
}

//

{
  "definitions": {
    "astring": {
      "description": "astring",
      "$ref": "defs2.json#/definitions/bstring"
    }
  }
}


// defs2.json
{
  "definitions": {
    "bstring": {
      "oneOf": [
        {
          "$ref": "#/definitions/cstring"
        },
        {
          "$ref": "#/definitions/dstring"
        }
      ]
    },
    "cstring": {
      "type": "string"
    },
    "dstring": {
      "type": "number"
    }
  }
}

The error that I see when trying to resolve this looks like: Error while processing route: dynamic-form Token "cstring" does not exist. MissingPointerError: Token "cstring" does not exist.

This only seems to happen when the intermediate schema object is an “extended” ref. For example, if we remove the description from astring then we are able to dereference without issue. From doing some debugging it looks to me like our issue is that when resolving cstring, the path we resolve is defs.json#/definitions/cstring instead of looking in defs2.json. I think this is because when we resolve extended $refs, we explicitly do not update the pointer’s path.

For reference: https://github.com/APIDevTools/json-schema-ref-parser/blob/13b00923fb279cfcb2a72e0906f3eadc13199d08/lib/pointer.js#L240-L245

I would like to propose a fix, but feel ill-equipped to do so without some guidance. It does seem like updating the path in the above code fixes my specific issue, but clearly that decision was made for a reason so I’d rather not step on any toes without understanding the intent. Any thoughts would be greatly appreciated!

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Comments: 19 (2 by maintainers)

Commits related to this issue

Most upvoted comments

@y0n3r this isn’t a simple thing, and lets not just stamp our feet and talk about being disappointed. All time spent on this is entirely voluntary, and I already run a reforestation charity with about 40 hours of my free time each week, so sometimes issues dont get seen to as fast as we’d all like.

@karlvr thank you for submitting a pull request, I’ll talk to you over there about getting it moving.

Everyone else, if you’re seeing this issue, please give https://www.npmjs.com/package/@openapi-generator-plus/json-schema-ref-parser a try and see if it fixes that issue. We’ll get the changes merged upstream ASAP.

@karlvr just got back to replying on this. I actually realized my problem is elsewhere (at least for the case that I was testing). Though I’ll try to give your solution a whirl and see how it goes and get back with results for future reference

This feels like the same problem that I’ve had, but I’m not sure what an extended ref is!

The problem I’ve had is that when it comes time to resolve (using your example):

    "bstring": {
      "oneOf": [
        {
          "$ref": "#/definitions/cstring"
        },
        {
          "$ref": "#/definitions/dstring"
        }
      ]
    },

… I’ve lost the context that these $refs are in defs2.json and it’s just looking for #/definitions/cstring in my main document.

So I’ve patched resolve-external.js to “correct” refs as we crawl the external document so that they reference the external document.

I’m still testing this myself, but it seems promising. Here’s the patch if it sounds like it might address this issue for you.

diff --git a/node_modules/@apidevtools/json-schema-ref-parser/lib/resolve-external.js b/node_modules/@apidevtools/json-schema-ref-parser/lib/resolve-external.js
index c7238fb..d1f1824 100644
--- a/node_modules/@apidevtools/json-schema-ref-parser/lib/resolve-external.js
+++ b/node_modules/@apidevtools/json-schema-ref-parser/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);
   }