joi icon indicating copy to clipboard operation
joi copied to clipboard

Document message templating

Open chriscalo opened this issue 3 years ago • 1 comments

Context

  • node version: N/A
  • module version: N/A

What are you trying to achieve or the steps to reproduce ?

const Joi = require("joi");

const schema = Joi.object({
  nested: Joi.object({
    name: Joi.string().required().messages({
      "any.required": "how do I do templating here?",
      "string.base"() {
        // via a function?
        return arguments.join(" ");
      },
      "string.empty": "like this? ${key} and what are the values I can use?",
    }),
  }),
});

const result = schema.validate({
  nested: {
  },
});

console.log(result.error.details);

Message formatting isn't at all documented, including:

  • the templating syntax (I had to dig through source to find things like {{#label}} and {:[.]}, but I've never seen that syntax, what do those characters mean or do?).
  • which values are available to be interpolated into the message for each (digging through the source I found key, label, and value, but had to use console.log() to learn that label is a quoted, dot-joined key path)

Copied from a StackOverflow answer I posted: https://stackoverflow.com/a/68092831/101869


Using templates

I had to dig through the source to find an example of how to do context-dependent templating / formatting of messages since it doesn't seem to be documented:

messages: {
  'string.alphanum': '{{#label}} must only contain alpha-numeric characters',
  'string.base': '{{#label}} must be a string',
  'string.base64': '{{#label}} must be a valid base64 string',
  'string.creditCard': '{{#label}} must be a credit card',
  'string.dataUri': '{{#label}} must be a valid dataUri string',
  'string.domain': '{{#label}} must contain a valid domain name',
  'string.email': '{{#label}} must be a valid email',
  'string.empty': '{{#label}} is not allowed to be empty',
  'string.guid': '{{#label}} must be a valid GUID',
  'string.hex': '{{#label}} must only contain hexadecimal characters',
  'string.hexAlign': '{{#label}} hex decoded representation must be byte aligned',
  'string.hostname': '{{#label}} must be a valid hostname',
  'string.ip': '{{#label}} must be a valid ip address with a {{#cidr}} CIDR',
  'string.ipVersion': '{{#label}} must be a valid ip address of one of the following versions {{#version}} with a {{#cidr}} CIDR',
  'string.isoDate': '{{#label}} must be in iso format',
  'string.isoDuration': '{{#label}} must be a valid ISO 8601 duration',
  'string.length': '{{#label}} length must be {{#limit}} characters long',
  'string.lowercase': '{{#label}} must only contain lowercase characters',
  'string.max': '{{#label}} length must be less than or equal to {{#limit}} characters long',
  'string.min': '{{#label}} length must be at least {{#limit}} characters long',
  'string.normalize': '{{#label}} must be unicode normalized in the {{#form}} form',
  'string.token': '{{#label}} must only contain alpha-numeric and underscore characters',
  'string.pattern.base': '{{#label}} with value {:[.]} fails to match the required pattern: {{#regex}}',
  'string.pattern.name': '{{#label}} with value {:[.]} fails to match the {{#name}} pattern',
  'string.pattern.invert.base': '{{#label}} with value {:[.]} matches the inverted pattern: {{#regex}}',
  'string.pattern.invert.name': '{{#label}} with value {:[.]} matches the inverted {{#name}} pattern',
  'string.trim': '{{#label}} must not have leading or trailing whitespace',
  'string.uri': '{{#label}} must be a valid uri',
  'string.uriCustomScheme': '{{#label}} must be a valid uri with a scheme matching the {{#scheme}} pattern',
  'string.uriRelativeOnly': '{{#label}} must be a valid relative uri',
  'string.uppercase': '{{#label}} must only contain uppercase characters'
}

An example of using a templated message:

const Joi = require("joi");

const schema = Joi.object({
  nested: Joi.object({
    name: Joi.string().required().messages({
      "any.required": "{{#label}} is required!!",
      "string.empty": "{{#label}} can't be empty!!",
    }),
  }),
});

const result = schema.validate({
  nested: {
    // comment/uncomment to see the other message
    // name: "",
  },
});

console.log(result.error.details);

When using the template syntax, the context values that seem to be passed are something like the following, though specific rules / validators may pass more context:

{
 ​key: "name", // this key, without ancestry
 ​label: `"nested.name"`, // full path with dots as separators, in quotes
 ​value: "", // the value that was validated
}

chriscalo avatar Jun 23 '21 02:06 chriscalo

Agreed, I don't think there are any examples of custom messaging.

Right now, trying to track down custom messages in the docs reads like this: any.messages(messages) ➜ any.prefs(options) ➜ any.validate(value, [options]) ➜ messages

and even then, there are no examples on how to use the messages option.

bromy avatar Mar 02 '22 17:03 bromy