Билдер для правил

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

Билдер правил представляет собой слой абстракции над базовыми валидаторами библиотеки validator.js или над композиционными API наподобие цепочек проверок в экосистеме Express. Его задача — превратить набор разрозненных условий в структурированное описание валидации, где каждая часть логически связана с остальными.

Основная идея построения цепочек

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

Типичная структура выглядит как набор шагов:

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

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

class RuleBuilder {
  constructor(field) {
    this.field = field;
    this.rules = [];
  }

  isString() {
    this.rules.push(v => typeof v === 'string');
    return this;
  }

  notEmpty() {
    this.rules.push(v => v && v.length > 0);
    return this;
  }

  minLength(len) {
    this.rules.push(v => typeof v === 'string' && v.length >= len);
    return this;
  }

  build() {
    return {
      field: this.field,
      validate: value => this.rules.every(fn => fn(value))
    };
  }
}

Композиция и переиспользование правил

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

Базовые правила выделяются в отдельные функции или классы:

const baseString = () =>
  new RuleBuilder()
    .isString()
    .notEmpty();

const shortText = () =>
  baseString()
    .minLength(3);

const longText = () =>
  baseString()
    .minLength(10);

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

Интеграция с внешними валидаторами

Билдер часто используется как оболочка над функциями библиотеки validator.js, которая предоставляет набор низкоуровневых проверок (email, URL, numeric и др.). Вместо прямого вызова функций создаётся единый слой композиции.

import isEmail from 'validator/lib/isEmail';

class ExtendedRuleBuilder extends RuleBuilder {
  isEmail() {
    this.rules.push(v => isEmail(v));
    return this;
  }

  isNumeric() {
    this.rules.push(v => !isNaN(parseFloat(v)));
    return this;
  }
}

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

Условная валидация

В реальных сценариях правила часто зависят от контекста. Билдер поддерживает условные ветвления, позволяя добавлять проверки только при выполнении определённых условий.

class ConditionalRuleBuilder extends RuleBuilder {
  when(condition, fn) {
    if (condition) {
      fn(this);
    }
    return this;
  }
}

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

Асинхронные правила

Некоторые проверки требуют обращения к внешним источникам: базе данных, API или файловой системе. Билдер расширяется поддержкой асинхронных функций.

class AsyncRuleBuilder extends RuleBuilder {
  async test(value) {
    for (const rule of this.rules) {
      const result = rule(value);
      if (result instanceof Promise) {
        if (!(await result)) return false;
      } else if (!result) {
        return false;
      }
    }
    return true;
  }
}

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

Схемы валидации как результат билдера

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

const userSchema = [
  new ExtendedRuleBuilder('email')
    .isEmail()
    .build(),

  new ExtendedRuleBuilder('password')
    .minLength(8)
    .build()
];

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

Декларативность и отделение логики

Билдер правил переносит акцент с императивной проверки на декларативное описание. Логика валидации становится частью конфигурации, а не процедурного кода.

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

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

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

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

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

class UserRules {
  static email() {
    return new ExtendedRuleBuilder('email').isEmail();
  }

  static password() {
    return new ExtendedRuleBuilder('password').minLength(8);
  }
}

Ошибки и обработка результатов

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

class ResultRuleBuilder extends RuleBuilder {
  constructor(field) {
    super(field);
    this.errors = [];
  }

  validate(value) {
    this.errors = [];

    for (const rule of this.rules) {
      if (!rule(value)) {
        this.errors.push(`Validation failed for ${this.field}`);
      }
    }

    return this.errors.length === 0;
  }
}

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

Взаимодействие с middleware-архитектурой

В серверных приложениях билдер часто интегрируется как middleware-слой. Валидаторы применяются до выполнения бизнес-логики, обеспечивая чистоту входных данных.

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

Расширяемость через плагины

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

function pluginIsUUID(builder) {
  builder.isUUID = function() {
    this.rules.push(v => /^[0-9a-f]{8}-/.test(v));
    return this;
  };
}

Механизм расширения делает систему гибкой и адаптируемой под специфические доменные задачи.

Оптимизация выполнения правил

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

validate(value) {
  for (const rule of this.rules) {
    if (!rule(value)) return false;
  }
  return true;
}

Такой подход особенно эффективен в высоконагруженных API.