【问题标题】:Returning multiple errors in Joi在 Joi 中返回多个错误
【发布时间】:2020-01-26 10:38:03
【问题描述】:

我正在尝试从我的 Joi 验证架构返回多个自定义错误消息。这是架构

const Joi = require("@hapi/joi");
const string = Joi.string();
const emailSchema = string.email();
const usernameSchema = string
  .min(3)
  .max(30)
  .error(() => "Username must be between 3 and 30 characters");
const passwordSchema = string
  .min(6)
  .error(() => "Password must be at least 6 characters");
const confirmPasswordSchema = Joi.valid(Joi.ref("passwordSchema")).error(
  () => "Passwords must match"
);

const localRegistrationSchema = Joi.object().keys({
  email: emailSchema.required().error(() => "Email is required"),
  username: usernameSchema.required().error(() => "Username is required"),
  password: passwordSchema.required().error(() => "Password is required"),
  confirmPassword: confirmPasswordSchema
});

这是我使用架构的地方

const { error } = localRegistrationSchema.validate(req.body, {
        abortEarly: false
      });
console.log(error);
if (error) throw Boom.boomify(error);

但我不断收到 TypeError: Cannot read property 'filter' of undefined 这似乎是由

details.push({
    message,
    path: item.path.filter((v) => typeof v !== 'object'),
    type: item.code,
    context: item.local
});

这是 Joi 错误处理代码的一部分

当我不附加 .error() 部分时,我没有收到此错误,但如果我使用 .error(new Error("custom error message") 则无法显示一个以上的错误

我不知道出了什么问题,也无法找到任何其他方式来返回多个自定义错误消息以正常工作

【问题讨论】:

  • Joel Jacobson 我找到了一个更优雅的解决方案。您可以使用Joi.any.messages() 选项。有关完整示例,请参阅:stackoverflow.com/a/58198097/5536304
  • 谢谢!!这太棒了。但是,我确实发现了一个警告,它也会影响您提供的原始代码以及您链接的示例中的代码。当您执行 string().required() 时,它会引发 string.empty 错误而不是 any.required 错误,因此检查 any.required 不起作用。您必须检查 string.empty。不知道其他类型是不是也一样。

标签: node.js express hapijs joi


【解决方案1】:

看到这仍然获得了很多意见,我想补充一点,这就是我目前使用 Joi 处理多个错误的方式。我只是将我所有的验证/清理函数串在一起,然后我的所有自定义消息都在最后进入 .messages({}),而不是使用 switch 语句。如果您在前端使用它,同样适用于 yup。这使得它比 switch 语句和错误消息帮助函数更简洁明了。

const string = Joi.string();
const passPattern =
  "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*\\W)[a-zA-Z0-9\\S]{8,}$";

export const signupLocalSchema = Joi.object({
  email: string.email().trim().lowercase().required().messages({
    "string.email": "Not a valid email address.",
    "string.empty": "Email is required.",
  }),
  username: string.min(3).max(30).trim().lowercase().required().messages({
    "string.min": "Username must be between 3 and 30 characters.",
    "string.max": "Username must be between 3 and 30 characters.",
    "string.empty": "Username is required.",
  }),
  password: string.pattern(new RegExp(passPattern)).messages({
    "string.pattern.base":
      "Password must be at least 8 characters and contain at least 1 lowercase, 1 uppercase, 1 number and 1 special character.",
  }),
  confirmPassword: Joi.valid(Joi.ref("password")).messages({
    "any.only": "Passwords must match.",
  }),
});

【讨论】:

    【解决方案2】:

    错误

    我调试了您的代码并简单地返回 () => 'some error message' 不适用于您的解决方案。我们需要返回一个函数。出现错误是因为自定义错误消息中的 path 属性为 undefined


    错误链接不起作用

    const schema = Joi.object({
      prop: Joi.string()
               .min(9)
               .error(() => 'min error message')
               .required()
               .error(() => 'required error message');
    });
    

    只有一个切换的错误消息有效

    const schema = Joi.object({
      username: Joi.string()
                .min(9)
                .required()
                .error((errors) => {
                  for (err of errors) {
                    switch (err.code) {
                      case ('string.min'): {
                        return simpleErrorMsgFunc("prop min error message", ["prop"])(); // invoke
                      }
                      case 'any.required': {
                        return simpleErrorMsgFunc("prop is required", ["prop"])(); // invoke
                      }
                      default: {
                        return simpleErrorMsgFunc("prop has error", ["prop"])(); // invoke
                      }
                    }
                  }
                }),
    });
    


    辅助函数

    我的解决方案的核心是以下功能。它返回一个function,它返回一个自定义错误object。:

    function simpleErrorMsgFunc(message, path) {
      return () => {
        return {
          toString: () => message,
          message,
          path,
        }
      };
    }
    


    整体解决方案

    const Joi = require("@hapi/joi");
    
    function simpleErrorMsgFunc(message, path) {
      return () => {
        return {
          toString: () => message,
          message,
          path,
        }
      };
    }
    
    const localRegistrationSchema = Joi.object().keys({
      // email is simple, we only need 1 error message
      email: Joi.string()
                .email()
                .required()
                .error(simpleErrorMsgFunc("Email is required", ["email"])),
    
      // username is advanced, we need 2 error message
      username: Joi.string()
                .min(3)
                .max(30)
                .required()
                .error((errors) => {
                  for (err of errors) {
                    switch (err.code) {
                      case ('string.min' || 'string.max'): {
                        return simpleErrorMsgFunc("username must be between 3 and 30 characters", ["username"])(); // invoke
                      }
                      case 'any.required': {
                        return simpleErrorMsgFunc("username is required", ["username"])(); // invoke
                      }
                      default: {
                        return simpleErrorMsgFunc("username has error", ["username"])(); // invoke
                      }
                    }
                  }
                }),
    
    // password is advanced, we need 2 error message
      password: Joi.string()
                   .min(6)
                   .required()
                   .error((errors) => {
                    for (err of errors) {
                      switch (err.code) {
                        case ('string.min'): {
                          return simpleErrorMsgFunc("Password must be at least 6 characters", ["password"])(); // invoke
                        }
                        case 'any.required': {
                          return simpleErrorMsgFunc("Password is required", ["password"])(); // invoke
                        }
                        default: {
                          return simpleErrorMsgFunc("password has error", ["password"])(); // invoke
                        }
                      }
                    }
                  }),
    
      confirmPassword: Joi.valid(Joi.ref("password"))
                         .error(simpleErrorMsgFunc("Passwords must match", ['confirmPassword']))
    });
    
    const req = {
      body: {
        email: 'some@gmail.com',
        username: 'hee',
         password: '45645656',
         confirmPassword: '45645656_',
      },
    };
    
    const { error } = localRegistrationSchema.validate(req.body, {
      abortEarly: false
    });
    console.log(JSON.stringify(error, null, 2));
    

    附:我注意到您的 confirmPassword 属性不是必需的!

    【讨论】:

    • 谢谢!该解决方案有效,但这不是一个罕见的用例。如果这是检查多个错误的最佳解决方案,我不明白为什么 joi 如此受欢迎。我修改了您的解决方案,试图通过删除 switch 语句并在不同的架构上多次检查相同的路径(即有多个相同的键)来使其更清洁和更模块化。它会产生更清晰、更模块化的代码,但最终只会检查每个键的最后一个实例。如果您知道可以更好地处理此用例的任何其他解决方案或库,请告诉我!
    • 没问题 Joel Jacobsen :) 我还认为在joi 中应该有一个更简单的解决方案来处理多个自定义错误消息。不幸的是,我没有找到任何东西。如果我找到另一个更适合您需要的验证库,我会通知您。
    • 好的,我认为问题在于我试图使用服务器端验证向客户端返回用户友好的消息,而我最好使用客户端验证让用户查看并且只需对安全/边缘情况进行服务器端验证,因此无需担心自定义消息(尽管如果这更容易一点仍然会很好)。
    【解决方案3】:

    你可以试试这样的

    email: emailSchema.required().error(() => {
        return {
          message: "Email is required."
        };
    }),
    

    【讨论】:

    • 尝试使用自定义标签,例如 `email: emailSchema.required().label('user email')
    • 也已经尝试过了,但它只是改变了标准错误消息中调用的路径名;它实际上并没有改变整个错误信息。
    猜你喜欢
    • 1970-01-01
    • 2014-11-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-16
    相关资源
    最近更新 更多