Validation验证

Before we get into the specifics of validation syntax, please keep the following rules in mind:在我们进入验证语法的细节之前,请记住以下规则:

  • Validation is defined in the SchemaType验证是在SchemaType中定义的
  • Validation is middleware. 验证是中间件Mongoose registers validation as a pre('save') hook on every schema by default.默认情况下,Mongoose将验证注册为每个模式上的pre('save')挂钩。
  • Validation always runs as the first pre('save') hook. 验证始终作为第一个pre('save')挂钩运行。This means that validation doesn't run on any changes you make in pre('save') hooks.这意味着验证不会在pre('save')挂钩中所做的任何更改上运行。
  • You can disable automatic validation before save by setting the validateBeforeSave option您可以通过设置validateBeforeSave选项来禁用保存前的自动验证
  • You can manually run validation using doc.validate() or doc.validateSync()您可以使用doc.validate()doc.validateSync()手动运行验证
  • You can manually mark a field as invalid (causing validation to fail) by using doc.invalidate(...)您可以使用doc.invalidate(...)手动将字段标记为无效(导致验证失败)
  • Validators are not run on undefined values. The only exception is the required validator.验证程序不是在未定义的值上运行的。唯一的例外是required验证器
  • When you call Model#save, Mongoose also runs subdocument validation. 当您调用Model#save时,Mongoose还会运行子文档验证。If an error occurs, your Model#save promise rejects如果发生错误,您的Model#save承诺将被拒绝
  • Validation is customizable验证是可自定义的
const schema = new Schema({
name: {
type: String,
required: true
}
});
const Cat = db.model('Cat', schema);

// This cat has no name :(
const cat = new Cat();

let error;
try {
await cat.save();
} catch (err) {
error = err;
}

assert.equal(error.errors['name'].message,
'Path `name` is required.');

error = cat.validateSync();
assert.equal(error.errors['name'].message,
'Path `name` is required.');

Built-in Validators内置验证程序

Mongoose has several built-in validators.Mongoose有几个内置的验证器。

Each of the validator links above provide more information about how to enable them and customize their error messages.上面的每个验证器链接都提供了关于如何启用它们和自定义它们的错误消息的更多信息。

const 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'],
required: function() {
return this.bacon > 3;
}
}
});
const Breakfast = db.model('Breakfast', breakfastSchema);

const badBreakfast = new Breakfast({
eggs: 2,
bacon: 0,
drink: 'Milk'
});
let 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 = 5;
badBreakfast.drink = null;

error = badBreakfast.validateSync();
assert.equal(error.errors['drink'].message, 'Path `drink` is required.');

badBreakfast.bacon = null;
error = badBreakfast.validateSync();
assert.equal(error.errors['bacon'].message, 'Why no bacon?');

Custom Error Messages自定义错误消息

You can configure the error message for individual validators in your schema. 您可以为模式中的各个验证器配置错误消息。There are two equivalent ways to set the validator error message:有两种等效的方法可以设置验证器错误消息:

  • Array syntax: 数组语法:min: [6, 'Must be at least 6, got {VALUE}']
  • Object syntax: 对象语法:enum: { values: ['Coffee', 'Tea'], message: '{VALUE} is not supported' }

Mongoose also supports rudimentary templating for error messages. Mongoose还支持错误消息的基本模板。Mongoose replaces {VALUE} with the value being validated.Mongoose将{VALUE}替换为正在验证的值。

const breakfastSchema = new Schema({
eggs: {
type: Number,
min: [6, 'Must be at least 6, got {VALUE}'],
max: 12
},
drink: {
type: String,
enum: {
values: ['Coffee', 'Tea'],
message: '{VALUE} is not supported'
}
}
});
const Breakfast = db.model('Breakfast', breakfastSchema);

const badBreakfast = new Breakfast({
eggs: 2,
drink: 'Milk'
});
const error = badBreakfast.validateSync();
assert.equal(error.errors['eggs'].message,
'Must be at least 6, got 2');
assert.equal(error.errors['drink'].message, 'Milk is not supported');

The unique Option is Not a Validatorunique选项不是验证程序

A common gotcha for beginners is that the unique option for schemas is not a validator. 对于初学者来说,一个常见的难题是模式的unique选项不是验证器。It's a convenient helper for building MongoDB unique indexes. 它是构建MongoDB唯一索引的方便助手。See the FAQ for more information.有关更多信息,请参阅常见问题解答

const uniqueUsernameSchema = new Schema({
username: {
type: String,
unique: true
}
});
const U1 = db.model('U1', uniqueUsernameSchema);
const U2 = db.model('U2', uniqueUsernameSchema);

const dup = [{ username: 'Val' }, { username: 'Val' }];
// Race condition! This may save successfully, depending on whether MongoDB built the index before writing the 2 docs.比赛条件!这可能会成功保存,这取决于MongoDB是否在编写2个文档之前构建了索引。
U1.create(dup).
then(() => {
}).
catch(err => {
});

// You need to wait for Mongoose to finish building the `unique` index before writing. 在编写之前,您需要等待Mongoose完成唯一索引的构建。
// You only need to build indexes once for a given collection, so you normally don't need to do this in production.对于给定的集合,只需要构建一次索引,所以在生产中通常不需要这样做。
// But, if you drop the database between tests, you will need to use `init()` to wait for the index build to finish.但是,如果在测试之间丢弃数据库,则需要使用`init()`来等待索引构建完成。
U2.init().
then(() => U2.create(dup)).
catch(error => {
// `U2.create()` will error, but will *not* be a mongoose validation error, it will be a duplicate key error.将出错,但**不会**是mongoose验证错误,这将是一个重复的密钥错误。
// See: 请参阅https://masteringjs.io/tutorials/mongoose/e11000-duplicate-key
assert.ok(error);
assert.ok(!error.errors);
assert.ok(error.message.indexOf('duplicate key error') !== -1);
});

Custom Validators自定义验证程序

If the built-in validators aren't enough, you can define custom validators to suit your needs.如果内置的验证器还不够,您可以定义自定义验证器来满足您的需求。

Custom validation is declared by passing a validation function. 通过传递验证函数来声明自定义验证。You can find detailed instructions on how to do this in the SchemaType#validate() API docs.您可以在SchemaType#validate()API文档中找到有关如何执行此操作的详细说明。

const userSchema = new Schema({
phone: {
type: String,
validate: {
validator: function(v) {
return /\d{3}-\d{3}-\d{4}/.test(v);
},
message: props => `${props.value} is not a valid phone number!`
},
required: [true, 'User phone number required']
}
});

const User = db.model('user', userSchema);
const user = new User();
let 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`验证成功!电话号码已定义并符合`DDD-DDD-DDD`
error = user.validateSync();
assert.equal(error, null);

Async Custom Validators异步自定义验证程序

Custom validators can also be asynchronous. If your validator function returns a promise (like an async function), mongoose will wait for that promise to settle. 自定义验证器也可以是异步的。如果您的验证器函数返回一个promise(像async函数一样),mongoose将等待该promise结束。If the returned promise rejects, or fulfills with the value false, Mongoose will consider that a validation error.如果返回的promise拒绝,或者用false值实现,Mongoose将认为这是一个验证错误。

const userSchema = new Schema({
name: {
type: String,
// You can also make a validator async by returning a promise.您还可以通过返回promise来使验证器异步。
validate: () => Promise.reject(new Error('Oops!'))
},
email: {
type: String,
// There are two ways for an promise-based async validator to fail:基于promise的异步验证器有两种失败方式:
// 1) If the promise rejects, Mongoose assumes the validator failed with the given error.如果promise拒绝,Mongoose将假定验证器失败并出现给定的错误。
// 2) If the promise resolves to `false`, Mongoose assumes the validator failed and creates an error with the given `message`.如果promise解析为false,Mongoose将假定验证器失败,并使用给定的`message`创建一个错误。
validate: {
validator: () => Promise.resolve(false),
message: 'Email validation failed'
}
}
});

const User = db.model('User', userSchema);
const user = new User();

user.email = 'test@test.co';
user.name = 'test';

let error;
try {
await user.validate();
} catch (err) {
error = err;
}
assert.ok(error);
assert.equal(error.errors['name'].message, 'Oops!');
assert.equal(error.errors['email'].message, 'Email validation failed');

Validation Errors验证错误

Errors returned after failed validation contain an errors object whose values are ValidatorError objects. 验证失败后返回的errors包含一个错误对象,其值为ValidatorError对象。Each ValidatorError has kind, path, value, and message properties. 每个ValidatorError都有kindpathvaluemessage属性。A ValidatorError also may have a reason property. ValidatorError也可能有一个reason属性。If an error was thrown in the validator, this property will contain the error that was thrown.如果在验证器中引发错误,则此属性将包含引发的错误。

const toySchema = new Schema({
color: String,
name: String
});

const validator = function(value) {
return /red|white|gold/i.test(value);
};
toySchema.path('color').validate(validator,
'Color `{VALUE}` not valid', 'Invalid color');
toySchema.path('name').validate(function(v) {
if (v !== 'Turbo Man') {
throw new Error('Need to get a Turbo Man for Christmas');
}
return true;
}, 'Name `{VALUE}` is not valid');

const Toy = db.model('Toy', toySchema);

const toy = new Toy({ color: 'Green', name: 'Power Ranger' });

let error;
try {
await toy.save();
} catch (err) {
error = err;
}

// `error` is a ValidationError object是ValidationError对象
// `error.errors.color` is a ValidatorError object是ValidatorError对象
assert.equal(error.errors.color.message, 'Color `Green` not valid');
assert.equal(error.errors.color.kind, 'Invalid color');
assert.equal(error.errors.color.path, 'color');
assert.equal(error.errors.color.value, 'Green');

// If your validator throws an exception, mongoose will use the error message.如果您的验证器抛出异常,mongoose将使用错误消息。
// If your validator returns `false`, mongoose will use the 'Name `Power Ranger` is not valid' message.如果您的验证器返回`false`,mongoose将使用`Name Power Ranger is not valid`消息。
assert.equal(error.errors.name.message,
'Need to get a Turbo Man for Christmas');
assert.equal(error.errors.name.value, 'Power Ranger');
// If your validator threw an error, the `reason` property will contain the original error thrown, including the original stack trace.如果您的验证器抛出了一个错误,那么`reason`属性将包含抛出的原始错误,包括原始堆栈跟踪。
assert.equal(error.errors.name.reason.message,
'Need to get a Turbo Man for Christmas');

assert.equal(error.name, 'ValidationError');

Cast Errors

Before running validators, Mongoose attempts to coerce values to the correct type. 在运行验证器之前,Mongoose尝试将值强制转换为正确的类型。This process is called casting the document. 这个过程称为铸造文档。If casting fails for a given path, the error.errors object will contain a CastError object.如果给定路径的强制转换失败,error.errors对象将包含一个CastError对象。

Casting runs before validation, and validation does not run if casting fails. 铸造在验证之前运行,如果铸造失败,则不运行验证。That means your custom validators may assume v is null, undefined, or an instance of the type specified in your schema.这意味着您的自定义验证器可能会假设vnullundefined或是您的模式中指定的类型的实例。

const vehicleSchema = new mongoose.Schema({
numWheels: { type: Number, max: 18 }
});
const Vehicle = db.model('Vehicle', vehicleSchema);

const doc = new Vehicle({ numWheels: 'not a number' });
const err = doc.validateSync();

err.errors['numWheels'].name; // 'CastError'
// 'Cast to Number failed for value "not a number" at path "numWheels"'
err.errors['numWheels'].message;

Global SchemaType Validation全局架构类型验证

In addition to defining custom validators on individual schema paths, you can also configure a custom validator to run on every instance of a given SchemaType. 除了在各个模式路径上定义自定义验证器之外,您还可以配置一个自定义验证器,使其在给定SchemaType的每个实例上运行。For example, the following code demonstrates how to make empty string '' an invalid value for all string paths.例如,以下代码演示如何使空字符串''成为所有字符串路径的无效值。

// Add a custom validator to all strings向所有字符串添加自定义验证器
mongoose.Schema.Types.String.set('validate', v => v == null || v > 0);

const userSchema = new Schema({
name: String,
email: String
});
const User = db.model('User', userSchema);

const user = new User({ name: '', email: '' });

const err = await user.validate().then(() => null, err => err);
err.errors['name']; // ValidatorError
err.errors['email']; // ValidatorError

Required Validators On Nested Objects嵌套对象上所需的验证程序

Defining validators on nested objects in mongoose is tricky, because nested objects are not fully fledged paths.在猫鼬中为嵌套对象定义验证器是很棘手的,因为嵌套对象不是完全成熟的路径。

let personSchema = new Schema({
name: {
first: String,
last: String
}
});

assert.throws(function() {
// This throws an error, because 'name' isn't a full fledged path这引发了一个错误,因为“name”不是一个完整的路径
personSchema.path('name').required(true);
}, /Cannot.*'required'/);

// To make a nested object required, use a single nested schema若要使嵌套对象成为必需,请使用单个嵌套模式
const nameSchema = new Schema({
first: String,
last: String
});

personSchema = new Schema({
name: {
type: nameSchema,
required: true
}
});

const Person = db.model('Person', personSchema);

const person = new Person();
const error = person.validateSync();
assert.ok(error.errors['name']);

Update Validators更新验证程序

In the above examples, you learned about document validation. 在上面的示例中,您了解了文档验证。Mongoose also supports validation for update(), updateOne(), updateMany(), and findOneAndUpdate() operations. Mongoose还支持update()updateOne()updateMany()findOneAndUpdate()操作的验证。Update validators are off by default - you need to specify the runValidators option.默认情况下,更新验证器是关闭的-您需要指定runValidators选项。

To turn on update validators, set the runValidators option for update(), updateOne(), updateMany(), or findOneAndUpdate(). 要打开更新验证器,请为update()updateOne()updateMany()findOneAndUpdate()设置runValidators选项。Be careful: update validators are off by default because they have several caveats.请注意:更新验证器在默认情况下是关闭的,因为它们有几个注意事项。

const toySchema = new Schema({
color: String,
name: String
});

const Toy = db.model('Toys', toySchema);

Toy.schema.path('color').validate(function(value) {
return /red|green|blue/i.test(value);
}, 'Invalid color');

const opts = { runValidators: true };

let error;
try {
await Toy.updateOne({}, { color: 'not a color' }, opts);
} catch (err) {
error = err;
}

assert.equal(error.errors.color.message, 'Invalid color');

Update Validators and 更新验证程序和this

There are a couple of key differences between update validators and document validators. 更新验证器和文档验证器之间有几个关键区别。In the color validation function below, this refers to the document being validated when using document validation. 在下面的颜色验证功能中,this引用使用文档验证时要验证的文档。However, when running update validators, this refers to the query object instead of the document. 但是,当运行更新验证器时,this引用的是查询对象,而不是文档。Because queries have a neat .get() function, you can get the updated value of the property you want.因为查询有一个整洁的.get()函数,所以可以获得所需属性的更新值。

const 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`.当在`validate()`或`validateSync()`中运行时,验证器可以使用它访问文档。
// When running with update validators, `this` is the Query, **not** the document being updated!当使用更新验证器运行时`this`是查询,**而不是**正在更新的文档!
// Queries have a `get()` method that lets you get the updated value.查询有一个`get()`方法,用于获取更新后的值。
if (this.get('name') && this.get('name').toLowerCase().indexOf('red') !== -1) {
return value === 'red';
}
return true;
});

const Toy = db.model('ActionFigure', toySchema);

const toy = new Toy({ color: 'green', name: 'Red Power Ranger' });
// Validation failed: color: Validator failed for path `color` with value `green`验证失败:`color`值为`green`的路径颜色的验证程序失败
let error = toy.validateSync();
assert.ok(error.errors['color']);

const update = { color: 'green', name: 'Red Power Ranger' };
const opts = { runValidators: true };

error = null;
try {
await Toy.updateOne({}, update, opts);
} catch (err) {
error = err;
}
// Validation failed: color: Validator failed for path `color` with value `green`验证失败:`color`值为`green`的路径颜色的验证程序失败
assert.ok(error);

Update Validators Only Run On Updated Paths更新验证程序仅在更新的路径上运行

The other key difference is that update validators only run on the paths specified in the update. 另一个关键区别是更新验证器只在更新中指定的路径上运行。For instance, in the below example, because 'name' is not specified in the update operation, update validation will succeed.例如,在以下示例中,由于更新操作中未指定“name”,因此更新验证将成功。

When using update validators, required validators only fail when you try to explicitly $unset the key.当使用更新验证器时,只有当您尝试显式$unset密钥时,required验证器才会失败。

const kittenSchema = new Schema({
name: { type: String, required: true },
age: Number
});

const Kitten = db.model('Kitten', kittenSchema);

const update = { color: 'blue' };
const opts = { runValidators: true };
// Operation succeeds despite the fact that 'name' is not specified尽管未指定'name',但操作仍成功
await Kitten.updateOne({}, update, opts);

const unset = { $unset: { name: 1 } };
// Operation fails because 'name' is required操作失败,因为需要“name”
const err = await Kitten.updateOne({}, unset, opts).then(() => null, err => err);
assert.ok(err);
assert.ok(err.errors['name']);

Update Validators Only Run For Some Operations仅对某些操作运行更新验证程序

One final detail worth noting: update validators only run on the following update operators:最后一个值得注意的细节是:更新验证器仅在以下更新运算符上运行:

  • $set
  • $unset
  • $push
  • $addToSet
  • $pull
  • $pullAll

For instance, the below update will succeed, regardless of the value of number, because update validators ignore $inc.例如,无论number的值是多少,下面的更新都会成功,因为更新验证器忽略了$inc

Also, $push, $addToSet, $pull, and $pullAll validation does not run any validation on the array itself, only individual elements of the array.此外,$push$addToSet$pull$pullAll验证不会对数组本身运行任何验证,只对数组的单个元素运行验证。

const testSchema = new Schema({
number: { type: Number, max: 0 },
arr: [{ message: { type: String, maxlength: 10 } }]
});

// Update validators won't check this, so you can still `$push` 2 elements更新验证器不会检查这一点,所以您仍然可以`$push` 2个元素
// onto the array, so long as they don't have a `message` that's too long.到数组中,只要他们没有太长的`message`。
testSchema.path('arr').validate(function(v) {
return v.length < 2;
});

const Test = db.model('Test', testSchema);

let update = { $inc: { number: 1 } };
const opts = { runValidators: true };

// There will never be a validation error here这里永远不会有验证错误
await Test.updateOne({}, update, opts);

// This will never error either even though the array will have at least 2 elements.即使数组将至少有2个元素,这也不会出错。
update = { $push: [{ message: 'hello' }, { message: 'world' }] };
await Test.updateOne({}, update, opts);

Next Up下一步

Now that we've covered Validation, let's take a look at Middleware.现在我们已经介绍了Validation,让我们来看看中间件