Миграция с других библиотек

Миграция на Vest из других систем валидации обычно требует не прямого переписывания правил один-в-один, а переосмысления модели валидации. Vest построен вокруг концепции тестов, сгруппированных в “сессии”, где каждая проверка — это отдельное утверждение. Это отличается от декларативных схем, используемых в библиотеках вроде Yup или Joi.

Ключевая особенность подхода:

  • валидация выражается как набор тестов
  • выполнение идёт последовательно или условно
  • ошибки фиксируются на уровне отдельных проверок
  • структура ближе к тестированию, чем к описанию схемы

Такой подход влияет на архитектуру миграции: переносится не схема, а логика проверки.


Сравнение моделей: schema-based vs test-based

Большинство популярных библиотек используют схемы:

  • Yup — цепочки методов и объектная схема
  • Joi — декларативное описание структуры
  • Zod — типизированные схемы с композициями

Vest использует другую модель:

  • каждая проверка — функция
  • каждая функция может быть условной
  • логика читается как последовательность утверждений

Пример различия:

Yup:

const schema = yup.object({
  email: yup.string().email().required()
});

Vest:

import { create, test, enforce } from 'vest';

const validate = create((data) => {
  test('email', 'Invalid email', () => {
    enforce(data.email).isEmail();
  });

  test('email', 'Email is required', () => {
    enforce(data.email).isNotEmpty();
  });
});

При миграции важно учитывать: порядок тестов и их группировка становятся частью логики, а не побочным эффектом.


Миграция с Yup

Yup часто используется в React-проектах, особенно вместе с формами. Основная сложность перехода — замена декларативных схем на процедурные тесты.

Карта соответствий

Yup Vest
string().required() test + enforce.isNotEmpty()
email() enforce.isEmail()
min(n) enforce.isGte(n.length)
when() условные test()

Переписывание базовой схемы

Yup:

const schema = yup.object({
  password: yup.string().min(8).required(),
});

Vest:

import { create, test, enforce } from 'vest';

const validate = create((data) => {
  test('password', 'Required', () => {
    enforce(data.password).isNotEmpty();
  });

  test('password', 'Too short', () => {
    enforce(data.password.length).greaterThanOrEquals(8);
  });
});

Условная логика

Yup:

yup.string().when('role', {
  is: 'admin',
  then: schema => schema.required()
});

Vest:

test('code', 'Required for admin', () => {
  if (data.role === 'admin') {
    enforce(data.code).isNotEmpty();
  }
});

Главное отличие: условия становятся частью обычного JS-кода, а не DSL.


Миграция с Joi

Joi ориентирован на строгую структуру данных и часто используется в backend-валидации.

Основные различия

  • Joi описывает объект
  • Vest описывает поведение
  • Joi возвращает объект ошибок
  • Vest агрегирует результаты тестов

Пример переноса

Joi:

const schema = Joi.object({
  age: Joi.number().min(18).required()
});

Vest:

test('age', 'Required', () => {
  enforce(data.age).isNotEmpty();
});

test('age', 'Must be at least 18', () => {
  enforce(data.age).greaterThanOrEquals(18);
});

Важный момент

В Joi структура данных проверяется целиком. В Vest каждая проверка изолирована, что упрощает частичную валидацию (например, при вводе формы по полям).


Миграция с Zod

Zod ближе всего к TypeScript и часто используется в типобезопасных приложениях.

Отличие подхода

Zod:

  • строгая типизация
  • композиция схем
  • синхронная валидация

Vest:

  • нет схем
  • логика через функции
  • фокус на UI-валидации

Пример

Zod:

const schema = z.object({
  username: z.string().min(3)
});

Vest:

test('username', 'Too short', () => {
  enforce(data.username.length).greaterThanOrEquals(3);
});

Потеря типовой схемы

При миграции с Zod важно учитывать:

  • типы не выводятся автоматически из Vest
  • типизацию нужно сохранять отдельно (TypeScript интерфейсы)
  • Vest не заменяет runtime + compile-time validation одновременно

Перенос асинхронной валидации

Во многих библиотеках асинхронные проверки описываются отдельно (например, remote validation в Yup или Zod).

Vest поддерживает асинхронные тесты напрямую.

Пример: проверка уникальности email

Yup (через кастомный test):

email: yup.string().test('unique', async (value) => {
  return await api.checkEmail(value);
});

Vest:

test('email', 'Email already exists', async () => {
  await enforce(data.email).matches(async (value) => {
    return await api.checkEmail(value);
  });
});

Особенности миграции async-логики

  • каждая async-проверка становится отдельным test
  • важно контролировать конкуренцию запросов
  • рекомендуется использовать debounce на уровне UI

Миграция групп полей и зависимостей

В схемных библиотеках часто используется вложенная структура:

  • nested objects
  • arrays
  • conditional branches

В Vest структура упрощается до ключей и условий.

Пример вложенного объекта

Yup:

address: yup.object({
  city: yup.string().required()
});

Vest:

test('address.city', 'Required', () => {
  enforce(data.address?.city).isNotEmpty();
});

Иерархия становится “плоской”, а вложенность выражается через строки ключей.


Миграция массивов

Списки в схемных библиотеках обычно описываются через array schemas.

Пример

Zod:

z.array(z.string().min(3))

Vest:

data.items.forEach((item, index) => {
  test(`items.${index}`, 'Too short', () => {
    enforce(item.length).greaterThanOrEquals(3);
  });
});

Особенность: контроль индекса полностью на стороне разработчика.


Интеграция с формами

При переходе с Formik, React Hook Form или аналогов меняется способ привязки ошибок.

Основная модель Vest

  • результат — объект с состоянием тестов
  • ошибки группируются по ключам
  • можно запрашивать состояние конкретного поля

Пример использования:

const result = validate(formData);

if (result.hasErrors('email')) {
  console.log(result.getErrors('email'));
}

Перенос с Formik

Formik обычно ожидает объект ошибок:

{
  email: "Invalid email"
}

Vest возвращает агрегированное состояние, которое часто требует адаптера:

const errors = result.getErrors();

Частые проблемы при миграции

1. Потеря декларативности

Код становится более императивным, что требует дисциплины в организации тестов.

2. Разрастание проверок

Каждое правило — отдельный test, что увеличивает объем кода.

3. Отсутствие централизованной схемы

Нет единой структуры, которая описывает всю форму.

4. Неявный порядок выполнения

Порядок test влияет на UX (например, какая ошибка покажется первой).


Стратегии постепенной миграции

Полный переход редко выполняется сразу. Используются гибридные подходы:

Поэтапная замена полей

  • сначала мигрируются отдельные поля
  • затем группы логики
  • затем асинхронные проверки

Обертка над старой схемой

Можно временно использовать адаптер:

const legacyErrors = yupSchema.validateSync(data, { abortEarly: false });
const vestErrors = validate(data);

Параллельная валидация

Обе системы работают одновременно, сравнивая результаты до полного перехода.


Оптимизация после миграции

После перехода на Vest появляется возможность улучшить архитектуру:

  • разделение тестов по доменам
  • условная активация проверок
  • динамическая валидация по шагам формы
  • оптимизация async-запросов

Пример группировки:

const validateUser = (data) => {
  test('user.name', ...);
  test('user.email', ...);
};

const validateProfile = (data) => {
  test('profile.age', ...);
};

Особенности мышления при работе с Vest

Миграция требует перехода от “описания структуры данных” к “описанию поведения системы”.

  • данные перестают быть центром
  • центр смещается к правилам проверки
  • валидация становится набором независимых утверждений
  • контроль логики полностью у разработчика

Такой подход особенно эффективен в сложных формах с динамическими правилами, где схемные библиотеки начинают ограничивать гибкость.