处理 Mongoose 验证错误——在哪里以及如何处理?

Posted

技术标签:

【中文标题】处理 Mongoose 验证错误——在哪里以及如何处理?【英文标题】:Handling Mongoose validation errors – where and how? 【发布时间】:2013-02-07 08:48:03 【问题描述】:

我正在尝试决定如何处理 Mongoose 中的验证错误。

使用节点验证器的自定义错误消息

我已经使用node-validator 定义了自己的验证规则,例如:

UserSchema.path('username')
  .validate(function (username) 
    return validator.check(username).notEmpty()
  , 'Username cannot be blank')

这将产生如下所示的错误:

  username: 
    message: 'Validator "Username cannot be blank" failed for path username',
     name: 'ValidatorError',
     path: 'username',
     type: 'Username cannot be blank' ,

使用猫鼬验证器

但是,node-validator 提供了自己的错误消息。如果我使用mongoose-validator Node 模块将 node-validator 直接插入到我的架构中,那么我可以直接使用这些错误消息:

var UserSchema = new Schema(
  name:  type: String, validate: [validate('notEmpty')] 
);

这将生成如下所示的错误消息:

  name: 
    message: 'Validator "String is empty" failed for path name',
     name: 'ValidatorError',
     path: 'name',
     type: 'String is empty'  

我也可以在这里提供自定义错误消息:

var UserSchema = new Schema(
  name:  type: String, validate: [validate(message: 'Name cannot be blank' , 'notEmpty')] 
);

猫鼬required 标志

Mongoose 允许您根据需要定义字段:

var UserSchema = new Schema(
  name:  type: String, required: true 
);

这将生成如下所示的错误消息:

  name: 
    message: 'Validator "required" failed for path name',
     name: 'ValidatorError',
     path: 'name',
     type: 'required'  

问题

感觉好像这些验证器想要您使用他们内置的错误消息。例如,我想将一个字段声明为required,如上所示,但我找不到自定义错误消息的方法。而且 mongoose-validator 模块直到最近才支持自定义消息,这让我认为它们是模型级别的反模式。

实现这些验证器的最佳方式是什么?我应该让他们自己产生错误,然后以某种方式解释它们吗?

【问题讨论】:

... 然后是Middleware Type Validation,无论您传递给next() 的任何类型,它都会作为错误返回。 更不用说验证方法有时可能是异步的。唯一的“一致”解决方案似乎恢复到本机驱动程序并为外部请求放置验证防火墙。 Mongoose 需要为所有错误提供标准的通用接口,包括它自己的错误和来自 mongo 驱动程序的错误。 【参考方案1】:

您需要问自己的问题是,谁首先对导致错误负责?

如果这种情况发生在您的系统中,并且您可以控制,只需像往常一样让错误发生在您身上,并在您进行的过程中清除错误,但我怀疑您正在做的应用程序面临着现实全球用户,而您希望清理他们的输入。

我建议客户端在将输入发送到服务器之前检查输入是否正确,并显示很好的帮助消息,例如“您的用户名必须介于 x 和 y 字符之间”。

然后在服务器端,您希望在 99% 的情况下,输入将直接来自您的净化客户端,因此您仍然使用您已经建议的技术对其进行验证,但如果出现错误,您只需返回一个用户界面的一般错误消息 - 因为您相信您的用户界面会显示帮助消息,因此验证错误必须是由错误或黑客攻击引起的。

请记住记录所有服务器端验证错误,因为它们可能是严重的错误或正在寻找漏洞利用的人。

【讨论】:

只是一个想法。我认为从 RESTful 的角度来看,您仍然希望区分一般错误和验证错误(验证错误为 400?)等。当您编写 frisby 测试时,您需要测试验证并发送带有相应的消息代码。 @ExxKA 不能再同意了。这正是困扰我的事情。【参考方案2】:

来自猫鼬:https://github.com/leepowellcouk/mongoose-validator

错误信息 自定义错误消息现在回到 0.2.1 并且可以通过选项对象进行设置:

validate(message: "String should be between 3 and 50 characters", 'len', 3, 50)


我是如何实现的:

var emailValidator = [validate(message: "Email Address should be between 5 and 64 characters",'len', 5, 64), validate(message: "Email Address is not correct",'isEmail')];

var XXXX = new Schema(
email : type: String, required: true, validate: emailValidator ); 

我的前端处理的是必需的,所以我从不期望猫鼬的“必需”错误会出现在用户面前,更像是后端的安全卫士。

【讨论】:

“我的前端处理的是必需的,所以我从没想过猫鼬的“必需”错误会出现在用户面前,更像是后端的安全防护。”根据您的渐进增强策略,这可能不可靠 - 就好像用户关闭了 javascript(或无法加载)然后它将绕过任何前端验证。如果这种验证对数据完整性很重要……那么它就完全不可靠了。显然,如果您有备份就可以了,但我不希望看到有人在数据库插入方面依赖前端验证。【参考方案3】:

在这一点上,相信 mongoose 如何处理错误似乎是合乎逻辑的。

您不希望您的模型处理错误消息。表示层(控制器?)应该依靠 type 来决定哪个是最好的用户友好消息来显示(考虑 i18n)。

还有using a middleware 可能会进行验证的情况。在这种情况下,将出现在控制器上的错误消息是您传递给 next() 回调的任何内容。

因此,对于中间件,虽然没有记录,但为了在您的模型中保持一致的验证 API,您应该直接使用 Mongoose 的 Error 构造函数:

var mongoose = require('mongoose');
var ValidationError = mongoose.Error.ValidationError;
var ValidatorError  = mongoose.Error.ValidatorError;

schema.pre('save', function (next) 
  if (/someregex/i.test(this.email)) 
    var error = new ValidationError(this);
    error.errors.email = new ValidatorError('email', 'Email is not valid', 'notvalid', this.email);
    return next(error);
  

  next();
);

这样,即使验证错误源自中间件,您也可以确保得到一致的验证错误处理。

为了将错误消息正确匹配到类型,我将创建一个 enum,它将充当所有可能类型的静态映射:

// my controller.js

var ValidationErrors = 
  REQUIRED: 'required',
  NOTVALID: 'notvalid',
  /* ... */
;


app.post('/register', function(req, res)
  var user = new userModel.Model(req.body);

  user.save(function(err)
    if (err) 
      var errMessage = '';

      // go through all the errors...
      for (var errName in err.errors) 
        switch(err.errors[errName].type) 
          case ValidationErrors.REQUIRED:
            errMessage = i18n('Field is required');
            break;
          case ValidationErrors.NOTVALID:
            errMessage = i18n('Field is not valid');
            break;
        
      
      res.send(errMessage);

    
  );
);

【讨论】:

不,不要那样做...应该在发现消息的地方生成消息... 猫鼬处理验证错误的最大问题是内置验证器,例如required,有一个open issue 作为功能请求。 我同意这种方法...例如,'unique' 由 MongoDB 而不是 mongoose 处理,并且具有与 mongoose 错误 obj 不同的形式。我认为编写一个错误解析器来处理自定义错误消息是有意义的。 这是否仍然与 Mongoose v3.8.8 相关,我可以看到文档中有一个示例:Toy.schema.path('color').validate(function (value) return /blue |green|white|red|orange|periwinkle/i.test(value); , '无效颜色');还是建议遵循上述模式?我关注了未解决的问题,它似乎已得到解决?【参考方案4】:

警告:从 Mongoose 4.1.3 开始,函数 ValidatorError 的签名已完全更改,以下信息不再适用:

从 Mongoose 3.8.12 开始,函数 ValidatorError 的签名是:

function ValidatorError (path, msg, type, val) 

类型可以是“无效”或“必需”

例如,如果您的“电子邮件”字段验证引发验证错误,您可以这样做:

var error = new ValidationError(this);
error.errors.email = 
      new ValidatorError('email', "Your err message.", 'notvalid', this.email);

【讨论】:

【参考方案5】:

从 mongoose 4.5.0 开始,Document#invalidate 返回一个 ValidationError。看到这个 https://github.com/Automattic/mongoose/issues/3964

此外,当尝试使 findOneAndUpdate 查询挂钩无效时,您可以这样做:

// pass null because there is no document instance
let err = new ValidationError(null)
err.errors[path] = new ValidatorError(
    path: 'postalCode',
    message: 'postalCode not supported on zones',
    type: 'notvalid',
    value,
)
throw(err)

【讨论】:

你漏掉了value属性名,我觉得正确的做法是value: this.path 我使用的是 ES2015 中引入的速记符号。该变量可能之前已声明过。我不知道,因为代码很旧。【参考方案6】:

我知道验证器插件可能很有帮助,但我认为 mongoose 验证的东西比它真正的复杂更令人生畏。从外面看确实很复杂,但是一旦你开始撕开它,它就不会那么糟糕了。

如果您查看下面的代码,您将看到如何使用内置验证器返回自定义错误消息的示例。

您所要做的就是在设置字段时设置第二个参数以及您自己的自定义错误消息。

查看下面的 requiredminlengthmaxlength 字段以查看我如何设置自定义错误消息,然后查看以下方法以了解如何访问错误对象或将错误对象发送到前端:

// Grab dependencies:
var mongoose = require('mongoose');

// Setup a schema:
var UserSchema = new mongoose.Schema (
    
        username: 
            type: String,
            minlength: [2, 'Username must be at least 2 characters.'],
            maxlength: [20, 'Username must be less than 20 characters.'],
            required: [true, 'Your username cannot be blank.'],
            trim: true,
            unique: true,
            dropDups: true,
        , // end username field
    ,
    
        timestamps: true,
    ,
);

// Export the schema:
module.exports = mongoose.model('User', UserSchema);

以上设置我们的字段具有自定义错误消息。但是我们如何访问它们或将它们发送到我们的前端?我们可以在我们的服务器控制器中设置以下方法,其响应数据被发送回 angular:

var myControllerMethods = 
    register : function(req, res) 
        // Create a user based on the schema we created:
        User.create(req.body)
            .then(function(newUser) 
                console.log('New User Created!', newUser);
                res.json(newUser);
            )
            .catch(function(err) 
                if (err.name == 'ValidationError') 
                    console.error('Error Validating!', err);
                    res.status(422).json(err);
                 else 
                    console.error(err);
                    res.status(500).json(err);
                
            )
    ,
;

如果您运行上面的代码,并且我们的任何 mongoose 验证器未通过,则错误 (err) 对象将被 Promise 中的 .catch() 抓取。如果您在控制台记录此错误,您将在该对象中看到我们的自定义消息,具体取决于标记的错误。

注意:上面的例子只是为 Mongoose 拥有的已经内置的验证添加自定义验证消息(如requiredminlengthmaxlength 等)。

如果您想创建更高级的验证,例如根据正则表达式模式验证字段等,那么您必须创建自定义 validator 函数。

请参阅此链接中的“自定义验证器”部分,了解如何在您的字段中添加验证器的一个很好的示例:http://mongoosejs.com/docs/validation.html

注意:您也可以使用“预保存挂钩”和“实例方法”,但这超出了本问题的范围,内置验证器和“自定义验证器”(上述链接)是更简单的路线。

希望这会有所帮助!

【讨论】:

你不知道mongoose返回的错误是这段代码的验证错误。 +UpTheCreek Promise 中的.catch() 函数会捕获任何因验证失败或创建失败而返回的错误。 好的,我在这些方面添加了一些内容:) +UpTheCreek 我喜欢这个改动,并且使用422的状态!聪明的! +1,祝你一切顺利! :D @twknab unique 参数不是验证器,而是“构建 MongoDB 唯一索引的便捷助手”-[mongoosejs.com/docs/…。您在代码 // username must be unique 中的评论有点误导。【参考方案7】:

查看 hmv 包,它可以帮助您自定义 mongoose 错误消息模板,包括唯一索引错误:

template : PATH_NAME must be at least MIN_LENGTH characters long
schema   :  fullname :  type : String, min : 3, $name : 'Full name'  
message  : Full name must be at least 3 characters long

template : PATH_NAME VALUE have been used, please choose another
schema   :  username :  type : String, unique : true  
message  : username MrBean have been used, please choose another

并且特别支持本地化,例如越南语:

template : PATH_NAME dài ít nhất MIN_LENGTH kí tự
schema   :  fullname :  type : String, min : 3, $name : 'tên tài khoản'  
message  : Tên tài khoản dài ít nhất 5 kí tự

好点是你只需要自定义一次消息模板,而不是像以前的方法那样自定义每个模式的每个字段。

【讨论】:

【参考方案8】:

您也可以使用 joi here。对于验证节点应用程序中的模式,它实际上是非常有用的包。

例如:

const Joi = require("joi");

const validateUser = user => 

 const Schema = 
  email: Joi.string().email().required(),
  name: Joi.string().min(3).max(20).required(),
  password: Joi.string().min(8).max(25).required()
 

 return Joi.validate(user, Schema);


【讨论】:

【参考方案9】:

// 我的 controller.js 或任何你的路由处理程序文件 // 这在 1 行中处理多个模式级别验证

// user object created from model
let user = new Users(
firstName: req.body.firstName,
lastName: req.body.lastName 
...

);

  // Since the Error response are in <Model Name>:<Schema Name>:<Error Message>, ... 
  var err = user.validateSync();
  if (err && err.message) return res.send(err.message.split(':')[2].split(',')[0]);

【讨论】:

以上是关于处理 Mongoose 验证错误——在哪里以及如何处理?的主要内容,如果未能解决你的问题,请参考以下文章

MVC 和 jQuery 验证,在哪里“编织”javascript 以及如何嵌入母版页?

Express.js 项目中在哪里进行验证——在数据库层进行验证(re. Mongoose)?

GraphQL 和表单验证错误

如何在 Express 和 NodeJS 中针对 Mongoose 错误进行错误处理。

Mongoose - 更新时如何验证模型?

在 React 中获取 Mongoose 验证错误消息