В основе библиотеки лежит функция enforce,
представляющая собой единый интерфейс описания утверждений (assertions).
Она реализует цепочку вызовов, где каждое последующее звено добавляет
новое ограничение к проверяемому значению.
Ключевая особенность архитектуры — расширяемость через
пользовательские операторы. Оператор в контексте Vest представляет собой
метод, прикреплённый к enforce(value), который принимает
дополнительные параметры и возвращает либо успешное выполнение, либо
ошибку валидации.
enforceОператор — это функция-предикат, интегрированная в цепочку
enforce, которая:
Базовая форма выглядит как:
enforce(value).operatorName(args);
Каждый оператор расширяет поведение базового объекта проверки, сохраняя единый стиль декларативного описания правил.
Встроенные операторы предоставляют стандартные проверки:
Однако прикладные задачи часто требуют доменной логики:
Именно здесь используются кастомные операторы, позволяющие расширять
enforce без модификации ядра.
Расширение системы выполняется через механизм регистрации, добавляющий новый метод в прототип или внутренний реестр операторов.
Обобщённая форма регистрации:
enforce.extend('operatorName', (value, ...args) => {
return true | false | Error;
});
Или более развернутый вариант с сообщением об ошибке:
enforce.extend('isEven', (value) => {
if (value % 2 !== 0) {
return 'Значение должно быть чётным';
}
});
После регистрации оператор становится доступным в цепочке:
enforce(4).isEven();
Функция кастомного оператора получает:
value — текущее значение проверки;Возвращаемые значения определяют результат:
undefined или true — проверка
пройдена;string — сообщение об ошибке;false — стандартная ошибка без уточнения;Error — исключительная ситуация с кастомным объектом
ошибки.Гибкость системы заключается в возможности возвращать динамические сообщения:
enforce.extend('minWords', (value, min) => {
const count = value.trim().split(/\s+/).length;
if (count < min) {
return `Минимальное количество слов: ${min}`;
}
});
Использование:
enforce('hello world').minWords(3);
Сообщения могут строиться на основе:
Кастомные операторы могут комбинироваться с встроенными, формируя сложные правила:
enforce(email)
.isString()
.isEmail()
.domainAllowed(['example.com']);
Композиция работает за счёт последовательного применения предикатов. Каждый оператор получает результат предыдущего состояния и либо продолжает цепочку, либо прерывает её.
Часто требуется параметризованная логика. Для этого используются фабрики операторов:
const minLength = (min) =>
(value) => {
if (value.length < min) {
return `Минимальная длина: ${min}`;
}
};
enforce.extend('minLength', minLength);
Такой подход позволяет создавать универсальные правила:
enforce('abc').minLength(5);
Некоторые проверки требуют обращения к внешним ресурсам:
Асинхронный оператор возвращает Promise:
enforce.extend('isUniqueUsername', async (value, api) => {
const exists = await api.checkUsername(value);
if (exists) {
return 'Имя пользователя уже занято';
}
});
Использование:
await enforce('john').isUniqueUsername(api);
Асинхронность сохраняет единый стиль цепочек, но требует явного ожидания результата.
При использовании TypeScript расширение оператора требует декларации типов:
declare module 'vest' {
interface Enforce {
minWords(min: number): Enforce;
}
}
Реализация:
enforce.extend('minWords', (value: string, min: number) => {
const count = value.trim().split(/\s+/).length;
if (count < min) {
return `Минимум слов: ${min}`;
}
});
Типизация обеспечивает:
Некоторые реализации позволяют операторам учитывать состояние цепочки:
Пример использования контекста:
enforce.extend('matchesField', (value, fieldValue) => {
if (value !== fieldValue) {
return 'Значения не совпадают';
}
});
Это позволяет строить межполевые зависимости валидации.
На практике выделяются устойчивые подходы:
Инкапсулируют бизнес-логику:
enforce.extend('isValidINN', (value) => {
// алгоритм проверки ИНН
});
Объединяют несколько проверок:
enforce.extend('strongPassword', (value) => {
if (value.length < 8) return 'Слишком короткий пароль';
if (!/[A-Z]/.test(value)) return 'Нет заглавной буквы';
if (!/[0-9]/.test(value)) return 'Нет цифры';
});
Используют внешние сервисы:
enforce.extend('isDisposableEmail', async (value, service) => {
const result = await service.check(value);
if (result.disposable) {
return 'Временные email-адреса запрещены';
}
});
При расширении системы часто возникают типовые проблемы:
Рациональная декомпозиция операторов повышает читаемость цепочек
enforce и упрощает поддержку системы валидации.