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

extract or iterate field errors #11

Open
scott2b opened this issue Apr 11, 2015 · 5 comments
Open

extract or iterate field errors #11

scott2b opened this issue Apr 11, 2015 · 5 comments

Comments

@scott2b
Copy link

scott2b commented Apr 11, 2015

It is not clear to me how one would get to the specific error for a key. The ValidationError object seems to only offer a string representation of the concatenated errors. It seems like maybe the context property is meant to contain individual errors, but that is always empty for me.

Is it possible to get to the specific field errors? This would be necessary, e.g., for using valideer for form validation. If it is possible, could you update the README with an example? If not, would you consider it as a feature request? This is really nicely done, but my primary use case at the moment is form validation.

@gsakkis
Copy link
Contributor

gsakkis commented Apr 14, 2015

A ValidationError is raised on the first encountered error. The context captures the location of the error in an arbitrarily nested input structure, for example:

import valideer as V
v = V.parse({"foo": ["integer"]})
try:
    v.validate({"foo": [1,"2","3"], "bar": 3.14})
except V.ValidationError as ex:
    print ex
    print ex.context

# prints
Invalid value '2' (str): must be integer (at foo[1])
[1, 'foo']

So it is not possible to capture all individual errors at the moment. The main use case of valideer is validation and adaptation for complex nested JSON API requests where the consumer is typically a developer, not so much web form validation for reporting all individual errors to end-users. I will leave this issue open as a feature request though I'm not sure how easy it would be to add in a backwards-compatible way.

Until then what you could try is, instead of definining a single validator for the whole form, define one validator for each form field and then manually loop over them, something like this:

field_validators = {
    "foo": V.parse("integer"),
    "bar": V.parse(["string"]),
}

form_data ={
    "foo": "3",
    "bar": ["1", 2, 3, "4"],
}

errors = []
for key, value in form_data.items():
    try:
        field_validators[key].validate(value)
    except V.ValidationError as ex:
        errors.append("Error at '{}': {}".format(key, ex))

@gsakkis
Copy link
Contributor

gsakkis commented May 31, 2015

@scott2b I opened #14 adding support for this feature. Feel free to give it a try and let me know if you have any feedback before I merge it and push a new release to PyPI.

@scott2b
Copy link
Author

scott2b commented Jun 1, 2015

This is nice, although it is not immediately clear to me how to make use of the resulting error structures. I'm not sure what use case you have designed for -- and I don't remember exactly what I was trying to do before myself, but I think it was for creating HTML forms with arbitrarily nested structures -- something most form validation libraries don't handle well.

As it is, it seems like to tease out the errors, I need to iterate MultipleValidationError.errors, and for each of those inspect the message and the context. Although, even from there, it does not seem straightforward to associate an error with a field -- it looks like I would have to reconstruct a key from the reverse of the context. Is that the expectation, or is there maybe something I overlooked?

An error structure that is made of a dictionary that reflects the original schema seems like it would be more straightforward to use. But again, I'm not sure what you are designing to, so maybe this meets the needs you have in mind. As for my part, I would probably want to dig a bit deeper on this before I said with certainty that a dictionary approach would be more useful.

In general, this seems good. Even for something like a JSON web API, which I understand to be Valideer's primary use case, it seems useful to be able to present all the errors, rather than shortcutting the validation.

I hope this is helpful to you. It is kind of a cursory review. If I can help by digging deeper on this, please let me know and I will see what I can do. Thanks for the update.

@gsakkis
Copy link
Contributor

gsakkis commented Jun 3, 2015

Thanks for the feedback, that's quite helpful. Just to clarify, this was mostly motivated by your feature request; I don't have any immediate need for it myself, which makes all the more important to hear from people who might actually use it :)

Based on your original post, I assumed that your use case was simple web form validation where you just needed to display as plain string messages all the form errors back to the user. What I didn't assume was that (a) the forms involve arbitrarily nested structures and that (b) there needs to be a mapping between each error and the respective part of the schema that is violated. This makes things a bit more challenging, not so much in terms of implementation but in terms of how to represent this mapping. You mention "associate an error with a field", where by "field" you probably mean a dictionary key. However valideer schemas represent all sorts of objects, not just dictionaries, so it's not obvious how to construct a general representation.

To make the discussion a bit more concrete, below is the sample schema from the README and an invalid input with multiple errors:

validator = V.parse({
    "+id": "number",
    "+name": "string",
    "+price": V.Range("number", min_value=0),
    "tags": ["string"],
    "stock": {
        "warehouse": "number",
        "retail": "number",
    }
})

product = {
    "id": "1",
    "price": -10,
    "tags": ["soldout", True, "popular", 3],
    "stock": {
        "retail": True,
    }
}

try: 
    validator.full_validate(product)
except V.MultipleValidationError as ex:               
    for e in ex.errors:
        print e

# output
Invalid value {'price': -10, 'stock': {'retail': True}, 'id': '1', 'tags': ['soldout', True, 'popular', 3]} (dict): missing required properties: ['name']
Invalid value '1' (str): must be number (at id)
Invalid value -10 (int): must not be less than 0 (at price)
Invalid value True (bool): must be number (at stock['retail'])
Invalid value True (bool): must be string (at tags[1])
Invalid value 3 (int): must be string (at tags[3])

How would you represent MultipleValidationError.errors as a nested error structure instead of a flat list of error messages? If you could suggest a representation that can be generalized to represent errors of arbitrary valideer schemas, I'd be happy to update the implementation accordingly.

@scott2b
Copy link
Author

scott2b commented Jun 4, 2015

As-is, there is no way in the presentation to associate an error with the appropriate field. Maybe the idea I have in mind is too simplistic with potential problems that I have not thought of yet, but I would think you would just follow the schema. Thus:

{
    "_missing_required": ["name"],
    "id": <Err(must be number)>,
    "price": <Err(must not be less than 0)>,
    "tags": [None,<Err(must be string)>,None,<Err(must be string)>],
    "stock": {
        "retail": <Err(must be number)>,
    }
}

In practice, I really don't know how well this would work for forms. I suppose one would have to walk the schema to generate the form, and along the way pull out instance data and errors based on the current key.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants