Joi: Custom errors - chaining breaks default functionality

Created on 29 Sep 2019  路  10Comments  路  Source: sideway/joi

Hi team, really appreciate the effort that has been into this validator to make it so dynamic and easy to use.

Describe the problem you are trying to fix (provide as much context as possible)

I'm using 16.1.4 version of the validator.

The problem I'm facing is with custom error messages. I've gone through https://github.com/hapijs/joi/issues/1739, and that did not help (in the sense that it does not accomplish the functionality for multiple keys).

My schema is:

const joiSchema = Joi.object({
  a: Joi.string()
    .error(new Error('a should be a type of text'))
    .min(2)
    .error(new Error('a should be a min of 2'))
    .max(10)
    .required(),
  b: Joi.string()
    .error(new Error(`b should be a type of text`))
    .valid('INBOUND', 'OUTBOUND')
    .error(new Error(`Incorrect input provided for field: b`))
    .min(1)
    .error(new Error(`b should at least have a length of 1`))
    .max(100)
    .required()
});

My validation code is: joiSchema.validate(payload, { abortEarly: false });

and my input payload is:

{
  "a": 1,
  "b": 2
}

Expected result:
{ message: "a should be a type of text, b should be a type of text" }

Actual result:
{ message: "a should be a min of 2" }


There are 3 concerns here:

  1. Only the last error is thrown. While validating, either all the failure messages should be returned (in this scenario, 'a should be a type of text, a should be a min of 2'), or at least the first validation failure (in this scenario: 'a should be a type of text').
  2. Validation does not move to other keys: Since the option abortEarly is set to false, the validation should proceed and additionally provide the error for 'b'. And similar to 'a', it should show at least the first encountered error, or all the errors encountered.
  3. Default messages are skipped when custom messages are used: When using custom message for one of the validation, let's say type check (string), and not using custom message for other validation, let's say .required(), the default message of other validation never generates. So, in this case, if 'a' is not even passed, and .required() does not have a custom message, the expected message: 'a is required' never appears.

I did a little test, and found out that if I use the custom error feature (any.error), even once anywhere, then it breaks the functionality.

By functionality I mean that:

  • abortEarly: false is ignored. Other schema keys are not validated.
  • Even for the same key ('a'), other non-custom errors are not considered. So, if I remove 'a' from input payload, then .required should trigger (which is not happening) and the error message should be '\"a\" is required.' (since this is the joi-defined error for any.required).

and by even once I mean:

const joiSchema = Joi.object({
  a: Joi.string()
    .error(new Error('a should be a type of text'))
    .min(2)
    .max(10)
    .required(),
  b: Joi.string()
    .valid('INBOUND', 'OUTBOUND')
    .min(1)
    .max(100)
    .required()
});

In this changed schema, the custom error is used only once.

Which API (or modification of the current API) do you suggest to solve that problem ?

I'm not sure yet, but I can look around the code if required.

Are you ready to work on a pull request if your suggestion is accepted ?

Yes (but it might take some time, as I've barely gone through the code yet).

documentation support

Most helpful comment

Yes @hueniverse. I found the list of errors (along with description) here: https://github.com/hapijs/joi/blob/master/API.md#list-of-errors. Thanks again.

All 10 comments

what version of @hapi/joi are you using ?
I'm using 16.1.4 it gives NO Errors ; below is the output

{
"value": {
"a": 1,
"b": 2
},
"error": {}
}

Sorry forgot to mention that. I'm also using the same 16.1.4. (I've updated the ticket with a little more details)

You are using the wrong method. .error() is not meant to override error messages. I've updated the documentation.

@hueniverse trying to use .message() however getting an error:

Error: Cannot apply rules to empty ruleset or the last rule added does not support rule properties
    at new module.exports (/Users/daniels/sources/wix-node-platform/node_modules/@hapi/hoek/lib/error.js:21:15)
    at module.exports (/Users/daniels/sources/wix-node-platform/node_modules/@hapi/hoek/lib/assert.js:20:11)

this is my validation code:

result.valid(...LANGUAGE_CODES).message({'any.only': 'invalid language'})

UPD: found how to tackle it with .prefs(...) looking into tests.
IMHO documentation should mention that for .valid and other non-ruleset based

@hueniverse Thanks.

@hugebdu I tried this:

const joiSchema = Joi.object({
  a: Joi.string()
    .messages({ 'any.only': `a should be a type of 'text'` })
    .min(2)
    .max(10)
    .required(),
  b: Joi.string()
    .messages({ 'any.only': `b should be a type of 'text'` })
    .valid('I', 'O')
    .messages({ 'any.only': `b could either be 'I' or 'O'` })
    .min(1)
    .max(100)
    .required()
});

with input as:

{
    a: 1,
    b: 2
}

Expected output: "a should be a type of 'text'. b should be a type of 'text'"
Actual output: "\"a\" must be a string. \"b\" must be one of [I, O]. \"b\" must be a string"

Can you please tell me what I'm doing wrong? (I went through the documentation: https://hapi.dev/family/joi/?v=16.1.5#anymessagesmessages, but could not find what is the correct usage)

Additional details:
After looking at tests and some code, I've tried:

Joi.string()
    .prefs({
      messages: {
        english: { 'any.type': `a should be a type of 'text'` }
      }
    })

and

Joi.string()
    .prefs({
      messages: {
        root: `a should be a type of 'text'`
      }
    })

But they all seem to fail to respond with custom message.

Update:
Ok, so after going through more code/documentation and tests/examples, I figured that out.
The correct schema to do this is:

Joi.string()
    .min(2)
    .max(10)
    .required()
    .messages({
      'string.base': `"a" should be a type of 'text'`,
      'string.empty': `"a" cannot be an empty field`,
      'any.required': `"a" is a required field`
    }),

You can have the .messages() at any level (as it is any.messages()). My preference is to keep it all together. The only pain is to find the keys for messages, i.e., in this example, 'string.base', 'string.empty', 'any.required' etc. I'm sure there must be a repo for all of these, but my current approach was to let my validation fail and find what error is returned. In the error object, the type is specified for the failure, then use that type in schema (under messages).

For not having to hard-code values, like in min/max, you can also use references:

    'string.min': `"a" should have a minimum length of {#limit}`

Output: "\"a\" should have a minimum length of 2."

Thanks @hueniverse and @hugebdu.

The documentation is pretty detailed about which error codes go with which method.

Yes @hueniverse. I found the list of errors (along with description) here: https://github.com/hapijs/joi/blob/master/API.md#list-of-errors. Thanks again.

This thread has been automatically locked due to inactivity. Please open a new issue for related bugs or questions following the new issue template instructions.

Was this page helpful?
0 / 5 - 0 ratings