Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

schemaPath in ajv.errors is often incorrect for errors inside $ref'd schema #512

Open
jboavida opened this issue Jun 6, 2017 · 7 comments

Comments

@jboavida
Copy link
Contributor

jboavida commented Jun 6, 2017

What version of Ajv are you using? Does the issue happen if you use the latest version?

4.11.8 and 5.1.5. Yes.

Ajv options object

Only { v5: true } is needed (and only for version 4), in order to have $ref.

JSON Schema

Schemas with a $ref that doesn't contain # or that ends in #/.

Sample data

Data that makes validation fail because of the $ref'd schema.

Your code

Minimal example:

var options = { v5: true };
var ajv = require('ajv')(options);
ajv.addSchema([
  { id: '/a', type: 'integer' },
  { id: '/b', items: { $ref: '/a' } }
]);
var data = [true]; // is not valid

Validation result, data AFTER validation, error messages

ajv.validate('/b', data);  // false
ajv.errors[0].schemaPath;  // '/a/type', but should be '/a#/type'

What results did you expect?

For that example, the correct schema path is '/a#/type'.

A longer example illustrates what happens, and points to a workaround and maybe a correct fix:

ajv = require('ajv')({ v5: true });
ajv.addSchema([
  { id: '/a', type: 'integer' },
  { id: '/b', items: { $ref: '/a' } },
  { id: '/c', items: { $ref: '/a#' } },
  { id: '/d', items: { $ref: '/a#/' } }
]);
ajv.validate('/b', [4]);  // true
ajv.validate('/c', [4]);  // true
ajv.validate('/d', [4]);  // true
ajv.validate('/b', [true]);  // false
ajv.errors[0].schemaPath;    // '/a/type'
ajv.validate('/c', [true]);  // false
ajv.errors[0].schemaPath;    // '/a#/type'
ajv.validate('/d', [true]);  // false
ajv.errors[0].schemaPath;    // '/a#//type'

A workaround consists of including an empty fragment (as in { "$ref": "/a#" }) when writing the schema. It is not a normalized id, but apparently that doesn't hurt anything.

As to fixing it...

I think this is controlled here https://github.com/epoberezkin/ajv/blob/master/lib/dot/ref.jst#L44, but I'm not sure it's the only place. If this guess is correct, then the fix could be as simple as replacing that line with $it.errSchemaPath = $schema.indexOf('#') != -1 ? $schema : $schema + '#'.

That doesn't address the #/ case in schema "/d". As far as I can tell, https://github.com/epoberezkin/ajv/blob/master/lib/dot/ref.jst#L26 calls https://github.com/epoberezkin/ajv/blob/fde7030a19833273b08e8de879cb2bf149adaf2f/lib/compile/index.js#L170, which in turn calls https://github.com/epoberezkin/ajv/blob/master/lib/compile/resolve.js#L225, which does eliminate terminal # or #/. (For example, it.resolve.url('/b', '/a#') == '/a' and it.resolve.url('/b', '/a#/') == '/a', while it.resolve.url('/b', '/a#something') == '/a#something'.) Maybe there is some practical way of taking advantage of getting the normalized schema id from the $refVal in L26? (Normalizing it again seems wasteful...)

Are you going to resolve the issue?

It would be cool, but I'm not at all certain I can understand all the ramifications. Specifically, I can't figure what should happen in the non-inlined case (L51 onward), and I can't follow the workings of the refVals (to address also the case with #/ ending) nor whether the change would have negative impact elsewhere. But maybe with some advice I can navigate it?

@epoberezkin
Copy link
Member

@jboavida well spotted. It's a known issue (#273) and it's a tricky one indeed. I should've written down the reasons I decided to leave it as is, there were some complications around it. I'll look into it.

@aapoalas
Copy link

I'm getting a similar problem but without any '#' or multiple schemas. I've a fairly large amount of definitions, some of them cross-referencing each other. One particular instance is a case where I have an object that can be one of two referenced definitions.

One of these definitions ('function') requires two properties, "func" and "params", while the other ('connection') requires "location" and "type".
The object being validated has only "params" defined, ie. it is lacking the "func" property. But as this is "oneOf" situation, I get errors from both. I get "missingParameter: 'location'" with schemaPath "#/definitions/connection/required" from 'connection but for the equivalent 'function' while I do get "missingParameter: 'func'", the schemaPath is "#/required"

Any further information that you need, I'll be happy to provide to the best of my abilities.

@epoberezkin
Copy link
Member

@aapoalas thank you. Some simplified example of your issue would be helpful too, in runkit.com if possible

@aapoalas
Copy link

Horrah, I actually managed to reproduce this! I was kind of expecting that to not happen with a simpler example but here it is: https://runkit.com/aapoalas/5941134ab79c0b00117f3137

The schema itself is still not too simple, about 60 lines, but the gist of it should be pretty understandable.

If you have any further questions, just ask.

@Mythili007
Copy link

Mythili007 commented Jan 2, 2019

I am trying to use the schema as below and getting the error as "can't resolve reference device from id #"

schema:

"device" : {
        "id" : "device",
        "type": "object",
        "additionalProperties": false,
        "properties": {
            "deviceId": {
                "type": 'string',
                "maxLength": 50
            }
      }
},
"deviceData" : {
        "id" : "deviceData",
        "type": "object",
        "additionalProperties": false,
        "properties": {
            "deviceData": {
                "type": 'object',
                "items": {
                    "$ref": "device"
                }
            }
        }
    }

Could you please tell me what part I am missing here?

@epoberezkin
Copy link
Member

@Mythili007 it’s unrelated to this issue, you reference is incorrect. You may need #/device - but it’s not clear from a partial sample. Please refer to the docs or JSON schema spec or submit a question to stackoverflow.

@jcttrll
Copy link

jcttrll commented Mar 11, 2020

I ran into this today with version 6.12.0, and have a much shorter, simpler RunKit example: https://runkit.com/5e68dad1e356eb0014ed2786/5e68dae0e356eb0014ed27af

  • ajv.errors[0] has a schemaPath of #/required, which should be #/definitions/A/required.
  • ajv.errors[1] has a correct schemaPath of #/definitions/B/required.

Removing the $ref in A (commenting out line 12) results in the correct schemaPath in ajv.errors[0].

@epoberezkin epoberezkin added this to the 7.0.0 milestone Sep 15, 2020
@epoberezkin epoberezkin modified the milestones: 7.0.0, v9+ Feb 10, 2021
smbea added a commit to camunda/element-templates-json-schema that referenced this issue Mar 1, 2023
smbea added a commit to camunda/element-templates-json-schema that referenced this issue Mar 1, 2023
jirutka added a commit to jirutka/ajv-cli that referenced this issue Jun 15, 2024
jirutka added a commit to jirutka/ajv-cli that referenced this issue Jun 15, 2024
jirutka added a commit to jirutka/ajv-cli that referenced this issue Jun 15, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

No branches or pull requests

5 participants