Валидация
Прежде чем углубиться в детали синтаксиса валидации, учтите следующие правила:
- Валидация определяется в SchemaType
- Валидация является средством промежуточного программного обеспечения. Mongoose регистрирует валидацию как
pre('save')хук на каждой схеме по умолчанию. - Валидация всегда выполняется как первый
pre('save')хук. Это означает, что валидация не выполняется при любых изменениях, которые вы вносите вpre('save')хуки. - Вы можете отключить автоматическую валидацию перед сохранением, установив опцию validateBeforeSave
- Вы можете вручную выполнить валидацию, используя
doc.validate()илиdoc.validateSync() - Вы можете вручную пометить поле как невалидное (приводящее к отказу валидации), используя
doc.invalidate(...) - Валидаторы не выполняются для неопределенных значений. Исключение составляет валидатор
required. - При вызове Model#save, Mongoose также выполняет валидацию дочерних документов. Если произойдет ошибка, ваше обещание Model#save отклонится
- Валидация настраивается
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.');
- Встроенные валидаторы
- Настраиваемые сообщения об ошибках
- Опция
uniqueне является валидатором - Настраиваемые валидаторы
- Асинхронные настраиваемые валидаторы
- Ошибки валидации
- Ошибки приведения типов
- Глобальная валидация SchemaType
- Обязательные валидаторы для вложенных объектов
- Валидаторы обновлений
- Валидаторы обновлений и
this - Валидаторы обновлений выполняются только для обновленных путей
- Валидаторы обновлений выполняются только для некоторых операций
Встроенные валидаторы
Mongoose имеет несколько встроенных валидаторов.
- Все SchemaTypes имеют встроенный валидатор required. Валидатор required использует функцию SchemaType's
checkRequired()для определения, удовлетворяет ли значение валидатору required. -
Числа имеют валидаторы
minиmax. -
Строки имеют валидаторы
enum,match,minLength, иmaxLength.
Каждая из ссылок на валидаторы выше предоставляет дополнительную информацию о том, как их включить и настроить сообщения об ошибках.
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?');
Настраиваемые сообщения об ошибках
Вы можете настроить сообщение об ошибке для отдельных валидаторов в вашей схеме. Есть два равноценных способа установить сообщение об ошибке валидатора:
- Синтаксис массива:
min: [6, 'Must be at least 6, got {VALUE}'] - Синтаксис объекта:
enum: { values: ['Coffee', 'Tea'], message: '{VALUE} is not supported' }
Mongoose также поддерживает простые шаблоны для сообщений об ошибках. 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');
Опция unique не является валидатором
Частая ошибка для новичков заключается в том, что опция unique для схем не является валидатором. Это удобный помощник для создания уникальных индексов MongoDB. Дополнительную информацию см. в FAQ.
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.
U1.create(dup).
then(() => {
}).
catch(err => {
});
// You need to wait for Mongoose to finish building the `unique`
// index before writing. 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.
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.
// 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);
});
Настраиваемые валидаторы
Если встроенных валидаторов недостаточно, вы можете определить настраиваемые валидаторы, соответствующие вашим потребностям.
Настраиваемая валидация объявляется путем передачи функции валидации. Подробные инструкции см. в 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`
error = user.validateSync();
assert.equal(error, null);
Асинхронные настраиваемые валидаторы
Настраиваемые валидаторы также могут быть асинхронными. Если ваша функция валидатора возвращает обещание (например, функция async), Mongoose будет ждать, пока это обещание разрешится. Если возвращаемое обещание отклоняется или выполняется со значением false, Mongoose будет рассматривать это как ошибку валидации.
const userSchema = new Schema({
name: {
type: String,
// You can also make a validator async by returning a promise.
validate: () => Promise.reject(new Error('Oops!'))
},
email: {
type: String,
// There are two ways for an promise-based async validator to fail:
// 1) If the promise rejects, Mongoose assumes the validator failed with the given error.
// 2) If the promise resolves to `false`, Mongoose assumes the validator failed and creates an error with the given `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');
Ошибки валидации
Возвращаемые ошибки после неудачной валидации содержат объект errors, значения которого являются объектами ValidatorError. Каждый ValidatorError имеет свойства kind, path, value, и message. У ValidatorError также может быть свойство reason. Если в валидаторе произошла ошибка, это свойство будет содержать произошедшую ошибку.
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
// `error.errors.color` is a ValidatorError object
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. If your validator returns `false`,
// mongoose will use the 'Name `Power Ranger` is not valid' message.
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.
assert.equal(error.errors.name.reason.message,
'Need to get a Turbo Man for Christmas');
assert.equal(error.name, 'ValidationError');
Ошибки приведения типов
Перед запуском валидаторов Mongoose пытается привести значения к нужному типу. Этот процесс называется приведением типов документа. Если приведение типов для определенного пути завершится неудачно, объект error.errors будет содержать объект CastError.
Приведение типов выполняется до валидации, и валидация не выполняется, если приведение типов завершается неудачно. Это означает, что ваши настраиваемые валидаторы могут предполагать, что v является null, undefined, или экземпляром типа, указанного в вашей схеме.
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;
По умолчанию, сообщения об ошибках приведения типов Mongoose выглядят как Cast to Number failed for value "pie" at path "numWheels". Вы можете переопределить стандартное сообщение об ошибке приведения типов Mongoose, установив опцию cast на вашем SchemaType на строку следующим образом.
const vehicleSchema = new mongoose.Schema({
numWheels: {
type: Number,
cast: '{VALUE} is not a number'
}
});
const Vehicle = db.model('Vehicle', vehicleSchema);
const doc = new Vehicle({ numWheels: 'pie' });
const err = doc.validateSync();
err.errors['numWheels'].name; // 'CastError'
// "pie" is not a number
err.errors['numWheels'].message;
Шаблонизация сообщений об ошибках приведения типов Mongoose поддерживает следующие параметры:
-
{PATH}: путь, который не удалось привести к типу -
{VALUE}: строковое представление значения, которое не удалось привести к типу -
{KIND}: тип, к которому Mongoose пытался привести значение, например,'String'или'Number'
Вы также можете определить функцию, которую Mongoose будет вызывать для получения сообщения об ошибке приведения типов следующим образом.
const vehicleSchema = new mongoose.Schema({
numWheels: {
type: Number,
cast: [null, (value, path, model, kind) => `"${value}" is not a number`]
}
});
const Vehicle = db.model('Vehicle', vehicleSchema);
const doc = new Vehicle({ numWheels: 'pie' });
const err = doc.validateSync();
err.errors['numWheels'].name; // 'CastError'
// "pie" is not a number
err.errors['numWheels'].message;
Глобальная валидация SchemaType
Помимо определения настраиваемых валидаторов для отдельных путей схем, вы также можете настроить настраиваемый валидатор, который будет выполняться для каждого экземпляра заданного SchemaType. Например, следующий код демонстрирует, как сделать пустую строку '' недопустимым значением для всех строковых путей.
// 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
Обязательные валидаторы для вложенных объектов
Определение валидаторов для вложенных объектов в mongoose сложно, потому что вложенные объекты не являются полноценными путями.
let 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
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']);
Валидаторы обновлений
В приведенных выше примерах вы узнали о валидации документов. Mongoose также поддерживает валидацию для операций update(), updateOne(), updateMany() и findOneAndUpdate(). Валидаторы обновлений отключены по умолчанию — вам нужно указать опцию runValidators.
Чтобы включить валидаторы обновлений, установите опцию runValidators для update(), updateOne(), updateMany(), или findOneAndUpdate(). Будьте внимательны: валидаторы обновлений отключены по умолчанию, потому что у них есть несколько ограничений.
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');
Валидаторы обновлений и this
Есть несколько ключевых различий между валидаторами обновлений и валидаторами документов. В функции валидации цвета ниже, this относится к документу, который валидируется при использовании валидации документов. Однако при выполнении валидаторов обновлений, this относится к объекту запроса, а не к документу. Поскольку у запросов есть удобная функция .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`.
// When running with update validators, `this` is the Query,
// **not** the document being updated!
// Queries have a `get()` method that lets you get the
// updated value.
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`
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`
assert.ok(error);
Валидаторы обновлений выполняются только для обновленных путей
Другое ключевое различие заключается в том, что валидаторы обновлений выполняются только для путей, указанных в обновлении. Например, в приведенном ниже примере, поскольку 'name' не указан в операции обновления, валидация обновления пройдет успешно.
При использовании валидаторов обновлений, валидаторы required только терпят неудачу, когда вы пытаетесь явно $unset ключ.
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
await Kitten.updateOne({}, update, opts);
const unset = { $unset: { name: 1 } };
// Operation fails because 'name' is required
const err = await Kitten.updateOne({}, unset, opts).then(() => null, err => err);
assert.ok(err);
assert.ok(err.errors['name']);
Валидаторы обновлений выполняются только для некоторых операций
Еще один важный момент: валидаторы обновлений только выполняются для следующих операторов обновления:
$set$unset$push$addToSet$pull$pullAll
Например, нижеприведенное обновление пройдет успешно, независимо от значения number, потому что валидаторы обновлений игнорируют $inc.
Также, валидация $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
// onto the array, so long as they don't have a `message` that's too long.
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.
update = { $push: [{ message: 'hello' }, { message: 'world' }] };
await Test.updateOne({}, update, opts);
Далее
Теперь, когда мы рассмотрели Validation, давайте посмотрим на Средства промежуточного программного обеспечения.
© 2010 LearnBoost
Licensed under the MIT License.
https://mongoosejs.com/docs/validation.html