Переиспользование логики

Архитектура валидации в 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');

Такой подход снижает связность между бизнес-логикой и конкретными полями формы.


Общие валидаторы как библиотека доменной логики

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

Типичный набор включает:

  • проверку строковых значений
  • проверку числовых диапазонов
  • правила формата (email, телефон, идентификаторы)
  • бизнес-ограничения (уникальность, допустимые статусы)

Пример доменного валидатора:

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-слоя

Ключевой аспект переиспользования — отделение логики проверки от представления данных. Валидаторы не должны зависеть от форм, компонентов или UI-состояния.

// доменная логика
const validateCredentials = ({ login, password }) => {
  validateEmail(login);
  validatePassword(password);
};

UI-слой лишь передаёт данные:

validateCredentials(formState);

Это позволяет использовать одни и те же правила в веб-приложениях, серверной логике и тестах.


Построение слоистой системы переиспользования

При масштабировании проекта логика валидации часто выстраивается в несколько уровней:

  • базовые примитивы (isRequired, minLength)
  • доменные правила (validateEmail, validateAge)
  • агрегированные сценарии (validateAuthForm, validateProfile)

Каждый слой использует предыдущий, не нарушая зависимости.

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(/.+@.+\..+/);
});

Это позволяет стандартизировать стиль написания и облегчает масштабирование кода в больших командах.