Javascript 如何在Mongoose.js中应用字段类型验证而不获得CastError?

Javascript 如何在Mongoose.js中应用字段类型验证而不获得CastError?,javascript,node.js,mongodb,express,mongoose,Javascript,Node.js,Mongodb,Express,Mongoose,我试图使用Express和Mongoose创建一个简单的表单,允许用户向集合中添加新项目 猫鼬模式: var Item = new Schema({ name : {type : String, required : true}, price : {type : Number, required : true}, description : {type: String, required: true}, }); 快线: var create = function(req, r

我试图使用Express和Mongoose创建一个简单的表单,允许用户向集合中添加新项目

猫鼬模式:

var Item = new Schema({
  name   : {type : String, required : true},
  price  : {type : Number, required : true},
  description : {type: String, required: true},
});
快线:

var create = function(req, res) {
  Item.create(req.body, function(err) {
    if (err) return res.render('admin/items/new', {title: "New Item", errors: _.values(err.errors)});
    res.redirect('/admin/items/index');
  });
};
到目前为止还很简单。我要做的是添加适当的验证,以确保用户输入的
price
是数字,如果没有,则显示适当的错误

在上面的代码中,如果用户在price文本字段中键入非数字字符,就会抛出CastError

因此,我尝试将以下验证添加到我的模式中:

Item.path('price').validate(function(v, fn) {
  if (typeof v === 'number' || v === undefined) return fn(true);
  return fn(false);
}, "must-be-numeric");
但是,Mongoose似乎在应用此验证之前尝试强制转换该值,因此不可能为此返回适当的验证错误,您仍然会得到一个强制转换错误。此外,CastError阻止对正在运行的其他字段进行任何验证检查

我认为我正在尝试做的事情是相当普遍的(在Rails中,您只需将
validate:price,:numericality=>true
添加到您的模型中即可)

是否有一个明显的模式是我缺少的,它有助于此类类型验证?

其他人是如何做到这一点的?


我尝试/想到的事情:

  • 我意识到,在将表单字段传递给Mongoose之前,我可以使用二级验证库(如节点验证器)来“预验证”表单字段。然而,我并不真正希望代码重复,也不希望将来自两个验证器的错误对象组合在一起,从而产生一组将反馈给用户的错误
  • 理想情况下,我会使用HTML5的“number”
    input
    元素来防止错误的用户输入,但考虑到Firefox和IE尚不支持它,这是一个非初学者
猫鼬中间件是您的朋友

Item.pre('save' function(next) {
  if (this.price) {
    if (!myValidationFunction(this.price)) {
      return next(new Error('Price must be yada yada yada'))    
    }
  }
  next()
})
pre('save')…事件在mongoose级别验证之前触发,因此您可以更好地控制错误的生成方式。然后,您可以在express中设置一个通用的错误处理中间件,该中间件可以捕获所有未捕获的错误,并按需格式化错误消息。express 2.x在('error'…事件,但已在Express 3中删除

本质上,错误捕获中间件就是这样做的

app.use(function(err, req, res, next) {
  if (!(err instanceof Error)) next()
  // First param was some kind of error
  // format and send err.message however you like
  // in dev mode err.stack is usually interesting
  // this code is usually terminal, and does not call next()
})

使用mongoose中间件时使用的两个技巧没有很好的文档记录:

首先,若要使保存失败,只需将错误对象传递给下一个。数据库将不会被触动。其次,您可以通过检查this.isNew属性来区分插入和更新

使用这种模式的一个优点是大多数流行的节点库都使用它,因此您可以满怀信心地整合错误处理,无论出现什么错误,您都有机会设置合理的格式

最后一点注意,如果您通常采用这种方法,您可能会发现自己希望在node中创建自己的自定义错误对象。这些方法非常有效,但设置起来有点棘手。下面是一篇好文章:


希望这会有所帮助!

我知道这是一篇非常古老的文章,我的回答可能对你没有多大帮助,但我还是为了那些将要使用这篇文章的用户而发布这篇文章

我个人建议在用户提交数据后使用(即在数据提交给mongoose之前)。这样,您可以验证用户输入本身,这是一个非常安全的想法。其次,这会创建一个清晰的代码,因为mongoose只允许您处理数据库,而不是您所寻找的所有自定义验证

您可以使用一个使用节点验证器的中间件。这是处理验证的一个非常好的方法

虽然有文档记录,但我个人并不认为预保存实际上是为了验证。它主要用于创建任何引用数据或发出事件,以便在代码的其他部分使用


我希望您也会觉得这很有用。

还有另一种方法。您可以使用自定义设置器和自定义验证器:

var setNumberOrUndefined = function (val) {

    // this prevents set undefined if the user did not
    // enter any value
    if (val == '')
        return null

    // Return undefined prevents CastError
    // now a validator must validate if it's a number or not
    var v = Number(val)
    return (isNaN(v))? undefined : v

}

var isNumberOrEmpty = function (val) {

    // This prevents return false if the user did not
    // enter any value
    if (val === null)
        return true
    else
        return 'number' == typeof val
}

var Item = new Schema({
    name   : {type : String, required : true},
    price  : {
        type : Number, 
        required : true,
        set: setNumberOrUndefined,
        validate: [
            {validator: isNumberOrEmpty, msg: 'Price must be numeric!'},
        ]
    },
    description : {type: String, required: true},
});

谢谢-我可以看出使用Mongoose中间件可以解决这个问题,但这仍然是一个解决办法。我希望能够应用并立即报告所有模型的验证。使用中间件方法,我只会从自定义中间件中得到一个错误。我还注意到,如果您试图分配一个错误类型的变量设置为mongoose模型路径,模型将忽略分配。这种无声的失败会让人感觉不舒服。它并没有真正忽略分配。它只是隐藏了分配,这肯定更糟。