Архитектура валидации в Vest строится вокруг идеи декларативных тестовых сценариев, которые позволяют описывать правила проверки данных так, будто это набор изолированных проверок. Такая модель естественным образом подводит к задаче повторного использования логики, поскольку в реальных приложениях одинаковые правила часто встречаются в разных формах, сущностях и сценариях.
Переиспользование здесь не ограничивается простым вынесением функций. В рамках Vest логика может быть композиционной, параметризованной и контекстной, что делает её ближе к функциональному программированию и тестовым фреймворкам одновременно.
Самый прямолинейный способ вынесения логики — создание чистых функций, возвращающих правила или группы правил. Поскольку Vest описывает валидацию через выполнение блоков, такие функции легко интегрируются в тестовые сценарии.
import { test, enforce } fr om 'vest';
const isRequiredString = (field) => {
test(`${field}_required`, 'Поле обязательно', () => {
enforce(field).isNotEmpty();
});
test(`${field}_type`, 'Должна быть строка', () => {
enforce(typeof field).equals('string');
});
};
Такая функция может быть вызвана в любом сценарии валидации, что обеспечивает единый источник правил для разных полей.
В сложных формах простого вызова функций недостаточно. Логика начинает разрастаться, и появляется необходимость объединять независимые блоки проверок.
Vest позволяет естественно складывать функции валидации друг с другом, формируя композиции:
const validateEmail = (email) => {
test('email_format', 'Некорректный email', () => {
enforce(email).matches(/^[^\s@]+@[^\s@]+\.[^\s@]+$/);
});
};
const validatePassword = (password) => {
test('password_length', 'Слишком короткий пароль', () => {
enforce(password.length).greaterThan(8);
});
};
const validateAuthForm = (data) => {
validateEmail(data.email);
validatePassword(data.password);
};
Композиционный подход устраняет дублирование и позволяет формировать более сложные правила из простых элементов.
Повторное использование становится более гибким, когда правила не зафиксированы, а зависят от параметров. Vest не накладывает ограничений на структуру функций, поэтому можно строить универсальные валидаторы.
const minLength = (value, length, fieldName) => {
test(`${fieldName}_min_length`, `Минимальная длина ${length}`, () => {
enforce(value.length).greaterThanOrEquals(length);
});
};
Теперь одно и то же правило применяется к любому полю:
minLength(username, 3, 'username');
minLength(password, 10, 'password');
Такой подход снижает связность между бизнес-логикой и конкретными полями формы.
В крупных проектах набор переиспользуемых функций превращается в отдельный слой доменных правил. Эти функции не зависят от конкретных форм и могут использоваться в разных частях приложения.
Типичный набор включает:
Пример доменного валидатора:
const validatePositiveNumber = (value, field) => {
test(`${field}_positive`, 'Число должно быть положительным', () => {
enforce(value).greaterThan(0);
});
};
Такие функции становятся строительными блоками, на которых строится вся система проверки данных.
Более продвинутый уровень абстракции — фабрики, возвращающие набор правил. Это позволяет создавать конфигурируемые валидаторы.
const createRangeValidator = (min, max, field) => {
return (value) => {
test(`${field}_range`, `Значение должно быть между ${min} и ${max}`, () => {
enforce(value).greaterThanOrEquals(min);
enforce(value).lessThanOrEquals(max);
});
};
};
Использование:
const validateAge = createRangeValidator(18, 65, 'age');
validateAge(user.age);
Такой подход особенно полезен при работе с однотипными сущностями, где различаются только параметры ограничений.
Логика может зависеть от внешнего состояния: конфигурации, пользовательских ролей или окружения. В Vest это удобно реализуется через замыкания.
const createRoleValidator = (allowedRoles) => (role) => {
test('role_check', 'Недопустимая роль', () => {
enforce(allowedRoles.includes(role)).equals(true);
});
};
Теперь валидатор можно адаптировать под разные контексты приложения:
const adminRoleValidator = createRoleValidator(['admin', 'superadmin']);
const userRoleValidator = createRoleValidator(['user', 'guest']);
Контекстная модель делает повторное использование более гибким без усложнения API.
Часто правила должны применяться не всегда, а только при выполнении определённых условий. Вместо дублирования логики вводятся условные обёртки.
const conditionalTest = (condition, callback) => {
if (condition) {
callback();
}
};
Использование:
conditionalTest(user.isPremium, () => {
test('premium_limit', 'Превышен лимит', () => {
enforce(user.lim it).lessThanOrEquals(1000);
});
});
Такой подход предотвращает разрастание дублирующихся блоков валидации.
Vest позволяет объединять тесты в логические группы, что упрощает повторное использование целых блоков валидации.
const validateAddress = (address) => {
test('street_required', () => {
enforce(address.street).isNotEmpty();
});
test('city_required', () => {
enforce(address.city).isNotEmpty();
});
test('zip_format', () => {
enforce(address.zip).matches(/^\d{5}$/);
});
};
Такой блок можно включать в разные формы: регистрации, доставки, профиля пользователя.
Ключевой аспект переиспользования — отделение логики проверки от представления данных. Валидаторы не должны зависеть от форм, компонентов или UI-состояния.
// доменная логика
const validateCredentials = ({ login, password }) => {
validateEmail(login);
validatePassword(password);
};
UI-слой лишь передаёт данные:
validateCredentials(formState);
Это позволяет использовать одни и те же правила в веб-приложениях, серверной логике и тестах.
При масштабировании проекта логика валидации часто выстраивается в несколько уровней:
Каждый слой использует предыдущий, не нарушая зависимости.
const validateProfile = (data) => {
validateEmail(data.email);
validateAge(data.age);
validateAddress(data.address);
};
Такая структура минимизирует дублирование и делает поведение системы предсказуемым.
Наиболее гибкий уровень — создание обёрток над test,
которые инкапсулируют повторяющиеся паттерны проверки.
const rule = (name, message, fn) => {
test(name, message, fn);
};
Теперь доменные правила выглядят компактнее:
rule('email_format', 'Некорректный email', () => {
enforce(email).matches(/.+@.+\..+/);
});
Это позволяет стандартизировать стиль написания и облегчает масштабирование кода в больших командах.