cerberus icon indicating copy to clipboard operation
cerberus copied to clipboard

Custom error messages

Open luizpericolo opened this issue 9 years ago • 30 comments

Could not find anywhere in the docs how I can define custom validation error messages, so I'm assuming that this is not implemented. Please link me to the doc page if I'm mistaken.

I think this is a really good functionality, specially if you want to use cerberus to validate request structure and don't want to process the outcome of the errors dict any further and wish to create beautiful error messages for your response.

luizpericolo avatar Nov 23 '15 19:11 luizpericolo

do you want custom messages for contributed or for custom rules? am i understanding correctly that you want a string as result?

funkyfuture avatar Nov 23 '15 23:11 funkyfuture

oh, and are humans the intended audience?

funkyfuture avatar Nov 24 '15 00:11 funkyfuture

Yes, I want strings and humans are the intended audience.

If I get a error validating a string field according to a regex rule I get the following error:

{
    "name": "value does not match regex '^[a-zA-Z0-9]$'"
}

I'd like to be able to assign a custom message in case the field 'name' is not valid. Something along the lines of:

{
    "name": "value must only contain letters and digits"
}

luizpericolo avatar Nov 24 '15 14:11 luizpericolo

a HumanReadableErrorHandler you could subclass and override error messages is on my agenda (#93). but i want to implement XML, JSON and YAML-output next; slowly but steadily evolving the underlying mechanics. unfortunately i can't give you a reliable estimation atm. it all might happen this year or at the beginning of the next. but several other projects need my attention.

you have an interesting example here as the error message is depending on the full constraint, not only the rule.

funkyfuture avatar Nov 25 '15 10:11 funkyfuture

+1

Also it is required for non-english projects, like this:

{
    "name": lazy_gettext("It seems that the user name is too complicated! Maybe you try something simpler?")
}
# => "Кажется, имя пользователя слишком сложное! Может, попробуете что-нибудь попроще?"

andreymal avatar Mar 04 '16 11:03 andreymal

I found a temporary workaround for 0.10:

import cerberus


class CustomErrorHandler(cerberus.errors.BasicErrorHandler):
    def __init__(self, tree=None, custom_messages=None):
        super(CustomErrorHandler, self).__init__(tree)
        self.custom_messages = custom_messages or {}

    def format_message(self, field, error):
        tmp = self.custom_messages
        for x in error.schema_path:
            try:
                tmp = tmp[x]
            except KeyError:
                return super(CustomErrorHandler, self).format_message(field, error)
        if isinstance(tmp, dict):  # if "unknown field"
            return super(CustomErrorHandler, self).format_message(field, error)
        else:
            return tmp


v = cerberus.Validator(
    {'username': {'minlength': 2, 'regex': r'[A-z0-9]+'}},
    error_handler=CustomErrorHandler(custom_messages={'username': {'regex': u'Не мудрите'}})
)
print(v.validate({'username': '⌀'}))  # => False
print(v.errors)  # => {'username': ['min length is 2', 'Не мудрите']}

(actually 'max length is 2' because #209 :)

andreymal avatar Mar 04 '16 13:03 andreymal

+1 this seems like a very practical feature!

Pithikos avatar Mar 20 '16 18:03 Pithikos

@Pithikos fyi: https://github.com/blog/2119-add-reactions-to-pull-requests-issues-and-comments

funkyfuture avatar Mar 20 '16 19:03 funkyfuture

I was looking for i10n too but it seems it's not on docs, it would be great to add them.

@funkyfuture I feel like your work in #93 is hidden under code, add it to docs and make it shine :)

ghost avatar Aug 29 '16 08:08 ghost

to clarify:

  • a HumanReadableErrorHandler is not about the localization of the errors.BasicErrorHandler, but about an errorhandler that returns a list of prose in strings, including l10n
  • that error handler would live here
    • ~there's only a reference xml-error handler in that collection on my disk yet~
    • i didn't plan to release anything of the collections before the 1.0 of cerberus

@k1-hedayati would you be interested in contributing? having a non-latin-alphabet in the mix from the beginning would certainly be useful. is Farsi a rtl-language?

funkyfuture avatar Aug 29 '16 10:08 funkyfuture

I didn't notice about 1.0, I thought your code is already on stable branch.

@funkyfuture yes I will be happy to contribute on this, how can I help? Yes Farsi and Arabic both are rtl

ghost avatar Aug 30 '16 09:08 ghost

@k1-hedayati i've updated the mentioned repo, so one should get an idea what the whole thing is about. feel free to open a related issue there. any contribution regarding design proposals, implementation, tests or translations (workflows) is welcome.

funkyfuture avatar Sep 03 '16 12:09 funkyfuture

Are custom error messages implemented yet?

This would be a great feature.

I have a multi-lingual app, and want to show messages such as 'Please enter your e-mail' or 'S'il vous plaît entrer votre e-mail' instead of a static 'required field'.

Thanks for your hard work on this library.

leebenson avatar Jun 27 '17 17:06 leebenson

malheureusement, non. i just can point you at this issue: https://github.com/funkyfuture/cerberus-collections/issues/2 (just updated some bits). i have no capacities to join in actively, but certainly will manage to review and release.

more complex error handlers will not find the way into cerberus in order to not depend on these in the release cycle.

funkyfuture avatar Jun 27 '17 19:06 funkyfuture

@andreymal, you don't necessarily need to override the format_message method: https://stackoverflow.com/q/47730454/2489914

cc @Arion-Dsh @nykolaslima

funkyfuture avatar Dec 09 '17 16:12 funkyfuture

@funkyfuture what if I wish to set different messages for different data types? For example, "Username is invalid" for username field and "Email is invalid" for email field. In the same validator of course

andreymal avatar Dec 09 '17 16:12 andreymal

yeah, then you have to tinker around. i related my comment to the i18n part that came up.

funkyfuture avatar Dec 09 '17 16:12 funkyfuture

@funkyfuture actually I combine both methods in production code :) https://pastebin.com/fkhZvBGr

andreymal avatar Dec 09 '17 16:12 andreymal

Any plan to implement internationalization?

I think it could be a good feature. Same as @leebenson, I want to show error messages in different languages.

pimtel avatar Dec 12 '17 22:12 pimtel

I thought localization could be implemented like jQuery validation as follow: https://github.com/jquery-validation/jquery-validation/tree/master/src/localization

Instead of have multiple files, cerberus could have a module called localization.py and we could set as follow:

v = Validator(schema, lang="pt-BR")

Does anyone agree?

pimtel avatar Dec 12 '17 22:12 pimtel

@rgercp imho, simple and universal lazy_gettext is a better solution

andreymal avatar Apr 03 '18 09:04 andreymal

If you don't want to mess with the error handler and your app doesn't need language features anyway, you can make a custom regex validator with the error message as a part of the argument for your custom validator

from cerberus import Validator
import re

class MyCustomValidator(Validator):
	def _validate_lazyregex(self, lazyregex_options, field, value):
		""" {'type': 'dict'} """
		pattern = lazyregex_options['pattern']
		fail_message = lazyregex_options['fail_message']
                if not isinstance(value, _str_type):
			return
		if not pattern.endswith('$'):
			pattern += '$'
		re_obj = re.compile(pattern)
		if not re_obj.match(value):
			self._error(field, fail_message)
schema = {
       'email': {
		'type': 'string',
		'required': True,
		'lazyregex': {
			'pattern': '^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$',
			'fail_message': 'Doesn\'t look like an email to me, man'
		}
	}
}

This is not the recommended approach, but just another option for the people who stumble upon this problem in the future.

rismanrp avatar May 18 '18 15:05 rismanrp

@nicolaiarocci @andreymal unable to find documentation for the custom error messages.

I tried the slightly modified version of the solution given below and it works with simple documents it fails when using complex docs. stackoverflow Modified version


class CustomErrorHandler(BasicErrorHandler):
    def __init__(self, schema):
        self.custom_defined_schema = schema

    def _format_message(self, field, error):
        error_msg = self.custom_defined_schema[field].get('meta', {}).get('_errors', {}).get(error.rule)
        if not error_msg:
           return error_msg
        return super(CustomErrorHandler, self)._format_message(field, error)

It feels like a workaround and also that's a lot of work just to give an alternate message.

sagarkaurav avatar Feb 28 '20 10:02 sagarkaurav

@funkyfuture I am trying to keep the custom error messages for certain fields in the meta tag. However, I am not able to access this meta tag for inner elements. Example, if a key is in a nested dictionary, then the below line of code does not allow me to access the meta tag. Can you suggest me a better way to handle this? error_msg = self.custom_defined_schema[field].get('meta', {}).get('error_message', field)

My CustomErorHandler has been attached below. I am trying to get meta tags for specific types of errors and throw them.

class CustomErrorHandler(BasicErrorHandler):
    def __init__(self, schema=None, tree=None):
        super().__init__()
        self.custom_defined_schema = schema
        if tree is not None:
            self.tree = tree
    def _format_message(self, field, error):
        logger.error("Field- " + str(field) + " | " + "Value- " + str(error.value) + " | " + "Error- " + str(
            super(CustomErrorHandler, self)._format_message(field, error)))
        print(error.value, "---", error.code)

        handle_error_fields = [0, 2, 36, 37, 66, 68]
        if error.code == 3:
            return "Field passed -" + field + " is not present in master. Please contact admin!"
        if error.code in handle_error_fields:
            return super(CustomErrorHandler, self)._format_message(field, error)

        error_message_found = False
        try:
            error_msg = self.custom_defined_schema[field].get('meta', {}).get('error_message', field)
            error_message_found = True
        except KeyError:
            pass
        if error_message_found == False:
            error_msg = self.custom_defined_schema[field].get('meta', {}).get('error_message', field)


        if error_msg == field:
            return super(CustomErrorHandler, self)._format_message(field, error)
        return error_msg

Abhisek1994Roy avatar Apr 26 '20 15:04 Abhisek1994Roy

Can you suggest me a better way to handle this?

not really from a quick glance and no knowledge of the use-case (and i'm not interested in solving it), but it could eventually be easier to not base on the BasicErrorHandler. instead implement a custom error handler that suits your exact requirements.

error_msg = self.custom_defined_schema[field].get('meta', {}).get('error_message', field)

should that secong call to get return the field name as default?

funkyfuture avatar Apr 27 '20 13:04 funkyfuture

Have figured it out @funkyfuture . Thanks. Had to use the schema_path to get the meta field

Abhisek1994Roy avatar Apr 27 '20 13:04 Abhisek1994Roy

@Abhisek1994Roy can you give some example how you did it

sagarkaurav avatar Apr 27 '20 14:04 sagarkaurav

I had the same problem. I prepared a tool to customize error messages. If you need, please don't hesitate to use Cerberror.

pbrus avatar Oct 07 '20 11:10 pbrus

@pbrus , out of curiosity, why don't you use the ErrorHandler API?

funkyfuture avatar Oct 07 '20 18:10 funkyfuture

@funkyfuture, because I don't need it and it seems to be somehow limited. Let me explain this.

If I understood your post correctly, inheriting from BasicErrorHandler allows to define a new error message1. Cerberror doesn't need these messages. The translate method matches defined messages with paths2. This is happening outside the validator of Cerberus. There is no sense to put these messages into errors.BasicErrorHandler.messages container using API and get them back in the next step to, finally, assign them to the paths. This is the first reason why I omit ErrorHandler API.

The second reason is caused by the limitation of API. Using it we are able to define new messages globally. Let's say that we need to define two fields with regex rules (code 65). The first one is a name of a user which must start with an upper-case letter. The second field is to store an email address. Using API we can set one error message for these fields. For me the better approach is:

('user', 'name') 65 "The name {{value}} must start with upper-case letter"
('user', 'email') 65 "The address email must contain @ sign"

This is what @ha1zum achieved partially. But this still doesn't solve a problem with unclear paths.

If we consider single fields we can easy handle them, modify them and so on. But the problems occur when we play with many nested fields. The definition of a schema is growing fast and I think that playing with a code is harder than just defining a file with paths and messages. And that is why I created Cerberror.


1 IMHO such code example should be in the Errors section. 2 I prefer to name the method translate instead of match because as an interface the first one should be more intuitive describing what the method does.

pbrus avatar Oct 08 '20 18:10 pbrus