Агрегация ошибок

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

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

Пример базового подхода:

import validator from 'validator';

const errors = [];

if (!validator.isEmail(email)) {
  errors.push('Некорректный email');
}

if (!validator.isLength(password, { min: 8 })) {
  errors.push('Пароль слишком короткий');
}

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

Понятие агрегации ошибок

Агрегация ошибок — это процесс преобразования разрозненных результатов валидации в единый структурированный объект. Чаще всего используется один из следующих форматов:

  • массив сообщений
  • объект с полями и массивами ошибок
  • вложенная структура (для сложных DTO)
  • карта ошибок (ключ → список сообщений)

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

Простейшая модель агрегации

Наиболее распространённый подход — объект, где ключ соответствует полю данных:

const errors = {};

if (!validator.isEmail(form.email)) {
  errors.email = errors.email || [];
  errors.email.push('Некорректный email');
}

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

В результате формируется структура:

{
  email: ['Некорректный email'],
  password: ['Минимальная длина 8 символов']
}

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

Унифицированная функция агрегации

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

function addError(errors, field, message) {
  if (!errors[field]) {
    errors[field] = [];
  }
  errors[field].push(message);
}

Использование:

const errors = {};

if (!validator.isEmail(form.email)) {
  addError(errors, 'email', 'Некорректный email');
}

if (!validator.isInt(form.age, { min: 18 })) {
  addError(errors, 'age', 'Возраст должен быть не меньше 18');
}

Это упрощает расширение правил и снижает дублирование кода.

Агрегация с использованием схемы полей

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

const rules = {
  email: (value) => validator.isEmail(value) || 'Некорректный email',
  password: (value) =>
    validator.isLength(value, { min: 8 }) || 'Слишком короткий пароль',
};

Функция агрегации:

function validate(data, rules) {
  const errors = {};

  Object.keys(rules).forEach((field) => {
    const result = rules[field](data[field]);

    if (result !== true) {
      addError(errors, field, result);
    }
  });

  return errors;
}

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

Глубокая агрегация ошибок

В реальных системах данные часто имеют вложенную структуру:

const form = {
  user: {
    email: '',
    profile: {
      age: 16,
    },
  },
};

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

const errors = {};

addError(errors, 'user.email', 'Некорректный email');
addError(errors, 'user.profile.age', 'Недопустимый возраст');

Результат:

{
  "user.email": ["Некорректный email"],
  "user.profile.age": ["Недопустимый возраст"]
}

Альтернативный вариант — вложенные объекты:

{
  user: {
    email: ['Некорректный email'],
    profile: {
      age: ['Недопустимый возраст']
    }
  }
}

Выбор структуры зависит от требований клиентской части и формата API.

Нормализация и объединение ошибок

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

Пример объединения:

function mergeErrors(target, source) {
  Object.keys(source).forEach((key) => {
    if (!target[key]) {
      target[key] = [];
    }
    target[key].push(...source[key]);
  });

  return target;
}

Это позволяет объединять результаты разных валидаторов без потери данных.

Агрегация ошибок в цепочках проверок

При последовательной проверке важно предотвращать избыточные вычисления:

const errors = {};

if (validator.isEmpty(form.email)) {
  addError(errors, 'email', 'Email обязателен');
} else if (!validator.isEmail(form.email)) {
  addError(errors, 'email', 'Некорректный email');
}

Использование else if снижает количество ошибок в одном поле, формируя более строгую приоритетность.

Множественные ошибки одного поля

В некоторых сценариях требуется сохранять все возможные нарушения:

if (!validator.isLength(password, { min: 8 })) {
  addError(errors, 'password', 'Минимум 8 символов');
}

if (!validator.matches(password, /[A-Z]/)) {
  addError(errors, 'password', 'Требуется заглавная буква');
}

if (!validator.matches(password, /[0-9]/)) {
  addError(errors, 'password', 'Требуется цифра');
}

Результат:

{
  password: [
    'Минимум 8 символов',
    'Требуется заглавная буква',
    'Требуется цифра'
  ]
}

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

Интеграция с серверной обработкой

В серверных приложениях агрегация ошибок часто становится частью middleware:

function validationMiddleware(req, res, next) {
  const errors = validate(req.body, rules);

  if (Object.keys(errors).length > 0) {
    return res.status(400).json({ errors });
  }

  next();
}

Это обеспечивает единообразный формат ошибок на уровне API.

Оптимизация структуры ошибок

При больших формах важно минимизировать размер ответа:

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

Пример:

{
  email: ['ERR_INVALID_EMAIL'],
  password: ['ERR_PASSWORD_TOO_SHORT']
}

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

Поведенческие модели агрегации

Существует несколько стратегий:

1. Стоп-режим (fail-fast) Остановка после первой ошибки.

2. Полная агрегация Сбор всех возможных ошибок.

3. Приоритетная агрегация Фиксация только критических нарушений.

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

Структурная консистентность

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

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