Синхронная валидация используется для мгновенной проверки данных без ожидания асинхронных операций. Такой подход особенно эффективен при проверке:
В библиотеке Vest синхронная валидация строится вокруг декларативных тестов, объединённых в suite — сценарий проверки формы или объекта.
Основной механизм Vest — функция create.
import { create, test, enforce } from 'vest';
const suite = create((data = {}) => {
test('username', 'Имя обязательно', () => {
enforce(data.username).isNotBlank();
});
});
test описывает отдельную проверку.enforce выполняет утверждение.Сигнатура теста:
test(fieldName, errorMessage, callback);
| Аргумент | Описание |
|---|---|
fieldName |
Название поля |
errorMessage |
Текст ошибки |
callback |
Логика проверки |
import { create, test, enforce } from 'vest';
const suite = create((data) => {
test('email', 'Email обязателен', () => {
enforce(data.email).isNotBlank();
});
});
const result = suite({
email: ''
});
console.log(result.getErrors());
Результат:
{
email: ['Email обязателен']
}
Vest использует API enforce, напоминающий
fluent-интерфейс.
test('password', 'Минимум 8 символов', () => {
enforce(data.password).longerThanOrEquals(8);
});
enforce(value).shorterThan(20);
enforce(value).shorterThanOrEquals(50);
enforce(value).longerThan(3);
test('firstName', 'Поле обязательно', () => {
enforce(data.firstName).isNotBlank();
});
test('age', 'Возраст обязателен', () => {
enforce(data.age).isDefined();
});
test('token', 'Токен отсутствует', () => {
enforce(data.token).isNotNull();
});
test('age', 'Возраст должен быть больше 18', () => {
enforce(data.age).greaterThan(18);
});
test('price', 'Цена слишком высокая', () => {
enforce(data.price).lessThan(1000);
});
enforce(value).greaterThanOrEquals(1);
enforce(value).lessThanOrEquals(10);
test('title', 'Должна быть строка', () => {
enforce(data.title).isString();
});
test('count', 'Должно быть число', () => {
enforce(data.count).isNumber();
});
test('tags', 'Должен быть массив', () => {
enforce(data.tags).isArray();
});
test('active', 'Некорректное значение', () => {
enforce(data.active).isBoolean();
});
test('email', 'Некорректный email', () => {
enforce(data.email).matches(
/^[^\s@]+@[^\s@]+\.[^\s@]+$/
);
});
test('phone', 'Неверный номер телефона', () => {
enforce(data.phone).matches(/^\+7\d{10}$/);
});
test('password', 'Пароль слишком простой', () => {
enforce(data.password).matches(
/^(?=.*[A-Z])(?=.*\d).+$/
);
});
В одном тесте допускается несколько утверждений.
test('username', 'Некорректное имя', () => {
enforce(data.username).isNotBlank();
enforce(data.username).longerThanOrEquals(3);
enforce(data.username).shorterThanOrEquals(20);
});
Однако более читаемым считается разделение на отдельные тесты.
test('username', 'Имя обязательно', () => {
enforce(data.username).isNotBlank();
});
test('username', 'Минимум 3 символа', () => {
enforce(data.username).longerThanOrEquals(3);
});
test('username', 'Максимум 20 символов', () => {
enforce(data.username).shorterThanOrEquals(20);
});
test('confirmPassword', 'Пароли не совпадают', () => {
enforce(data.confirmPassword).equals(data.password);
});
Vest позволяет выполнять проверки только при определённых условиях.
const suite = create((data) => {
if (data.hasDiscount) {
test('discount', 'Скидка обязательна', () => {
enforce(data.discount).isDefined();
});
}
});
test('city', 'Город обязателен', () => {
enforce(data.address.city).isNotBlank();
});
test('city', 'Город обязателен', () => {
enforce(data.address?.city).isNotBlank();
});
test('tags', 'Добавьте минимум один тег', () => {
enforce(data.tags.length).greaterThan(0);
});
test('roles', 'Роль admin обязательна', () => {
enforce(data.roles).contains('admin');
});
data.items.forEach((item, index) => {
test(`items.${index}.title`, 'Название обязательно', () => {
enforce(item.title).isNotBlank();
});
});
const result = suite(formData);
result.getErrors();
result.getErrors('email');
result.hasErrors();
result.hasErrors('password');
result.getValid();
result.isValid('email');
result.isValid();
import { create, test, enforce } from 'vest';
const registrationSuite = create((data) => {
test('name', 'Имя обязательно', () => {
enforce(data.name).isNotBlank();
});
test('name', 'Минимум 2 символа', () => {
enforce(data.name).longerThanOrEquals(2);
});
test('email', 'Email обязателен', () => {
enforce(data.email).isNotBlank();
});
test('email', 'Некорректный email', () => {
enforce(data.email).matches(
/^[^\s@]+@[^\s@]+\.[^\s@]+$/
);
});
test('password', 'Пароль обязателен', () => {
enforce(data.password).isNotBlank();
});
test('password', 'Минимум 8 символов', () => {
enforce(data.password).longerThanOrEquals(8);
});
test('password', 'Пароль должен содержать цифру', () => {
enforce(data.password).matches(/\d/);
});
test('confirmPassword', 'Пароли не совпадают', () => {
enforce(data.confirmPassword).equals(data.password);
});
test('age', 'Возраст должен быть больше 18', () => {
enforce(data.age).greaterThan(18);
});
});
const result = registrationSuite({
name: '',
email: 'wrong',
password: '123',
confirmPassword: '111',
age: 15
});
console.log(result.getErrors());
Результат:
{
name: [
'Имя обязательно',
'Минимум 2 символа'
],
email: [
'Некорректный email'
],
password: [
'Минимум 8 символов'
],
confirmPassword: [
'Пароли не совпадают'
],
age: [
'Возраст должен быть больше 18'
]
}
Vest позволяет добавлять собственные синхронные валидаторы.
import { enforce } from 'vest';
enforce.extend({
isEven(value) {
return value % 2 === 0;
}
});
test('count', 'Число должно быть чётным', () => {
enforce(data.count).isEven();
});
enforce.extend({
isStrongPassword(value) {
return (
/[A-Z]/.test(value) &&
/\d/.test(value) &&
value.length >= 8
);
}
});
Использование:
test('password', 'Слабый пароль', () => {
enforce(data.password).isStrongPassword();
});
function validateEmail(email) {
enforce(email).isNotBlank();
enforce(email).matches(
/^[^\s@]+@[^\s@]+\.[^\s@]+$/
);
}
Использование:
test('email', 'Некорректный email', () => {
validateEmail(data.email);
});
const passwordRules = (password) => {
enforce(password).longerThanOrEquals(8);
enforce(password).matches(/\d/);
};
export const loginSuite = create((data) => {
test('email', 'Введите email', () => {
enforce(data.email).isNotBlank();
});
test('password', 'Введите пароль', () => {
enforce(data.password).isNotBlank();
});
});
export const profileSuite = create((data) => {
test('firstName', 'Введите имя', () => {
enforce(data.firstName).isNotBlank();
});
});
Неверно:
test('email', 'Ошибка', async () => {
await fetch('/api');
});
Синхронная валидация должна выполняться мгновенно и не содержать
Promise.
Плохо:
test('username', 'Ошибка', () => {
enforce(data.username).isNotBlank();
enforce(data.username).longerThanOrEquals(3);
enforce(data.username).shorterThanOrEquals(20);
enforce(data.username).matches(/^[a-z]+$/);
});
Лучше разделять проверки на независимые тесты.
Проблемный код:
enforce(data.user.address.city).isNotBlank();
Безопасный вариант:
enforce(data.user?.address?.city).isNotBlank();
import { create, test, enforce } from 'vest';
export const formSuite = create((data) => {
test('field', 'Поле обязательно', () => {
enforce(data.field).isNotBlank();
});
test('field', 'Минимум 3 символа', () => {
enforce(data.field).longerThanOrEquals(3);
});
test('field', 'Максимум 20 символов', () => {
enforce(data.field).shorterThanOrEquals(20);
});
});
Эффективная структура обычно строится следующим образом:
enforce.extend;test.Проверки выполняются мгновенно без ожидания сетевых операций.
Тесты легко комбинируются и масштабируются.
Логика описывается как набор правил, а не как цепочка условий.
Каждый test отвечает за одно конкретное правило.
Кастомные валидаторы и функции правил можно применять в разных формах.
Синхронные проверки не зависят от состояния асинхронных процессов и всегда выполняются последовательно.