Комбинирование валидаторов

Библиотека validator.js построена вокруг набора независимых функций, каждая из которых выполняет строго одну проверку: строка является email, URL, числом, находится ли в диапазоне и т.д. Реальная валидация данных в приложениях почти никогда не ограничивается одной проверкой, поэтому ключевым механизмом становится комбинирование валидаторов.

Комбинирование означает построение сложных правил из простых функций с использованием логических связок, композиции и переиспользуемых обёрток.


Логическая композиция: базовый принцип

Каждый валидатор в validator.js возвращает булево значение. Это позволяет использовать стандартные логические операторы Jav * aScript:

import validator from 'validator';

const isValidEmail = (value) =>
  validator.isEmail(value);

const isCorporateEmail = (value) =>
  validator.isEmail(value) && value.endsWith('@company.com');

Здесь реализовано простое комбинирование через оператор &&. Такая схема соответствует логике AND-композиции, когда все условия должны быть истинны.


Разделение нормализации и проверки

Комбинирование часто начинается с предварительной обработки данных. validator.js предоставляет функции нормализации, которые удобно включать в цепочки.

const normalizeAndValidateEmail = (value) => {
  const normalized = validator.normalizeEmail(value, {
    gmail_remove_dots: true,
  });

  return validator.isEmail(normalized);
};

Такое разделение улучшает предсказуемость: сначала данные приводятся к единому виду, затем проверяются.


Функциональная композиция валидаторов

При усложнении логики целесообразно использовать функциональную композицию. Валидаторы становятся функциями высшего порядка.

const composeValidators = (...validators) => (value) =>
  validators.every((fn) => fn(value));

const isNonEmpty = (v) => !validator.isEmpty(v);
const isEmail = (v) => validator.isEmail(v);

const validateEmail = composeValidators(isNonEmpty, isEmail);

Функция composeValidators реализует универсальный механизм AND-композиции. Она позволяет собирать сложные правила из независимых компонентов.


OR-композиция валидаторов

В validator.js отсутствует встроенный механизм логического OR, поэтому он реализуется вручную:

const anyOf = (...validators) => (value) =>
  validators.some((fn) => fn(value));

const isEmail = (v) => validator.isEmail(v);
const isPhone = (v) => validator.isMobilePhone(v);

const isEmailOrPhone = anyOf(isEmail, isPhone);

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


Комбинирование с условиями и контекстом

Валидация часто зависит от внешнего контекста, что требует расширенных схем комбинирования:

const isAdultUserEmail = (email, age) =>
  age >= 18 && validator.isEmail(email);

Более универсальный подход — создание фабрик валидаторов:

const createEmailValidator = (domain) => (email) =>
  validator.isEmail(email) && email.endsWith(domain);

const isCompanyEmail = createEmailValidator('@company.com');

Здесь комбинируются проверка формата и бизнес-логика.


Композиция через пайплайны преобразований

Валидация часто строится как последовательность шагов: нормализация → очистка → проверка.

const pipeline = (...steps) => (value) =>
  steps.reduce((acc, fn) => fn(acc), value);

const trim = (v) => validator.trim(v);
const escape = (v) => validator.escape(v);
const isLengthValid = (v) => validator.isLength(v, { min: 5 });

const validateUsername = pipeline(trim, escape, isLengthValid);

Такой подход делает процесс проверки предсказуемым и легко расширяемым.


Комбинирование с кастомными правилами

validator.js легко интегрируется с пользовательскими функциями:

const isNotBlacklisted = (value) => {
  const blacklist = ['admin', 'root', 'test'];
  return !blacklist.includes(value.toLowerCase());
};

const isValidUsername = (value) =>
  validator.isAlphanumeric(value) && isNotBlacklisted(value);

Здесь комбинируется библиотечная проверка и прикладная логика.


Составные схемы валидации объектов

При работе с формами или API часто требуется проверка нескольких полей одновременно:

const validateUser = (user) => ({
  email: validator.isEmail(user.email),
  password: validator.isStrongPassword(user.password),
  age: validator.isInt(user.age.toString(), { min: 18 }),
});

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


Агрегация ошибок при комбинировании

Комбинированные валидаторы могут возвращать не только булево значение, но и подробную информацию:

const validatePassword = (value) => {
  const errors = [];

  if (!validator.isLength(value, { min: 8 }))
    errors.push('Минимальная длина 8 символов');

  if (!/[A-Z]/.test(value))
    errors.push('Нет заглавной буквы');

  if (!/[0-9]/.test(value))
    errors.push('Нет цифры');

  return {
    valid: errors.length === 0,
    errors,
  };
};

Такой формат позволяет комбинировать несколько проверок в единый результат, сохраняя детализацию причин.


Условное комбинирование

Иногда валидаторы включаются динамически в зависимости от состояния данных:

const validate = (value, options) => {
  let check = validator.isLength(value, { min: 3 });

  if (options.strict) {
    check = check && validator.isAlphanumeric(value);
  }

  return check;
};

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


Переиспользуемые композиционные модули

Для масштабируемых проектов создаются библиотеки валидаторов на основе композиции:

export const validators = {
  email: (v) => validator.isEmail(v),
  requiredEmail: (v) =>
    !validator.isEmpty(v) && validator.isEmail(v),
  username: composeValidators(
    (v) => validator.isLength(v, { min: 3, max: 20 }),
    validator.isAlphanumeric
  ),
};

Такая структура упрощает поддержку и стандартизирует правила валидации во всём приложении.


Производительность комбинированных проверок

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

const validate = (value) =>
  !validator.isEmpty(value) &&
  validator.isEmail(value) &&
  validator.isLength(value, { max: 255 });

Ранний выход при неудачной проверке уменьшает количество операций.


Ошибки проектирования при комбинировании

Распространённая проблема — чрезмерное усложнение цепочек:

const validate = (v) =>
  validator.isEmail(v) &&
  validator.isLength(v, { min: 5 }) &&
  someOtherCheck(v) &&
  anotherCheck(v);

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


Композиция как основной механизм расширения

Комбинирование валидаторов формирует основу архитектуры проверки данных. Простые функции validator.js превращаются в строительные блоки, из которых собираются сложные бизнес-правила, включая условные проверки, альтернативные сценарии и многоуровневую фильтрацию данных.