验证 - 图1mongoose

验证 - 图2mongoose

[

验证 ](#验证)

如果你要使用验证,请注意一下几点:

  • 验证定义于 SchemaType
  • 验证是一个中间件。它默认作为 pre('save')` 钩子注册在 schema 上
  • 你可以使用 doc.validate(callback)doc.validateSync() 手动验证
  • 验证器不对未定义的值进行验证,唯一例外是 required 验证器
  • 验证是异步递归的。当你调用 Model#save,子文档验证也会执行,出错的话 Model#save 回调会接收错误
  • 验证是可定制的
  1. var schema = new Schema({
  2. name: {
  3. type: String,
  4. required: true
  5. }
  6. });
  7. var Cat = db.model('Cat', schema);
  8. // This cat has no name :(
  9. var cat = new Cat();
  10. cat.save(function(error) {
  11. assert.equal(error.errors['name'].message,
  12. 'Path `name` is required.');
  13. error = cat.validateSync();
  14. assert.equal(error.errors['name'].message,
  15. 'Path `name` is required.');
  16. });

[

内建 Validators ](#内建-validators)

Mongoose 有一些内建验证器。

上面的链接提供了使用和错误处理相关的详细信息

  1. var breakfastSchema = new Schema({
  2. eggs: {
  3. type: Number,
  4. min: [6, 'Too few eggs'],
  5. max: 12
  6. },
  7. bacon: {
  8. type: Number,
  9. required: [true, 'Why no bacon?']
  10. },
  11. drink: {
  12. type: String,
  13. enum: ['Coffee', 'Tea'],
  14. required: function() {
  15. return this.bacon > 3;
  16. }
  17. }
  18. });
  19. var Breakfast = db.model('Breakfast', breakfastSchema);
  20. var badBreakfast = new Breakfast({
  21. eggs: 2,
  22. bacon: 0,
  23. drink: 'Milk'
  24. });
  25. var error = badBreakfast.validateSync();
  26. assert.equal(error.errors['eggs'].message,
  27. 'Too few eggs');
  28. assert.ok(!error.errors['bacon']);
  29. assert.equal(error.errors['drink'].message,
  30. '`Milk` is not a valid enum value for path `drink`.');
  31. badBreakfast.bacon = 5;
  32. badBreakfast.drink = null;
  33. error = badBreakfast.validateSync();
  34. assert.equal(error.errors['drink'].message, 'Path `drink` is required.');
  35. badBreakfast.bacon = null;
  36. error = badBreakfast.validateSync();
  37. assert.equal(error.errors['bacon'].message, 'Why no bacon?');

[

unique 不是验证器 ](#unique-不是验证器)

初学者常见的 unique 选项 不是验证器。这是构建 MongoDB unique indexes 的辅助函数。 详见 FAQ

  1. var uniqueUsernameSchema = new Schema({
  2. username: {
  3. type: String,
  4. unique: true
  5. }
  6. });
  7. var U1 = db.model('U1', uniqueUsernameSchema);
  8. var U2 = db.model('U2', uniqueUsernameSchema);
  9. var dup = [{ username: 'Val' }, { username: 'Val' }];
  10. U1.create(dup, function(error) {
  11. // Race condition! This may save successfully, depending on whether
  12. // MongoDB built the index before writing the 2 docs.
  13. });
  14. // Need to wait for the index to finish building before saving,
  15. // otherwise unique constraints may be violated.
  16. U2.once('index', function(error) {
  17. assert.ifError(error);
  18. U2.create(dup, function(error) {
  19. // Will error, but will *not* be a mongoose validation error, it will be
  20. // a duplicate key error.
  21. assert.ok(error);
  22. assert.ok(!error.errors);
  23. assert.ok(error.message.indexOf('duplicate key error') !== -1);
  24. });
  25. });
  26. // There's also a promise-based equivalent to the event emitter API.
  27. // The `init()` function is idempotent and returns a promise that
  28. // will resolve once indexes are done building;
  29. U2.init().then(function() {
  30. U2.create(dup, function(error) {
  31. // Will error, but will *not* be a mongoose validation error, it will be
  32. // a duplicate key error.
  33. assert.ok(error);
  34. assert.ok(!error.errors);
  35. assert.ok(error.message.indexOf('duplicate key error') !== -1);
  36. });
  37. });

[

自定义验证器 ](#自定义验证器)

如果内建检验器不够用了,你可以定义满足自己需要的检验器

自定义检验器通过传入一个检验函数来定义,更多细节请看 SchemaType#validate() API 文档

  1. var userSchema = new Schema({
  2. phone: {
  3. type: String,
  4. validate: {
  5. validator: function(v) {
  6. return /\d{3}-\d{3}-\d{4}/.test(v);
  7. },
  8. message: '{VALUE} is not a valid phone number!'
  9. },
  10. required: [true, 'User phone number required']
  11. }
  12. });
  13. var User = db.model('user', userSchema);
  14. var user = new User();
  15. var error;
  16. user.phone = '555.0123';
  17. error = user.validateSync();
  18. assert.equal(error.errors['phone'].message,
  19. '555.0123 is not a valid phone number!');
  20. user.phone = '';
  21. error = user.validateSync();
  22. assert.equal(error.errors['phone'].message,
  23. 'User phone number required');
  24. user.phone = '201-555-0123';
  25. // Validation succeeds! Phone number is defined
  26. // and fits `DDD-DDD-DDDD`
  27. error = user.validateSync();
  28. assert.equal(error, null);

[

异步自定义验证器 ](#异步自定义验证器)

自定义检验器可以是异步的。如果检验函数 返回 promise (像 async 函数), mongoose 将会等待该 promise 完成。 如果你更喜欢使用回调函数,设置 isAsync 选项, mongoose 会将回调函数作为验证函数的第二个参数。

  1. var userSchema = new Schema({
  2. name: {
  3. type: String,
  4. // You can also make a validator async by returning a promise. If you
  5. // return a promise, do **not** specify the `isAsync` option.
  6. validate: function(v) {
  7. return new Promise(function(resolve, reject) {
  8. setTimeout(function() {
  9. resolve(false);
  10. }, 5);
  11. });
  12. }
  13. },
  14. phone: {
  15. type: String,
  16. validate: {
  17. isAsync: true,
  18. validator: function(v, cb) {
  19. setTimeout(function() {
  20. var phoneRegex = /\d{3}-\d{3}-\d{4}/;
  21. var msg = v + ' is not a valid phone number!';
  22. // 第一个参数是布尔值,代表验证结果
  23. // 第二个参数是报错信息
  24. cb(phoneRegex.test(v), msg);
  25. }, 5);
  26. },
  27. // 默认报错信息会被 `cb()` 第二个参数覆盖
  28. message: 'Default error message'
  29. },
  30. required: [true, 'User phone number required']
  31. }
  32. });
  33. var User = db.model('User', userSchema);
  34. var user = new User();
  35. var error;
  36. user.phone = '555.0123';
  37. user.name = 'test';
  38. user.validate(function(error) {
  39. assert.ok(error);
  40. assert.equal(error.errors['phone'].message,
  41. '555.0123 is not a valid phone number!');
  42. assert.equal(error.errors['name'].message,
  43. 'Validator failed for path `name` with value `test`');
  44. });

[

验证错误 ](#验证错误)

验证失败返回的 err 包含一个 ValidatorError 对象。 每一个 ValidatorError 都有 kindpathvaluemessage 属性。 ValidatorError 也可能有 reason 属性, 如果检验器抛出错误,这个属性会包含该错误原因。

  1. var toySchema = new Schema({
  2. color: String,
  3. name: String
  4. });
  5. var validator = function(value) {
  6. return /red|white|gold/i.test(value);
  7. };
  8. toySchema.path('color').validate(validator,
  9. 'Color `{VALUE}` not valid', 'Invalid color');
  10. toySchema.path('name').validate(function(v) {
  11. if (v !== 'Turbo Man') {
  12. throw new Error('Need to get a Turbo Man for Christmas');
  13. }
  14. return true;
  15. }, 'Name `{VALUE}` is not valid');
  16. var Toy = db.model('Toy', toySchema);
  17. var toy = new Toy({ color: 'Green', name: 'Power Ranger' });
  18. toy.save(function (err) {
  19. // `err` is a ValidationError object
  20. // `err.errors.color` is a ValidatorError object
  21. assert.equal(err.errors.color.message, 'Color `Green` not valid');
  22. assert.equal(err.errors.color.kind, 'Invalid color');
  23. assert.equal(err.errors.color.path, 'color');
  24. assert.equal(err.errors.color.value, 'Green');
  25. // mongoose 5 新特性,如果验证器抛错,
  26. // mongoose 会使用该错误信息。如果验证器返回 `false`,
  27. // mongoose 会使用 'Name `Power Ranger` is not valid'。
  28. assert.equal(err.errors.name.message,
  29. 'Need to get a Turbo Man for Christmas');
  30. assert.equal(err.errors.name.value, 'Power Ranger');
  31. // If your validator threw an error, the `reason` property will contain
  32. // the original error thrown, including the original stack trace.
  33. assert.equal(err.errors.name.reason.message,
  34. 'Need to get a Turbo Man for Christmas');
  35. assert.equal(err.name, 'ValidationError');
  36. });

[

嵌套对象中的 Required 检验器 ](#嵌套对象中的-required-检验器)

定义嵌套对象的验证器需要特别注意。

  1. var personSchema = new Schema({
  2. name: {
  3. first: String,
  4. last: String
  5. }
  6. });
  7. assert.throws(function() {
  8. // 这里会报错,因为 'name' 不是“完整成熟的路径”
  9. personSchema.path('name').required(true);
  10. }, /Cannot.*'required'/);
  11. // 要让嵌套对象 required,要使用单独的嵌套 schema
  12. var nameSchema = new Schema({
  13. first: String,
  14. last: String
  15. });
  16. personSchema = new Schema({
  17. name: {
  18. type: nameSchema,
  19. required: true
  20. }
  21. });
  22. var Person = db.model('Person', personSchema);
  23. var person = new Person();
  24. var error = person.validateSync();
  25. assert.ok(error.errors['name']);

[

Update 验证器 ](#update-验证器)

上例中,你学习了 document 的验证。Mongoose 还支持验证 update()findOneAndUpdate() 操作。 Update 验证器默认关闭,如需打开,请另外配置 runValidators

注意:update 验证器默认关闭是因为里面有几个注意事项必须先了解。

  1. var toySchema = new Schema({
  2. color: String,
  3. name: String
  4. });
  5. var Toy = db.model('Toys', toySchema);
  6. Toy.schema.path('color').validate(function (value) {
  7. return /blue|green|white|red|orange|periwinkle/i.test(value);
  8. }, 'Invalid color');
  9. var opts = { runValidators: true };
  10. Toy.update({}, { color: 'bacon' }, opts, function (err) {
  11. assert.equal(err.errors.color.message,
  12. 'Invalid color');
  13. });

[

Update 验证器与 this ](#update-验证器与-this)

update 验证器和 document 验证器有诸多不同。 上面的颜色验证函数,this 指向验证中的 document 。 然而 update 验证器运行时,被更新文档不一定存在于服务器内存, 所以 this 值未定义。

  1. var toySchema = new Schema({
  2. color: String,
  3. name: String
  4. });
  5. toySchema.path('color').validate(function(value) {
  6. // When running in `validate()` or `validateSync()`, the
  7. // validator can access the document using `this`.
  8. // Does **not** work with update validators.
  9. if (this.name.toLowerCase().indexOf('red') !== -1) {
  10. return value !== 'red';
  11. }
  12. return true;
  13. });
  14. var Toy = db.model('ActionFigure', toySchema);
  15. var toy = new Toy({ color: 'red', name: 'Red Power Ranger' });
  16. var error = toy.validateSync();
  17. assert.ok(error.errors['color']);
  18. var update = { color: 'red', name: 'Red Power Ranger' };
  19. var opts = { runValidators: true };
  20. Toy.update({}, update, opts, function(error) {
  21. // The update validator throws an error:
  22. // "TypeError: Cannot read property 'toLowerCase' of undefined",
  23. // because `this` is **not** the document being updated when using
  24. // update validators
  25. assert.ok(error);
  26. });

[

context 选项 ](#context-选项)

context 选项允许你把 update 验证器的 this 设置为 query

  1. toySchema.path('color').validate(function(value) {
  2. // When running update validators with the `context` option set to
  3. // 'query', `this` refers to the query object.
  4. if (this.getUpdate().$set.name.toLowerCase().indexOf('red') !== -1) {
  5. return value === 'red';
  6. }
  7. return true;
  8. });
  9. var Toy = db.model('Figure', toySchema);
  10. var update = { color: 'blue', name: 'Red Power Ranger' };
  11. // Note the context option
  12. var opts = { runValidators: true, context: 'query' };
  13. Toy.update({}, update, opts, function(error) {
  14. assert.ok(error.errors['color']);
  15. });

[

Update 验证器字段路径 ](#update-验证器字段路径)

另一个关键不同点是 update 验证器只运行于更新的字段。 下例中,因为 'name' 在更新操作未被指定,所以此次更新操作成功。

使用 update 验证器的时候, required 验证器只会在你对某个字段显式使用 $unset 才会触发。

  1. var kittenSchema = new Schema({
  2. name: { type: String, required: true },
  3. age: Number
  4. });
  5. var Kitten = db.model('Kitten', kittenSchema);
  6. var update = { color: 'blue' };
  7. var opts = { runValidators: true };
  8. Kitten.update({}, update, opts, function(err) {
  9. // 即使 'name' 没有指定也操作成功了
  10. });
  11. var unset = { $unset: { name: 1 } };
  12. Kitten.update({}, unset, opts, function(err) {
  13. // 'name' required, 操作失败
  14. assert.ok(err);
  15. assert.ok(err.errors['name']);
  16. });

[

Update 验证器只运行于指定字段路径 ](#update-验证器只运行于指定字段路径)

最后要注意的是:update 验证器运行于下列更新操作:

  • $set
  • $unset
  • $push (>= 4.8.0)
  • $addToSet (>= 4.8.0)
  • $pull (>= 4.12.0)
  • $pullAll (>= 4.12.0)

例如,以下 update 成功执行,不管 number 的值,因为 update 验证器 无视 $inc 。同样, $push$addToSet$pull$pullAll 验证器 不会对数组自身验证,只会对数组中的元素验证。

  1. var testSchema = new Schema({
  2. number: { type: Number, max: 0 },
  3. arr: [{ message: { type: String, maxlength: 10 } }]
  4. });
  5. // Update 验证器不会作检查,所以你再仍然可以 `$push` 两个元素到数组
  6. // 只要他们的 `message` 没有超长
  7. testSchema.path('arr').validate(function(v) {
  8. return v.length < 2;
  9. });
  10. var Test = db.model('Test', testSchema);
  11. var update = { $inc: { number: 1 } };
  12. var opts = { runValidators: true };
  13. Test.update({}, update, opts, function(error) {
  14. // 这里不会报错
  15. update = { $push: [{ message: 'hello' }, { message: 'world' }] };
  16. Test.update({}, update, opts, function(error) {
  17. // 这里也不会报错
  18. });
  19. });

[

$push 和 $addToSet ](#$push-和-$addtoset)

4.8.0 新特性: update 验证器也运行于 $push$addToSet

  1. var testSchema = new Schema({
  2. numbers: [{ type: Number, max: 0 }],
  3. docs: [{
  4. name: { type: String, required: true }
  5. }]
  6. });
  7. var Test = db.model('TestPush', testSchema);
  8. var update = {
  9. $push: {
  10. numbers: 1,
  11. docs: { name: null }
  12. }
  13. };
  14. var opts = { runValidators: true };
  15. Test.update({}, update, opts, function(error) {
  16. assert.ok(error.errors['numbers']);
  17. assert.ok(error.errors['docs']);
  18. });