Организация кода валидации в Vest строится вокруг идеи изолированных
наборов правил, объединённых в логические блоки. Базовой единицей
выступает suite, внутри которой группируются тесты
test, а сами проверки реализуются через
enforce.
Ключевой принцип — каждая бизнес-область или форма должна иметь собственный независимый модуль валидации, не зависящий от других частей приложения.
Пример структуры:
// validation/user.validation.js
import { suite, test, enforce } from 'vest';
export const userValidation = suite('user', (data = {}) => {
test('email', 'invalid_email', () => {
enforce(data.email).isEmail();
});
test('password', 'weak_password', () => {
enforce(data.password).longerThan(6);
});
});
Такое разделение позволяет локализовать изменения и снижает связанность кода.
При масштабировании приложения важно разделять валидацию по доменным областям, а не по страницам интерфейса. Страница может меняться, но доменная модель остаётся стабильной.
Подход:
auth.validation.js — регистрация, логин, восстановление
пароляprofile.validation.js — пользовательские данныеbilling.validation.js — платёжные данные// validation/auth.validation.js
export const loginValidation = suite('login', (data) => {
test('email', 'invalid_email', () => {
enforce(data.email).isEmail();
});
test('password', 'required', () => {
enforce(data.password).isNotEmpty();
});
});
Разделение по доменам снижает риск дублирования правил и облегчает сопровождение.
В Vest допускается логическая композиция наборов валидации через функции и переиспользуемые блоки.
Структурирование через вложенные функции:
const emailRules = (data) => {
test('email', 'invalid_email', () => {
enforce(data.email).isEmail();
});
};
const passwordRules = (data) => {
test('password', 'weak_password', () => {
enforce(data.password).longerThan(6);
});
};
export const registrationValidation = suite('registration', (data) => {
emailRules(data);
passwordRules(data);
});
Такой подход превращает валидацию в набор независимых строительных блоков.
Одной из ключевых проблем масштабируемых систем является дублирование проверок. Vest позволяет эффективно решать эту задачу через вынесение общих правил в отдельные модули.
// rules/email.rules.js
export const emailRules = (data) => {
test('email', 'invalid_email', () => {
enforce(data.email).isEmail();
});
};
Использование:
import { emailRules } from './rules/email.rules';
export const contactFormValidation = suite('contact', (data) => {
emailRules(data);
test('message', 'required', () => {
enforce(data.message).isNotEmpty();
});
});
Такой подход формирует библиотеку правил, применимую во множестве контекстов.
Для сложных доменных систем применяется паттерн фабрик, когда набор правил создаётся динамически.
const createLengthRule = (field, min) => (data) => {
test(field, 'too_short', () => {
enforce(data[field]).longerThan(min);
});
};
const min8CharsPassword = createLengthRule('password', 8);
export const securityValidation = suite('security', (data) => {
min8CharsPassword(data);
});
Фабрики позволяют параметризовать правила и снижать повторяемость кода.
При сложных формах валидация должна быть разложена на логические слои:
const baseUserFields = (data) => {
test('email', 'invalid_email', () => {
enforce(data.email).isEmail();
});
};
const profileConstraints = (data) => {
test('username', 'too_short', () => {
enforce(data.username).longerThan(3);
});
};
export const userProfileValidation = suite('profile', (data) => {
baseUserFields(data);
profileConstraints(data);
});
Декомпозиция делает код линейным по сложности и уменьшает когнитивную нагрузку.
Vest поддерживает асинхронные проверки, что требует особой организации кода. Такие проверки должны быть изолированы от синхронных правил.
test('username', 'username_taken', async () => {
const exists = await api.checkUsername(data.username);
enforce(exists).isFalsy();
});
Рекомендуемая структура:
const asyncRules = (data) => {
test('email', 'email_taken', async () => {
const exists = await api.checkEmail(data.email);
enforce(exists).isFalsy();
});
};
Разделение предотвращает блокировку синхронной логики и упрощает тестирование.
В сложных формах часть правил активируется только при определённых условиях.
test('companyName', 'required', () => {
if (data.isCompany) {
enforce(data.companyName).isNotEmpty();
}
});
Для масштабируемости такие условия выносятся в отдельные функции:
const ifCompany = (data, fn) => {
if (data.isCompany) fn();
};
export const checkoutValidation = suite('checkout', (data) => {
ifCompany(data, () => {
test('companyName', 'required', () => {
enforce(data.companyName).isNotEmpty();
});
});
});
Организация сообщений об ошибках должна быть отделена от логики валидации. Это позволяет унифицировать интерфейс ошибок и облегчает локализацию.
export const errorMessages = {
invalid_email: 'Email имеет некорректный формат',
weak_password: 'Пароль недостаточной длины',
};
Связка происходит по ключу ошибки, возвращаемому
test.
Структурирование:
errors/ — словари сообщенийvalidation/ — логика проверкиconstants/ — коды ошибокПри использовании TypeScript структура валидации становится строго типизированной, что снижает количество ошибок на этапе разработки.
type LoginData = {
email: string;
password: string;
};
export const loginValidation = suite<LoginData>('login', (data) => {
test('email', 'invalid_email', () => {
enforce(data.email).isEmail();
});
});
Рекомендуется:
anytypes/При росте приложения эффективна следующая организация:
src/
validation/
auth/
login.validation.js
register.validation.js
profile/
profile.validation.js
rules/
email.rules.js
password.rules.js
errors/
auth.errors.js
profile.errors.js
types/
auth.types.ts
Такая структура отражает доменную модель и облегчает навигацию по коду.
При увеличении количества форм и правил критически важно соблюдать несколько архитектурных принципов:
Эти принципы обеспечивают предсказуемость поведения системы валидации и упрощают её развитие при росте проекта