Создание собственных правил

Создание собственных правил в Validator.js основано на расширении набора функций валидации за счёт пользовательских проверок, которые соответствуют специфике предметной области. Библиотека изначально предоставляет широкий набор готовых проверок строк, чисел, дат и других типов данных, однако реальные проекты часто требуют более строгих или нестандартных условий.


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

Типовой контракт пользовательского правила:

function customRule(value) {
  return true; // или false
}

Расширенные варианты предполагают возврат строки с ошибкой:

function customRule(value) {
  if (value < 10) {
    return 'Значение должно быть не меньше 10';
  }
  return true;
}

Инкапсуляция правил в отдельные функции

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

const isEvenNumber = (value) => {
  if (typeof value !== 'number') {
    return 'Значение должно быть числом';
  }
  return value % 2 === 0 || 'Число должно быть чётным';
};

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


Композиция пользовательских правил

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

const composeValidators = (...validators) => (value) => {
  for (const validator of validators) {
    const result = validator(value);
    if (result !== true) {
      return result;
    }
  }
  return true;
};

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

const isPositive = (value) => value > 0 || 'Должно быть положительным';
const isInteger = (value) => Number.isInteger(value) || 'Должно быть целым числом';

const validateNumber = composeValidators(isPositive, isInteger);

Расширение стандартного набора проверок

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

import validator from 'validator';

const isCorporateEmail = (email) => {
  if (!validator.isEmail(email)) {
    return 'Некорректный email';
  }

  const domain = email.split('@')[1];
  return domain.endsWith('company.com') || 'Разрешены только корпоративные адреса';
};

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


Использование фабрик правил

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

const minLength = (min) => (value) => {
  if (typeof value !== 'string') {
    return 'Значение должно быть строкой';
  }
  return value.length >= min || `Минимальная длина ${min} символов`;
};

Применение:

const validateUsername = minLength(6);

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


Контекстные правила валидации

Некоторые проверки требуют доступа к внешнему состоянию или другим полям объекта. В таких случаях правило принимает дополнительный параметр контекста.

const matchField = (fieldName) => (value, context) => {
  return value === context[fieldName] || 'Значения не совпадают';
};

Пример контекста:

const context = {
  password: '123456'
};

const confirmPasswordValidator = matchField('password');
confirmPasswordValidator('123456', context);

Нормализация входных данных

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

const normalizeString = (value) => {
  if (typeof value !== 'string') {
    return '';
  }
  return value.trim().toLowerCase();
};

После нормализации значение становится предсказуемым для последующих проверок.


Обработка сложных структур данных

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

const validateArray = (rule) => (arr) => {
  if (!Array.isArray(arr)) {
    return 'Значение должно быть массивом';
  }

  for (const item of arr) {
    const result = rule(item);
    if (result !== true) {
      return result;
    }
  }

  return true;
};

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


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

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

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

Порядок правил становится частью логики, а не просто технической деталью.


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

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

const userValidationRules = {
  username: composeValidators(
    minLength(6),
    (v) => /^[a-z0-9_]+$/.test(v) || 'Недопустимые символы'
  ),
  age: (v) => v >= 18 || 'Возраст должен быть не менее 18'
};

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


Ошибки проектирования пользовательских правил

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

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

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


Масштабирование системы правил

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

const rulesRegistry = new Map();

const registerRule = (name, fn) => {
  rulesRegistry.set(name, fn);
};

const getRule = (name) => rulesRegistry.get(name);

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