Цепочки валидации

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

Поскольку Validator.js предоставляет набор чистых функций, цепочка валидации формируется не внутри самой библиотеки, а на уровне архитектуры приложения. Это приводит к необходимости построения композиционных механизмов, объединяющих несколько проверок в единый поток обработки данных.

Базовая композиция проверок

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

import validator from 'validator';

function validateEmail(value) {
  if (!validator.isString(value)) return false;
  if (!validator.isEmail(value)) return false;
  if (!validator.isLength(value, { max: 254 })) return false;
  return true;
}

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

Формирование универсальных цепочек

Для масштабирования логики валидации применяется обобщённая структура цепочки, в которой набор правил задаётся динамически:

import validator from 'validator';

function runChain(value, rules) {
  for (const rule of rules) {
    const result = rule(value);
    if (result !== true) {
      return result;
    }
  }
  return true;
}

Каждая функция-правило инкапсулирует отдельную проверку Validator.js:

const emailRules = [
  (v) => validator.isString(v) || 'не строка',
  (v) => validator.isEmail(v) || 'некорректный email',
  (v) => validator.isLength(v, { max: 254 }) || 'слишком длинный email'
];

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

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

Цепочки становятся более гибкими при разбиении логики на переиспользуемые блоки. Например, базовая проверка строки:

const isNonEmptyString = (v) =>
  typeof v === 'string' && !validator.isEmpty(v.trim());

Она может использоваться как часть любой более сложной цепочки:

const passwordRules = [
  (v) => isNonEmptyString(v) || 'пустое значение',
  (v) => validator.isLength(v, { min: 8 }) || 'минимум 8 символов',
  (v) => /[A-Z]/.test(v) || 'нет заглавной буквы',
  (v) => /[0-9]/.test(v) || 'нет цифры'
];

Условные ветвления внутри цепочки

В некоторых случаях логика валидации зависит от контекста. Это приводит к появлению условных ветвлений внутри цепочки:

const rules = [
  (v, ctx) => ctx.required ? !validator.isEmpty(v) || 'обязательное поле' : true,
  (v, ctx) => ctx.type === 'email' ? validator.isEmail(v) || 'email некорректен' : true
];

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

Нормализация данных перед проверкой

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

const normalized = validator.trim(validator.toString(input));

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

Накопление ошибок вместо раннего выхода

Альтернативный подход к цепочкам — выполнение всех проверок с накоплением ошибок:

function validate(value, rules) {
  const errors = [];

  for (const rule of rules) {
    const result = rule(value);
    if (result !== true) {
      errors.push(result);
    }
  }

  return errors;
}

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

Расширение цепочек кастомными валидаторами

Validator.js не ограничивает расширение логики, позволяя внедрять собственные проверки в общий поток:

const isStrongPassword = (v) =>
  validator.isLength(v, { min: 10 }) &&
  /[!@#$%^&*]/.test(v);

Эти функции становятся равноправными участниками цепочки наряду со встроенными методами.

Порядок выполнения и производительность

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

  1. Проверка типа значения
  2. Проверка пустоты
  3. Проверка длины
  4. Регулярные выражения
  5. Сложные бизнес-правила

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

Практическая структура цепочки для формы

Типовая форма регистрации может использовать многоуровневую цепочку:

const usernameRules = [
  (v) => isNonEmptyString(v) || 'пустое имя',
  (v) => validator.isLength(v, { min: 3, max: 20 }) || 'некорректная длина',
  (v) => validator.isAlphanumeric(v) || 'только буквы и цифры'
];

const emailRules = [
  (v) => isNonEmptyString(v) || 'пустое значение',
  (v) => validator.isEmail(v) || 'неверный email'
];

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

Интеграция цепочек в прикладную архитектуру

Цепочки часто встраиваются в слой сервисов или middleware. Это позволяет централизовать контроль данных до попадания в бизнес-логику:

function validateUser(data) {
  return {
    username: validate(data.username, usernameRules),
    email: validate(data.email, emailRules)
  };
}

Такой подход упрощает сопровождение и делает поведение системы предсказуемым при росте количества правил.

Типовые проблемы при построении цепочек

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

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