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

Include property name in error object #255

Closed
reharik opened this issue Aug 1, 2016 · 15 comments
Closed

Include property name in error object #255

reharik opened this issue Aug 1, 2016 · 15 comments

Comments

@reharik
Copy link

reharik commented Aug 1, 2016

Hi, I'm passing in a schema and getting a collection of errors back. However, I need to know the property that is causing the error. In the error object, there is "dataPath" however, most of the time that value is "".
When it does show it, it is in the path notation as explained in the docs, and I would still have to parse it to get the name, but more importantly, most of the time it's blank. If it's a required field error I can dig into the "params" and get missing field, but if it's a format error then I'd have to have different logic. I suspect I'm doing something wrong, so I'm include some data.

this is what the out put of the errors array looks like, and it's in verbose, sorry trying to be thorough.

[
    {
        "keyword": "format",
        "dataPath": ".startTime",
        "schemaPath": "#/properties/startTime/format",
        "params": {
            "format": "time"
        },
        "message": "should match format \"time\"",
        "schema": "time",
        "parentSchema": {
            "type": "string",
            "format": "time"
        },
        "data": ""
    },
    {
        "keyword": "required",
        "dataPath": "",
        "schemaPath": "#/required",
        "params": {
            "missingProperty": "endTime"
        },
        "message": "should have required property 'endTime'",
        "schema": {
            "id": {
                "type": "string",
                "format": "uuid"
            },
            "startTime": {
                "type": "string",
                "format": "time"
            },
            "endTime": {
                "type": "string",
                "format": "time"
            },
            "display": {
                "type": "string"
            },
            "color": {
                "description": "color to display tasks",
                "type": "string"
            }
        },
        "parentSchema": {
            "title": "Example Schema",
            "type": "object",
            "properties": {
                "id": {
                    "type": "string",
                    "format": "uuid"
                },
                "startTime": {
                    "type": "string",
                    "format": "time"
                },
                "endTime": {
                    "type": "string",
                    "format": "time"
                },
                "display": {
                    "type": "string"
                },
                "color": {
                    "description": "color to display tasks",
                    "type": "string"
                }
            },
            "required": [
                "startTime",
                "endTime",
                "display",
                "color"
            ]
        },
        "data": {
            "startTime": ""
        }
    },
    {
        "keyword": "required",
        "dataPath": "",
        "schemaPath": "#/required",
        "params": {
            "missingProperty": "display"
        },
        "message": "should have required property 'display'",
        "schema": {
            "id": {
                "type": "string",
                "format": "uuid"
            },
            "startTime": {
                "type": "string",
                "format": "time"
            },
            "endTime": {
                "type": "string",
                "format": "time"
            },
            "display": {
                "type": "string"
            },
            "color": {
                "description": "color to display tasks",
                "type": "string"
            }
        },
        "parentSchema": {
            "title": "Example Schema",
            "type": "object",
            "properties": {
                "id": {
                    "type": "string",
                    "format": "uuid"
                },
                "startTime": {
                    "type": "string",
                    "format": "time"
                },
                "endTime": {
                    "type": "string",
                    "format": "time"
                },
                "display": {
                    "type": "string"
                },
                "color": {
                    "description": "color to display tasks",
                    "type": "string"
                }
            },
            "required": [
                "startTime",
                "endTime",
                "display",
                "color"
            ]
        },
        "data": {
            "startTime": ""
        }
    },
    {
        "keyword": "required",
        "dataPath": "",
        "schemaPath": "#/required",
        "params": {
            "missingProperty": "color"
        },
        "message": "should have required property 'color'",
        "schema": {
            "id": {
                "type": "string",
                "format": "uuid"
            },
            "startTime": {
                "type": "string",
                "format": "time"
            },
            "endTime": {
                "type": "string",
                "format": "time"
            },
            "display": {
                "type": "string"
            },
            "color": {
                "description": "color to display tasks",
                "type": "string"
            }
        },
        "parentSchema": {
            "title": "Example Schema",
            "type": "object",
            "properties": {
                "id": {
                    "type": "string",
                    "format": "uuid"
                },
                "startTime": {
                    "type": "string",
                    "format": "time"
                },
                "endTime": {
                    "type": "string",
                    "format": "time"
                },
                "display": {
                    "type": "string"
                },
                "color": {
                    "description": "color to display tasks",
                    "type": "string"
                }
            },
            "required": [
                "startTime",
                "endTime",
                "display",
                "color"
            ]
        },
        "data": {
            "startTime": ""
        }
    }
]

here is my json.schema

{
  "title": "Example Schema",
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "format": "uuid"
    },
    "startTime": {
      "type": "string",
      "format": "time"
    },
    "endTime": {
      "type": "string",
      "format": "time"
    },
    "display": {
      "type": "string"
    },
    "color": {
      "description": "color to display tasks",
      "type": "string"
    }
  },
  "required": [
    "startTime",
    "endTime",
    "display",
    "color"
  ]
}
@epoberezkin
Copy link
Member

Possibly, duplicate of #93.
required keyword is applied to the parent object, so dataPath points to it; if it's a root object it will be an empty string. Same for additionalProperties and dependencies. As you can see from your errors object dataPath points to the same value that is in data property.
See this comment: #93 (comment)
Keywords applied on the property level (i.e. those used in subschemas inside properties keyword, e.g. type, format, etc.) always have property name as part of dataPath, but it can be in JavaScript index format in case it is not a valid identifier and in some other cases. It you need to parse out the property name in can be easier if you use jsonPointers option - there is also escaping for symbols "/", "~" etc., but at least no index notation.

@reharik
Copy link
Author

reharik commented Aug 1, 2016

So this is a good explanation of what is happening, but it doesn't really address the issue that, as a property level validator, this is very difficult to use. I can try and dig in and create a pull request if you'd be open to adding a property value to the error object that would provide a consistent ( between different types of errors ) user experience.
Thanks,
r

@epoberezkin
Copy link
Member

What I was trying to explain is that you mix two different concepts here - the property of the data object in which the error happens (e.g. when you use keywords type, minimum etc.) and the property to which the error is related (when you use required, additionalProperties, dependencies) - in which case the error happens on the object level, not on the property level.

dataPath points to the point in the object where the error happens.

property related to error is always available in error.params.

I don't think we should add one more property in error object if it doesn't add any additional information. I think it should be in the application code that processes the errors... I will think about it.

In any case, the structure of error objects for all keywords is documented and it won't change, not without major version change, so you can rely on it in the code that processes errors.

@reharik
Copy link
Author

reharik commented Aug 2, 2016

So if I understand correctly there are basically only two paths. One is
for the required rules, and the other is for the more specific type, format
etc rules. You feel that the client can derive what the property is and
that is enough, and if it turns out to be pretty straightforward, then
that's cool. I feel that the client should be presented with an easily
consumable ( no need for derived or constructed data ) payload, but that is
just an opinion.
I think perhaps my use case is not a frequent use of the product. I'm
trying to use json-spec and ajv to validate my data in as many places as I
can, e.g. in an api middleware on the frontend and as client side
validation on a property by property level. It is the latter that I guess
is not that common a use case and thus what I'm asking for is not that
common.
In any case, thanks for engaging me and explaining how I can get what I
need.
R

On Tue, Aug 2, 2016 at 8:34 AM, Evgeny Poberezkin notifications@github.com
wrote:

What I was trying to explain is that you mix two different concepts here -
the property of the data object in which the error happens (e.g. when you
use keywords type, minimum etc.) and the property to which the error is
related (when you use required, additionalProperties, dependencies) - in
which case the error happens on the object level, not on the property level.

dataPath points to the point in the object where the error happens.

property related to error is always available in error.params.

I don't think we should add one more property in error object if it
doesn't add any additional information. I think it should be in the
application code that processes the errors... I will think about it.

In any case, the structure of error objects for all keywords is documented
and it won't change, not without major version change, so you can rely on
it in the code that processes errors.


You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
#255 (comment),
or mute the thread
https://github.com/notifications/unsubscribe-auth/AA12zkIck3UGr-xhB9IhtImO46kU3niiks5qb0degaJpZM4JZKnO
.

@epoberezkin
Copy link
Member

So if I understand correctly there are basically only two paths. One is
for the required rules, and the other is for the more specific type, format
etc rules.

Yes, the former pointing to the parent object.
You can use the option errorDataPath: 'property' (see Options) that would make dataPath always pointing to the property in question (for keywords required, additionalProperties and dependencies).

What I don't like about this option is that data property of the error object (with verbose: true option) would still point to the parent object becoming inconsistent with dataPath.

You feel that the client can derive what the property is and
that is enough, and if it turns out to be pretty straightforward, then
that's cool.

Kind of. I am fighting the "feature creep" here a little bit... Also I think duplicating the same information in multiple places should be at least considered carefully. As I wrote, I need to think about it...

I feel that the client should be presented with an easily
consumable ( no need for derived or constructed data ) payload, but that is
just an opinion.

That I agree with. I think it is (at least partially) addressed with message property of errors and also .errorsText method - both include messages that refer to the property in question. Also #100 when implemented would allow to include custom error message generation in the schema, that would most likely address your issue as well. I still hope somebody would implement it, there is even a small bounty for it...

@epoberezkin epoberezkin changed the title can't get property name that is validated Include property name in error object Aug 2, 2016
@reharik
Copy link
Author

reharik commented Aug 2, 2016

Hi, yes #100 does look interesting. I'm so over booked. But if you can give me a few pointers as to where in the code I should be looking and/or where you would like to put it as well as any caveats or concerns you may have I can take a look.
It seems like it might not be too difficult to implement, so any head start you could give me would be great.
Thanks,
R

@reharik reharik closed this as completed Aug 2, 2016
@epoberezkin
Copy link
Member

Let's keep it open for now...

Re #100, you'll need to see how custom keywords that generate inline code are implemented and follow more or less the same approach. Other custom keyword types won't allow you to manipulate the array of errors. It's not too difficult, but you'll need to figure out a few things about how ajv works internally.

@epoberezkin epoberezkin reopened this Aug 3, 2016
@braco
Copy link

braco commented Aug 9, 2016

@epoberezkin: why isnt { errorDataPath: 'property' } default, out of curiosity? The error format for missing fields was pretty baffling before I discovered that. The point of validation seems like it would almost always be directed at the object being validated, not the schema.

The fact that required is a parental array { required: [field] } and not a part of the property { field: { required: true } } seems like an implementation detail in JSON schema, more than anything.

PS, great job on the ajv in general!

@epoberezkin
Copy link
Member

epoberezkin commented Aug 10, 2016

Because the error with required keyword happens on the parent object level, rather than on the property level - property doesn't exist and it never gets validated, object does.

Also, error has properties data (actual reference, in verbose mode) and dataPath (JavaScript property or JSONPath) - by default they point to the same data.

See above - it's explained there too.

@epoberezkin
Copy link
Member

epoberezkin commented Aug 14, 2016

@reharik I was about to implement it, but stopped at naming this property of error object... The error can be both related to the object property (in which case relatedProperty seem right) and to the array item in which case the name doesn't fit. Having two different names seems wrong, it defeats the original purpose of getting this info from a single place... relatedPropertyOrItem seems quite stupid too. What do you think?

@xfg
Copy link

xfg commented Aug 31, 2016

I don't understand too how I can create error messages to my html forms with ajv errors. I want to write the algorithm which will be display error messages nearby with input fields, but I can't understand how it do because within error object from ajv frequently unclear to which the field can be attributed this error. I must figure out it that from dataPath that from missingProperty for require keyword that from devil knows where for custom keyword errors for example validatePassword which I want to display nearby with the password input field.

And the saddest that I can't find any articles how to use ajv for displaying errors for html forms at frontend.

@epoberezkin
Copy link
Member

This can be helpful: https://github.com/epoberezkin/ajv#validation-errors

Error is always either on the property level (where you know the data path) or on the parent object level in some cases (where you know the data path as well). So you should be able to filter errors for each field.

As I wrote above, you can use the option errorDataPath: 'property', in which case all errors will be on the property level.

@xfg
Copy link

xfg commented Aug 31, 2016

@epoberezkin thanks for the reply I will try to use errorDataPath: 'property' maybe it really help me.

@epoberezkin
Copy link
Member

This can be helpful maybe: https://github.com/DivineGod/ajv-error-messages

@ashnur
Copy link

ashnur commented Jul 18, 2018

It's not just required that doesn't have correct dataPath and can not be processed. Everything that is conditional, or anything that is combined from multiple schemas is complicated beyond hope, each and every time in a different way. I am actually curious if this is by design? One would assume that coming up with the same error format multiple times would be easier and more efficient than coming up different error data structures of each different situation.

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