# 驗證(validation)
> 原文:[Validation](http://mongoosejs.com/docs/validation.html)
## Validation
在我們進入驗證語法的細節之前,請記住以下的規則:
- 驗證是在[SchemaType](http://mongoosejs.com/docs/schematypes.html)定義
- 驗證是[中間件](http://mongoosejs.com/docs/middleware.html)。Mongoose寄存器驗證作為`pre('save')`鉤子在每個模式默認情況下。
- 你可以手動使用doc運行驗證。`validate(callback) or doc.validateSync()`。
- 驗證程序不運行在未定義的值上。唯一的例外是`required`[驗證器](http://mongoosejs.com/docs/api.html#schematype_SchemaType-required)。
- 驗證異步遞歸;當你調用[Model#save](http://mongoosejs.com/docs/api.html#model_Model-save),子文檔驗證也可以執行。如果出現錯誤,你的 [Model#save](http://mongoosejs.com/docs/api.html#model_Model-save)回調接收它。
- 驗證是可定制的。
```
var schema = new Schema({
name: {
type: String,
required: true
}
});
var Cat = db.model('Cat', schema);
// This cat has no name :(
var cat = new Cat();
cat.save(function(error) {
assert.equal(error.errors['name'].message,
'Path `name` is required.');
error = cat.validateSync();
assert.equal(error.errors['name'].message,
'Path `name` is required.');
});
```
### 內置驗證器
Mongoose有幾個內置驗證器。
- 所有的[schematypes](http://mongoosejs.com/docs/schematypes.html)有內置的[require](http://mongoosejs.com/docs/api.html#schematype_SchemaType-required)驗證器。所需的驗證器使用SchemaType的[checkrequired()函數](http://mongoosejs.com/docs/api.html#schematype_SchemaType-checkRequired)確定的值滿足所需的驗證器。
- 數值([Numbers](http://mongoosejs.com/docs/api.html#schema-number-js) )有最大([man](http://mongoosejs.com/docs/api.html#schema_number_SchemaNumber-max))和最小([min](http://mongoosejs.com/docs/api.html#schema_number_SchemaNumber-min))的驗證器。
- 字符串([String](http://mongoosejs.com/docs/api.html#schema-string-js))有[枚舉](http://mongoosejs.com/docs/api.html#schema_string_SchemaString-enum),[match](http://mongoosejs.com/docs/api.html#schema_string_SchemaString-match),[maxLength](http://mongoosejs.com/docs/api.html#schema_string_SchemaString-maxlength)和[minLength](http://mongoosejs.com/docs/api.html#schema_string_SchemaString-minlength)驗證器。
每一個上述的驗證鏈接提供更多的信息關于如何使他們和自定義錯誤信息。
```
var breakfastSchema = new Schema({
eggs: {
type: Number,
min: [6, 'Too few eggs'],
max: 12
},
bacon: {
type: Number,
required: [true, 'Why no bacon?']
},
drink: {
type: String,
enum: ['Coffee', 'Tea']
}
});
var Breakfast = db.model('Breakfast', breakfastSchema);
var badBreakfast = new Breakfast({
eggs: 2,
bacon: 0,
drink: 'Milk'
});
var error = badBreakfast.validateSync();
assert.equal(error.errors['eggs'].message,
'Too few eggs');
assert.ok(!error.errors['bacon']);
assert.equal(error.errors['drink'].message,
'`Milk` is not a valid enum value for path `drink`.');
badBreakfast.bacon = null;
error = badBreakfast.validateSync();
assert.equal(error.errors['bacon'].message, 'Why no bacon?');
```
### 自定義驗證器
如果內置驗證器是不夠的話,你可以自定義驗證器來適應你的需求。
自定義驗證是通過傳遞驗證函數聲明的。你可以找到詳細說明如何在[SchemaType#validate() API文檔](http://mongoosejs.com/docs/api.html#schematype_SchemaType-validate)。
```
var userSchema = new Schema({
phone: {
type: String,
validate: {
validator: function(v) {
return /\d{3}-\d{3}-\d{4}/.test(v);
},
message: '{VALUE} is not a valid phone number!'
},
required: [true, 'User phone number required']
}
});
var User = db.model('user', userSchema);
var user = new User();
var error;
user.phone = '555.0123';
error = user.validateSync();
assert.equal(error.errors['phone'].message,
'555.0123 is not a valid phone number!');
user.phone = '';
error = user.validateSync();
assert.equal(error.errors['phone'].message,
'User phone number required');
user.phone = '201-555-0123';
// Validation succeeds! Phone number is defined
// and fits `DDD-DDD-DDDD`
error = user.validateSync();
assert.equal(error, null);
```
### 異步的自定義驗證器
自定義驗證器可以異步。如果你的驗證函數接受2個參數,mongoose將視為第二個參數是一個回調。
即使你不想使用異步驗證,小心,因為mongoose 4 視為所有的帶2個參數函數是異步的,像[`validator.isEmail` 功能](http://mongoosejs.com/docs/validation.html)。
```
var userSchema = new Schema({
phone: {
type: String,
validate: {
validator: function(v, cb) {
setTimeout(function() {
cb(/\d{3}-\d{3}-\d{4}/.test(v));
}, 5);
},
message: '{VALUE} is not a valid phone number!'
},
required: [true, 'User phone number required']
}
});
var User = db.model('User', userSchema);
var user = new User();
var error;
user.phone = '555.0123';
user.validate(function(error) {
assert.ok(error);
assert.equal(error.errors['phone'].message,
'555.0123 is not a valid phone number!');
});
```
### 驗證錯誤
驗證失敗后Errors返回一個錯誤的對象實際上是validatorerror對象。每個[ValidatorError](http://mongoosejs.com/docs/api.html#error-validation-js)有kind, path, value, and message屬性。
```
var toySchema = new Schema({
color: String,
name: String
});
var Toy = db.model('Toy', toySchema);
var validator = function (value) {
return /blue|green|white|red|orange|periwinkle/i.test(value);
};
Toy.schema.path('color').validate(validator,
'Color `{VALUE}` not valid', 'Invalid color');
var toy = new Toy({ color: 'grease'});
toy.save(function (err) {
// err is our ValidationError object
// err.errors.color is a ValidatorError object
assert.equal(err.errors.color.message, 'Color `grease` not valid');
assert.equal(err.errors.color.kind, 'Invalid color');
assert.equal(err.errors.color.path, 'color');
assert.equal(err.errors.color.value, 'grease');
assert.equal(err.name, 'ValidationError');
});
```
### Required驗證在嵌套的對象
定義嵌套對象驗證器在mongoose是是棘手的,因為嵌套對象不完全成熟的路徑。
```
var personSchema = new Schema({
name: {
first: String,
last: String
}
});
assert.throws(function() {
// This throws an error, because 'name' isn't a full fledged path
personSchema.path('name').required(true);
}, /Cannot.*'required'/);
// To make a nested object required, use a single nested schema
var nameSchema = new Schema({
first: String,
last: String
});
personSchema = new Schema({
name: {
type: nameSchema,
required: true
}
});
var Person = db.model('Person', personSchema);
var person = new Person();
var error = person.validateSync();
assert.ok(error.errors['name']);
```
### 更新(update)驗證器
上面的例子,你了解了文檔的驗證。Mongoose也支持`update()`和`findoneandupdate()`操作的驗證。在Mongoose 4.x,更新驗證器是默認關閉-您需要指定`runvalidators`選項。
開啟更新驗證器,設置runValidators選項為`update()` 或 `findOneAndUpdate()`。小心:更新驗證器默認關閉因為他們有幾個注意事項。
```
var toySchema = new Schema({
color: String,
name: String
});
var Toy = db.model('Toys', toySchema);
Toy.schema.path('color').validate(function (value) {
return /blue|green|white|red|orange|periwinkle/i.test(value);
}, 'Invalid color');
var opts = { runValidators: true };
Toy.update({}, { color: 'bacon' }, opts, function (err) {
assert.equal(err.errors.color.message,
'Invalid color');
});
```
### 更新(update)驗證器和this
更新(update)驗證器和文檔(document)驗證器有一個主要的區別。在上面的顏色驗證功能中,這是指在使用文檔(document)驗證時所進行的文檔的驗證。然而,運行更新驗證時,被更新的文件可能不在服務器的內存中,所以默認情況下這個值不定義。
```
var toySchema = new Schema({
color: String,
name: String
});
toySchema.path('color').validate(function(value) {
// When running in `validate()` or `validateSync()`, the
// validator can access the document using `this`.
// Does **not** work with update validators.
if (this.name.toLowerCase().indexOf('red') !== -1) {
return value !== 'red';
}
return true;
});
var Toy = db.model('ActionFigure', toySchema);
var toy = new Toy({ color: 'red', name: 'Red Power Ranger' });
var error = toy.validateSync();
assert.ok(error.errors['color']);
var update = { color: 'red', name: 'Red Power Ranger' };
var opts = { runValidators: true };
Toy.update({}, update, opts, function(error) {
// The update validator throws an error:
// "TypeError: Cannot read property 'toLowerCase' of undefined",
// because `this` is **not** the document being updated when using
// update validators
assert.ok(error);
});
```
### context 選項
context選項允許你在更新驗證器設置此值為相關查詢。
```
toySchema.path('color').validate(function(value) {
// When running update validators with the `context` option set to
// 'query', `this` refers to the query object.
if (this.getUpdate().$set.name.toLowerCase().indexOf('red') !== -1) {
return value === 'red';
}
return true;
});
var Toy = db.model('Figure', toySchema);
var update = { color: 'blue', name: 'Red Power Ranger' };
// Note the context option
var opts = { runValidators: true, context: 'query' };
Toy.update({}, update, opts, function(error) {
assert.ok(error.errors['color']);
});
```
### 路徑更新驗證器 (Update Validator Paths)
另一個主要差別,更新驗證器只能運行在指定的路徑中的更新。例如,在下面的示例中,因為在更新操作中沒有指定“name”,更新驗證將成功。
使用更新(update)驗證器,required驗證器驗證失敗時,你要明確地`$unset`這個key。
```
var kittenSchema = new Schema({
name: { type: String, required: true },
age: Number
});
var Kitten = db.model('Kitten', kittenSchema);
var update = { color: 'blue' };
var opts = { runValidators: true };
Kitten.update({}, update, opts, function(err) {
// Operation succeeds despite the fact that 'name' is not specified
});
var unset = { $unset: { name: 1 } };
Kitten.update({}, unset, opts, function(err) {
// Operation fails because 'name' is required
assert.ok(err);
assert.ok(err.errors['name']);
});
```
### 更新指定的路徑只運行驗證器
### (Update Validators Only Run On Specified Paths)
最后值得注意的一個細節:更新(update)驗證器只能運行在 $set 和 $unset 選項。例如,下面的更新將成功,無論數字的值。
```
var testSchema = new Schema({
number: { type: Number, max: 0 },
});
var Test = db.model('Test', testSchema);
var update = { $inc: { number: 1 } };
var opts = { runValidators: true };
Test.update({}, update, opts, function(error) {
// There will never be a validation error here
});
```