# 验证（validation）

> 原文：[Validation](http://mongoosejs.com/docs/validation.html)\
> 翻译：小虾米（QQ:509129）

## 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)回调接收它。
* 验证是可定制的。

```javascript
 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)验证器。

每一个上述的验证链接提供更多的信息关于如何使他们和自定义错误信息。

```javascript
 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)。

```javascript
  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)。

```javascript
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属性。

```javascript
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是是棘手的，因为嵌套对象不完全成熟的路径。

```javascript
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()`。小心：更新验证器默认关闭因为他们有几个注意事项。

```javascript
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）验证时所进行的文档的验证。然而，运行更新验证时，被更新的文件可能不在服务器的内存中，所以默认情况下这个值不定义。

```javascript
 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选项允许你在更新验证器设置此值为相关查询。

```javascript
 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。

```javascript
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 选项。例如，下面的更新将成功，无论数字的值。

```javascript
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
    });
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://mongoose.shujuwajue.com/guide/validation.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
