Библиотека class-validator не выбрасывает исключения
автоматически при каждой валидации. Основной результат работы — это
массив ошибок типа ValidationError, возвращаемый функциями
validate или validateSync. Исключения
появляются только в случаях, когда используется
validateOrReject, либо когда ошибки преобразуются вручную в
исключения приложения (например, в рамках NestJS или собственного слоя
обработки).
Такое разделение позволяет отделить сам процесс проверки данных от политики обработки ошибок, что делает библиотеку гибкой для различных архитектурных подходов.
Каждая ошибка валидации представлена объектом
ValidationError, содержащим подробную информацию о
несоответствии данных правилам:
property — имя поля, где обнаружена ошибкаvalue — значение, вызвавшее нарушениеconstraints — объект с текстами нарушенных правилchildren — вложенные ошибки для сложных объектовОсобенность constraints заключается в том, что одно поле
может нарушать сразу несколько правил, например IsString,
MinLength, IsNotEmpty, что приводит к
множественным сообщениям внутри одного узла ошибки.
Вложенность children критична при работе с DTO,
содержащими сложные структуры. Ошибки формируются рекурсивно, сохраняя
контекст вложенности объектов.
Функция validate возвращает массив ошибок вместо
исключения, поэтому обработка всегда строится через проверку
результата:
const errors = await validate(dto);
if (errors.length > 0) {
// обработка ошибок
}
Такой подход делает обработку явной, но требует дополнительного кода для преобразования ошибок в формат, пригодный для API или логирования.
Типичная проблема возникает при необходимости унификации ответа:
массив ValidationError не подходит для прямой передачи
клиенту, поскольку содержит избыточную вложенность и служебные поля.
Практика обработки почти всегда включает нормализацию структуры ошибок в плоский формат.
Рекурсивный разбор ValidationError позволяет получить
список сообщений:
function flattenErrors(errors: ValidationError[]): string[] {
const result: string[] = [];
for (const error of errors) {
if (error.constraints) {
result.push(...Object.values(error.constraints));
}
if (error.children && error.children.length > 0) {
result.push(...flattenErrors(error.children));
}
}
return result;
}
Такой подход устраняет вложенность и подготавливает данные для передачи в HTTP-ответ.
Функция validateOrReject переводит модель работы в
исключительный режим. При наличии ошибок возвращается отклонённый
Promise с массивом ValidationError.
await validateOrReject(dto);
При ошибке управление переходит в catch:
try {
await validateOrReject(dto);
} catch (errors) {
// обработка ValidationError[]
}
Такой механизм удобен для архитектур, где исключения являются основным способом управления ошибками, особенно в сервисных слоях.
В веб-приложениях часто требуется преобразовать ошибки валидации в
HTTP-ошибки. Наиболее распространённый вариант — формирование
BadRequestException.
import { BadRequestException } from '@nestjs/common';
try {
await validateOrReject(dto);
} catch (errors) {
throw new BadRequestException(errors);
}
Однако прямой возврат ValidationError[] редко
используется без трансформации, поскольку формат слишком детализирован
для клиента.
В системах, использующих NestJS, применяется механизм
exceptionFactory, позволяющий контролировать формат ошибки
на уровне пайпа валидации.
new ValidationPipe({
exceptionFactory: (errors) => {
return new BadRequestException(
errors.map(e => ({
field: e.property,
errors: Object.values(e.constraints || {})
}))
);
}
});
Этот подход переносит ответственность за форматирование ошибок в единый слой, исключая дублирование логики в контроллерах и сервисах.
При работе со сложными структурами ключевую роль играет корректная
обработка children. Ошибки вложенных объектов не
поднимаются автоматически в верхний уровень.
Пример проблемы:
Решение заключается в сохранении пути к полю при обходе дерева ошибок:
function collectErrors(errors: ValidationError[], path = '') {
const result: string[] = [];
for (const error of errors) {
const currentPath = path ? `${path}.${error.property}` : error.property;
if (error.constraints) {
result.push(
...Object.values(error.constraints).map(msg => `${currentPath}: ${msg}`)
);
}
if (error.children?.length) {
result.push(...collectErrors(error.children, currentPath));
}
}
return result;
}
Так формируется полный контекст ошибки, необходимый для диагностики сложных DTO.
Синхронная функция validateSync возвращает массив ошибок
напрямую, без Promise. Это упрощает обработку в утилитарных сценариях,
но лишает возможности работы с асинхронными валидаторами.
const errors = validateSync(dto);
Асинхронный вариант validate необходим при использовании
кастомных декораторов, работающих с внешними сервисами или базой
данных.
Различие влияет на стратегию обработки: синхронный путь чаще используется в утилитах и тестах, асинхронный — в приложениях с внешними зависимостями.
ValidationError содержит опциональные поля, что требует
аккуратной обработки в TypeScript.
Наиболее уязвимые места:
constraints может быть undefinedchildren может отсутствоватьvalue может содержать сложные типы данныхПоэтому безопасная обработка всегда включает проверки наличия полей перед доступом.
В некоторых архитектурах ошибки валидации рассматриваются не как технические сбои, а как часть бизнес-правил. В таком случае результат class-validator преобразуется в доменные объекты ошибок.
Например:
FieldValidationErrorDomainValidationExceptionТакой подход отделяет библиотечную структуру
ValidationError от внутренней модели приложения, снижая
связанность слоёв.
При сложных системах ошибки часто сохраняются в логах. Важно
учитывать, что ValidationError может содержать вложенные
структуры значительного объёма.
Практика логирования обычно включает:
value,
target)Это снижает нагрузку на систему логирования и упрощает анализ инцидентов.
При использовании class-transformer ошибки могут
возникать не только на этапе валидации, но и на этапе преобразования
типов. Это создаёт дополнительный слой, где исключения могут появляться
до вызова validate.
Типичный сценарий:
"123" преобразуется в числоПоэтому обработка ошибок должна учитывать весь pipeline трансформации и валидации, а не только конечный результат class-validator.