Извлечение конкретных ошибок

Механизм валидации в библиотеке строится вокруг объекта ValidationError, который возвращается после выполнения функции validate() или validateSync(). Каждый такой объект описывает не только факт ошибки, но и её точное место в структуре данных, включая вложенные поля и массивы.

Базовая структура ошибки:

interface ValidationError {
  target?: object;
  property: string;
  value?: any;
  constraints?: {
    [type: string]: string;
  };
  children?: ValidationError[];
  contexts?: {
    [type: string]: any;
  };
}

Ключевое значение здесь имеет поле constraints. Оно содержит набор нарушенных правил, где ключ — тип валидатора, а значение — текст ошибки.

Пример результата:

{
  property: "email",
  value: "wrong-email",
  constraints: {
    isEmail: "email must be an email"
  }
}

Получение ошибок после validate()

Функция validate() возвращает массив ошибок. Даже если ошибка одна, результат всегда массив.

import { validate } from "class-validator";

const errors = await validate(user);

Каждый элемент массива соответствует одному полю, нарушившему правила.

Фильтрация ошибок по конкретному полю

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

const emailError = errors.find(err => err.property === "email");

Дальнейший доступ к причинам ошибки:

const message = emailError?.constraints
  ? Object.values(emailError.constraints)[0]
  : null;

Извлечение всех сообщений ошибок

Часто требуется не структура, а плоский список текстов ошибок. Для этого используется рекурсивный обход:

function extractMessages(errors) {
  const messages = [];

  for (const error of errors) {
    if (error.constraints) {
      messages.push(...Object.values(error.constraints));
    }

    if (error.children && error.children.length) {
      messages.push(...extractMessages(error.children));
    }
  }

  return messages;
}

Результат — единый массив строк без структуры:

[
  "email must be an email",
  "password must be longer than 8 characters"
]

Работа с вложенными объектами

При использовании вложенных DTO (@ValidateNested) ошибки приобретают древовидную структуру.

Пример:

class Profile {
  @IsString()
  city: string;
}

class User {
  @ValidateNested()
  profile: Profile;
}

Ошибка внутри profile будет находиться в children:

const profileError = errors.find(e => e.property === "profile");

const cityError = profileError?.children?.find(
  e => e.property === "city"
);

Извлечение ошибок по пути (path-based access)

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

function findErrorByPath(errors, path) {
  const parts = path.split(".");
  let current = errors;

  for (const part of parts) {
    const found = current.find(e => e.property === part);
    if (!found) return null;
    current = found.children || [];
  }

  return current;
}

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

const error = findErrorByPath(errors, "profile.city");

Работа с constraints и типами ошибок

Поле constraints может содержать несколько нарушений одновременно:

{
  constraints: {
    isString: "must be a string",
    minLength: "too short"
  }
}

Извлечение всех типов ошибок:

const types = Object.keys(error.constraints);

Извлечение первого сообщения:

const firstMessage = Object.values(error.constraints)[0];

Нормализация ошибок в единую структуру

Часто требуется преобразовать ошибки в формат API:

function normalizeErrors(errors) {
  return errors.map(err => ({
    field: err.property,
    messages: err.constraints
      ? Object.values(err.constraints)
      : [],
    children: err.children?.length
      ? normalizeErrors(err.children)
      : []
  }));
}

Результат становится удобным для фронтенда:

[
  {
    field: "email",
    messages: ["email must be an email"],
    children: []
  }
]

Поиск ошибок по типу валидатора

Иногда важно найти ошибки конкретного типа, например isNotEmpty:

function findByConstraint(errors, type) {
  const result = [];

  for (const error of errors) {
    if (error.constraints?.[type]) {
      result.push(error.constraints[type]);
    }

    if (error.children?.length) {
      result.push(...findByConstraint(error.children, type));
    }
  }

  return result;
}

Пример:

findByConstraint(errors, "isNotEmpty");

Извлечение только полей с ошибками

Для упрощённой диагностики можно получить список проблемных полей:

function collectFields(errors) {
  const fields = [];

  for (const err of errors) {
    fields.push(err.property);

    if (err.children?.length) {
      fields.push(...collectFields(err.children));
    }
  }

  return fields;
}

Уникализация ошибок

При глубоких структурах ошибки могут дублироваться. Используется Set:

function uniqueMessages(errors) {
  const set = new Set();

  const walk = (errs) => {
    for (const e of errs) {
      if (e.constraints) {
        Object.values(e.constraints).forEach(m => set.add(m));
      }

      if (e.children?.length) {
        walk(e.children);
      }
    }
  };

  walk(errors);

  return [...set];
}

Использование validateSync для синхронной обработки

Синхронная версия позволяет работать без async:

import { validateSync } from "class-validator";

const errors = validateSync(user);

Дальнейшая обработка идентична, так как структура ошибок не отличается.

Извлечение ошибок в формате field → message

Часто требуется простое отображение:

function mapFieldErrors(errors) {
  const result = {};

  for (const err of errors) {
    result[err.property] = err.constraints
      ? Object.values(err.constraints)[0]
      : null;
  }

  return result;
}

Пример результата:

{
  email: "email must be an email",
  password: "password must be longer than 8 characters"
}

Контексты ошибок (contexts)

Некоторые валидаторы добавляют дополнительную информацию:

{
  contexts: {
    minLength: {
      required: 8,
      actual: 5
    }
  }
}

Извлечение контекста:

const context = error.contexts?.minLength;

Это позволяет строить более информативные сообщения на уровне бизнес-логики.

Объединение всех подходов в единый обход

Комплексная функция для извлечения полной информации:

function deepExtract(errors) {
  const result = [];

  const walk = (errs, path = "") => {
    for (const e of errs) {
      const currentPath = path ? `${path}.${e.property}` : e.property;

      if (e.constraints) {
        for (const [type, message] of Object.entries(e.constraints)) {
          result.push({
            path: currentPath,
            type,
            message
          });
        }
      }

      if (e.children?.length) {
        walk(e.children, currentPath);
      }
    }
  };

  walk(errors);

  return result;
}

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