Node.js + Joi如何显示自定义错误信息?

Node.js + Joi how to display a custom error messages?

使用 Joi.

验证用户在 Node.js RESTapi 中的输入似乎非常简单

但问题是我的应用程序不是用英文编写的。 这意味着我需要向前端用户发送自定义的书面消息。

我用谷歌搜索了这个,只发现了问题。

也许有人可以为此提供解决方案?

这是我用来验证 Joi 系统的代码:

    var schema = Joi.object().keys({
      firstName: Joi.string().min(5).max(10).required(),
      lastName: Joi.string().min(5).max(10).required()
      ..
    });

    Joi.validate(req.body, schema, function(err, value) {
      if (err) {
        return catched(err.details); 
      }
    });

    function catched(reject) {
      res.json({
        validData: false,
        errors: reject
      });
    }

另外,有没有办法在客户端使用Joi

谢谢!

我找到的一个解决方案是设置:

var schema = Joi.object().keys({
  firstName: Joi.string().min(5).max(10).required().label("Your error message in here"),
  lastName: Joi.string().min(5).max(10).required()
  ..
});

然后从回调 error 变量中打印 label

你可以使用 .error(new Error('message')),它对我有用

var schema = Joi.object().keys({
  firstName: Joi.string().min(5).max(10).required().error(new Error('Give your error message here for first name')),
  lastName: Joi.string().min(5).max(10).required().error(new Error('Give your error message here for last name'))
  ..
});

Joi.validate(req.body, schema, function(err, value) {
  if (err) {
    console.log(err.message)
    return catched(err.message); 
  }
});

添加自定义消息的解决方法: 只需添加另一个链式函数即可在定义架构时抛出错误。
在你的情况下

 firstName: Joi.string().min(5).max(10).required().error(new Error('I am a custom error and I know it!')),

其余将保持不变。

在客户端使用 Joi 的解决方案(你的第二个问题)

Joi-Browser 是允许在客户端使用相同模式的包。

Here 是一个有趣的讨论,您可以看看。

干杯!

let schema = Joi.object({ foo: Joi.number().min(0).error(() => '"foo" requires a positive number') });

文档 link

Joi 版本 14.0.0

const SchemaValidation = {
  coins: Joi.number()
    .required()
    .error(() => {
      return {
        message: 'Coins is required.',
      };
    }),
  challenge_name: Joi.string()
    .required()
    .error(() => {
      return {
        message: 'Challenge name is required.',
      };
    }),
  challengeType: Joi.string()
    .required()
    .error(() => {
      return {
        message: 'Challenge type is required.',
      };
    }),
  challengeDescription: Joi.string()
    .required()
    .error(() => {
      return {
        message: 'Challenge description is required.',
      };
    }),
};

errors对象中你可以得到,错误类型和改变信息。

上扩展,如果您有许多不同的错误类型,您可以检查存在哪种类型的错误,并相应地设置其消息:

Joi v17.4.0

v17.4.0 使用 err.code

var schema = Joi.object().keys({
  firstName: Joi.string().min(5).max(10).required().error(errors => {
    errors.forEach(err => {
      switch (err.code) {
        case "any.empty":
          err.message = "Value should not be empty!";
          break;
        case "string.min":
          err.message = `Value should have at least ${err.local.limit} characters!`;
          break;
        case "string.max":
          err.message = `Value should have at most ${err.local.limit} characters!`;
          break;
        default:
          break;
      }
    });
    return errors;
  }),
  // ...
});

您可以在此处查看错误列表:Joi 17.4.0 API Reference > Errors > List of errors

Joi v14.3.1

v14.3.1 使用 err.type

var schema = Joi.object().keys({
  firstName: Joi.string().min(5).max(10).required().error(errors => {
    errors.forEach(err => {
      switch (err.type) {
        case "any.empty":
          err.message = "Value should not be empty!";
          break;
        case "string.min":
          err.message = `Value should have at least ${err.context.limit} characters!`;
          break;
        case "string.max":
          err.message = `Value should have at most ${err.context.limit} characters!`;
          break;
        default:
          break;
      }
    });
    return errors;
  }),
  // ...
});

您可以在此处查看错误列表:Joi 14.3.1 API Reference > Errors > List of errors

您还可以查看 any.error 参考以获取更多信息。引用文档:

Overrides the default joi error with a custom error if the rule fails where:

  • err can be:
    • an instance of Error - the override error.
    • a function(errors), taking an array of errors as argument, where it must either:
      • return a string - substitutes the error message with this text
      • return a single object or an Array of it, where:
        • type - optional parameter providing the type of the error (eg. number.min).
        • message - optional parameter if template is provided, containing the text of the error.
        • template - optional parameter if message is provided, containing a template string, using the same format as usual joi language errors.
        • context - optional parameter, to provide context to your error if you are using the template.
      • return an Error - same as when you directly provide an Error, but you can customize the error message based on the errors.
  • options:
    • self - Boolean value indicating whether the error handler should be used for all errors or only for errors occurring on this property (true value). This concept only makes sense for array or object schemas as other values don't have children. Defaults to false.
let schema = Joi.object().keys({
   Joi.string().required().options({language: {any: {required: "First name is required"}}})
});

原回答:

目前的方式(我个人觉得更好)是使用.messages()(或.prefs({messages}))。

const Joi = require('@hapi/joi');

const joiSchema = Joi.object({
  a: 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`,
      'string.min': `"a" should have a minimum length of {#limit}`,
      'any.required': `"a" is a required field`
    })
});

const validationResult = joiSchema.validate({ a: 2 }, { abortEarly: false });
console.log(validationResult.error); // expecting ValidationError: "a" should be a type of 'text'

.errors() 的用法是 not recommended 只是用自定义消息更新默认消息。

.prefs({ messages }) 是一种提供更多选项作为首选项的精心设计的方法。 prefs 的其他选项直接取自 .validate()

的选项

进一步阅读:https://github.com/hapijs/joi/issues/2158


更新 1:我看到上面的解释对某些人来说不成立,所以我放了一些代码来测试你自己。在这里查看:https://runkit.com/embed/fnfaq3j0z9l2

还更新了之前共享的代码片段,以包含从包包含、使用到调用实际验证方法的详细信息。


更新 2:joi 错误类型列表及其描述(对于 .messages() - 如 string.base、array.unique、date.min 等..) 可用 here.


更新 3:Joi 有 moved from hapi project to standalone: https://www.npmjs.com/package/joi。 确保您使用的是最新版本(或至少高于 v17)。

对于

有问题的任何人

...messages is not a function

错误,您必须使用 npm install @hapi/joi 安装 joi,并使用 @hapi/joi 导入它。我错误地安装了没有 @hapi/ 前缀的 joi,我花了一段时间才找到错误。

只需调用"message()"函数:

firstName: Joi.string().message("Your custom message")

我找到的最佳解决方案是:

为 JOI 验证创建一个中间件

Validator.js - 您可以创建自定义错误对象

const Joi = require('Joi');

module.exports = schema => (req, res, next) => {
  const result = Joi.validate(req.body, schema);
  if (result.error) {
    return res.status(422).json({
      errorCause: result.error.name,
      missingParams: result.error.details[0].path,
      message: result.error.details[0].message
    });
  }
  next();
};

在路由或控制器中传递这个中间件函数


const joiValidator = require('../utils/validator'); // Wherever you have declare the validator or middlerware

   const  userSchema = joi.object().keys({
    email : joi.string().email().required(),
    password : joi.string().required()
  });

  routes.routes('/').get(joiValidator(userSchema), (req, res) => {
    res.status(200).send('Person Check');
  });

在最新版本中使用 message as.

var schema = Joi.object().keys({
  firstName: Joi.string().min(5).max(10).required().messages({
    "string.base": `"username" should be a type of 'text'`,
    "string.empty": `"username" cannot be an empty field`,
    "any.required": `"username" is a required.`,
  }),
  lastName: Joi.string().min(5).max(10).required().messages({
    "string.base": `"lastName" should be a type of 'text'`,
    "string.empty": `"lastName" cannot be an empty field`,
    "any.required": `"lastName" is a required.`,
  }),
  [...]
});

您还可以显示特定 属性

的消息
const Joi = require('Joi');

const schema = Joi.object({
    username: Joi.string()
      .min(2)
      .max(30)
      .required()
      .pattern(new RegExp(/^(?!.*\.\.)(?!.*\.$)[^\W][\w.]{0,29}$/))
      .message({"string.pattern.base":"Invalid username",
                "string.min":"minimum 2 character required",
                "string.max":"maximum 30 characters allowed"})
  });

您可以参考此消息对象键。

messages: {
      'any.custom': [Object],
      'any.default': [Object],
      'any.failover': [Object],
      'any.invalid': [Object],
      'any.only': [Object],
      'any.ref': [Object],
      'any.required': [Object],
      'any.unknown': [Object],
      'string.alphanum': [Object],
      'string.base': [Object],
      'string.base64': [Object],
      'string.creditCard': [Object],
      'string.dataUri': [Object],
      'string.domain': [Object],
      'string.email': [Object],
      'string.empty': [Object],
      'string.guid': [Object],
      'string.hex': [Object],
      'string.hexAlign': [Object],
      'string.hostname': [Object],
      'string.ip': [Object],
      'string.ipVersion': [Object],
      'string.isoDate': [Object],
      'string.isoDuration': [Object],
      'string.length': [Object],
      'string.lowercase': [Object],
      'string.max': [Object],
      'string.min': [Object],
      'string.normalize': [Object],
      'string.token': [Object],
      'string.pattern.base': [Object],
      'string.pattern.name': [Object],
      'string.pattern.invert.base': [Object],
      'string.pattern.invert.name': [Object],
      'string.trim': [Object],
      'string.uri': [Object],
      'string.uriCustomScheme': [Object],
      'string.uriRelativeOnly': [Object],
      'string.uppercase': [Object]
    }

使用模板

我不得不深入研究 source 以找到一个示例,说明如何进行消息的上下文相关模板化/格式化,因为它似乎没有记录在案:

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'
}

使用模板消息的示例:

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);

使用模板语法时,似乎传递的上下文值类似于以下内容,但特定规则/验证器可能会传递更多上下文:

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

如果您需要一种简单的方法来传递自定义消息,请使用 js-flex-validator

import Flex, { validateObject } from "js-flex-validator";

const constraints = [
  Flex("username")
    .string()
    .allowEmpty()
    .min(3, "Username should be at least 3 characters")
    .max(50, "Username should not exceeds 50 characters"),
  Flex("email")
    .email("This email is not valid.")
    .match(/\w.@edu.com$/, "Should be a edu.com domain")
    .required()
    .min(5, "Email should be at least 3 characters")
    .max(255, "Username should not exceeds 255 characters"),
  Flex("password")
    .string()
    .required()
    .min(5, "Password should be at least 5 characters")
    .max(20, "Password should not exceeds 50 characters"),
];

const data = {username: "geek4", email: "example@email.com", password: "123456" };
const { hasError, errorDetails } = validateObject(data, constraints)

对于像我一样希望使用自定义消息验证数字的任何人,这是来自源代码:

'number.base': '{{#label}} must be a number',
'number.greater': '{{#label}} must be greater than {{#limit}}',
'number.infinity': '{{#label}} cannot be infinity',
'number.integer': '{{#label}} must be an integer',
'number.less': '{{#label}} must be less than {{#limit}}',
'number.max': '{{#label}} must be less than or equal to {{#limit}}',
'number.min': '{{#label}} must be greater than or equal to {{#limit}}',
'number.multiple': '{{#label}} must be a multiple of {{#multiple}}',
'number.negative': '{{#label}} must be a negative number',
'number.port': '{{#label}} must be a valid port',
'number.positive': '{{#label}} must be a positive number',
'number.precision': '{{#label}} must have no more than {{#limit}} decimal places',
'number.unsafe': '{{#label}} must be a safe number'

这是我如何使用 joi 17.6 进行操作的示例

VerifyAuthOtpValidator (otpProps: OtpProps) {
        const otpSchema = Joi.object<OtpProps>({
            otp_code: Joi.string().required().messages({
                "string.empty": "No OTP code provided",
                "any.required": "No OTP code provided",
            })
        });
        const {error, value} = otpSchema.validate(otpProps);
        //you can log the error property, as this would enable you
        // see the error type, which you can then, customise
        // the message
        // console.log("\n\t error: ", error)
        if(error){
            return error.message
        }
        return value
    }