ajv-validator/ajv-merge-patch

$ref errors with ajv 6

Closed this issue · 10 comments

Updating ajv-merge-patch devDependency to ajv@6.5.1 produces the following test failures:

  1) async schema loading "before each" hook for "should load missing schemas":
     can't resolve reference http://json-schema.org/draft-06/schema# from id #


  2) errors missing $ref "before each" hook for "should throw exception if cannot resolve $ref":
     can't resolve reference http://json-schema.org/draft-06/schema# from id #


  3) keyword $merge "before each" hook for "should extend schema defined in $merge":
     can't resolve reference http://json-schema.org/draft-06/schema# from id #


  4) keyword $patch "before each" hook for "should extend schema defined in $patch":
     can't resolve reference http://json-schema.org/draft-06/schema# from id #

I ran into these errors trying to use relative $ref as the source in my schema.

I stupidly forgot to update the tests to add the draft-06 meta schema before capturing the error.

With this patch:

diff --git a/spec/merge.spec.js b/spec/merge.spec.js
index c9a573b..6b3aea8 100644
--- a/spec/merge.spec.js
+++ b/spec/merge.spec.js
@@ -10,6 +10,11 @@ describe('keyword $merge', function() {

   beforeEach(function() {
     ajvInstances = [ new Ajv, new Ajv ];
+    ajvInstances.forEach(
+      function (ajv) {
+        ajv.addMetaSchema(require('ajv/lib/refs/json-schema-draft-06.json'));
+      }
+    );
     addKeywords(ajvInstances[0]);
     addMerge(ajvInstances[1]);
   });
diff --git a/spec/patch.spec.js b/spec/patch.spec.js
index 11ea424..42c938b 100644
--- a/spec/patch.spec.js
+++ b/spec/patch.spec.js
@@ -10,6 +10,11 @@ describe('keyword $patch', function() {

   beforeEach(function() {
     ajvInstances = [ new Ajv, new Ajv ];
+    ajvInstances.forEach(
+      function (ajv) {
+        ajv.addMetaSchema(require('ajv/lib/refs/json-schema-draft-06.json'));
+      }
+    );
     addKeywords(ajvInstances[0]);
     addPatch(ajvInstances[1]);
   });

The tests failures related to my issue are:


  3) keyword $merge should extend schema defined in $ref:
     Error: schema with key or id "" already exists
      at checkUnique (node_modules/ajv/lib/ajv.js:478:11)
      at Ajv.addSchema (node_modules/ajv/lib/ajv.js:135:3)
      at testMerge (spec/merge.spec.js:55:11)
      at Array.forEach (<anonymous>)
      at Context.<anonymous> (spec/merge.spec.js:45:18)

  4) keyword $merge should extend schema defined with relative $ref:
     can't resolve reference #/definitions/source from id #


  5) keyword $merge should extend schema with patch defined with relative $ref:
     can't resolve reference #/definitions/patch from id #


  6) keyword $patch should extend schema defined in $ref:
     Error: schema with key or id "" already exists
      at checkUnique (node_modules/ajv/lib/ajv.js:478:11)
      at Ajv.addSchema (node_modules/ajv/lib/ajv.js:135:3)
      at testPatch (spec/patch.spec.js:55:11)
      at Array.forEach (<anonymous>)
      at Context.<anonymous> (spec/patch.spec.js:45:18)

  7) keyword $patch should extend schema defined with relative $ref:
     can't resolve reference #/definitions/source from id #

I'm wading out of my depth here, but I found that when ajv.getSchema is called by the custom keyword macro, the root schema is undefined. If I force resolveSchema to fall into the if (refPath != baseId) block, the unresolved reference errors no longer occur, leaving only two failures:

  3) keyword $merge should extend schema defined in $ref:
     Error: schema with key or id "" already exists
      at checkUnique (node_modules/ajv/lib/ajv.js:478:11)
      at Ajv.addSchema (node_modules/ajv/lib/ajv.js:135:3)
      at testMerge (spec/merge.spec.js:55:11)
      at Array.forEach (<anonymous>)
      at Context.<anonymous> (spec/merge.spec.js:45:18)

  4) keyword $patch should extend schema defined in $ref:
     Error: schema with key or id "" already exists
      at checkUnique (node_modules/ajv/lib/ajv.js:478:11)
      at Ajv.addSchema (node_modules/ajv/lib/ajv.js:135:3)
      at testPatch (spec/patch.spec.js:55:11)
      at Array.forEach (<anonymous>)
      at Context.<anonymous> (spec/patch.spec.js:45:18)

@epoberezkin any ideas? I could work on a PR to fix this, but I'm not sure how to go about it yet. I'll keep digging while I await a response...

I found that changing id to $id fixes the test cases in this library when used with ajv >= 6. Is there some reason why an id is required for internal refs?

I think I've figured this out - trying to resolve a $ref in a macro custom keyword did not work, presumably b/c the root schema is undergoing compilation when the macro is evaluated.

The following patch to ajv fixes the issue:

diff --git a/lib/compile/resolve.js b/lib/compile/resolve.js
index cdd1a59..1f2a786 100644
--- a/lib/compile/resolve.js
+++ b/lib/compile/resolve.js
@@ -70,7 +70,7 @@ function resolveSchema(root, ref) {
   var p = URI.parse(ref)
     , refPath = _getFullPath(p)
     , baseId = getFullPath(this._getId(root.schema));
-  if (refPath !== baseId) {
+  if (!(root instanceof SchemaObject) || refPath !== baseId) {
     var id = normalizeId(refPath);
     var refVal = this._refs[id];
     if (typeof refVal == 'string') {

I think I can write a test case for this in ajv and open a PR there to resolve the issue.

I’ll need to catch up with it :)
I will review it over weekend.

I’d say we need to split these 2 issues. Firstly, it looks like this library wasn’t updated for draft-07/ajv v6. Could you please make a PR to address this here? That will be a major release of this package.

Secondly, as I understood, the issue in this library (after this update) is a generic one related to macro keywords (that’s what I need to catch up with). It would definitely help to have the simplest possible failing test case in ajv, ideally without this library, that would demonstrate this issue.

Thank you @jstewmon

@epoberezkin thanks for taking a look.

I submitted a PR to ajv with my fix and a minimal test case.

I submitted a PR here to update the peer dep to >= v6.0.0.

Thank you

It's merged