Конфликты с другими библиотеками

Пересечение зон ответственности валидации данных

Основная причина конфликтов вокруг class-validator заключается в том, что библиотека работает на уровне декораторов классов и метаданных TypeScript, тогда как многие альтернативные решения используют схемную или функциональную модель валидации.

Типичный конфликт возникает при одновременном использовании:

  • class-validator
  • class-transformer
  • zod
  • joi
  • yup
  • кастомных middleware-валидаторов в Express/Fastify/NestJS

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


Конфликт с class-transformer

Наиболее тесная и одновременно проблемная связка — это class-validator + class-transformer.

Источник конфликта

  • class-validator работает с экземплярами классов
  • class-transformer отвечает за преобразование plain object → class instance

Если преобразование не выполнено, валидация либо не сработает, либо будет частичной.

Типичный сбой

import { validate } from "class-validator";

class User {
  name: string;
}

const user = { name: "John" };

validate(user as any); // валидируется некорректно

Проблема: объект не является экземпляром User, метаданные декораторов не применяются.

Правильный пайплайн

import { plainToInstance } from "class-transformer";
import { validate } from "class-validator";

const dto = plainToInstance(User, user);
await validate(dto);

Скрытый конфликт

При использовании NestJS конфликт становится неявным: pipeline трансформации встроен, и разработчик может ошибочно считать, что class-validator работает с plain object напрямую.


Конфликт метаданных reflect-metadata

class-validator полностью зависит от:

  • reflect-metadata
  • emitDecoratorMetadata

Проблема №1: отсутствие глобального импорта

Если reflect-metadata не импортирован первым:

import "reflect-metadata";

метаданные не будут записаны, и валидаторы будут “молчать”.

Проблема №2: дублирование метаданных

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

  • routing-controllers
  • typeorm
  • class-validator

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

Это приводит к:

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

Конфликт с NestJS ValidationPipe

NestJS использует class-validator как базовый механизм, но добавляет собственный слой:

  • ValidationPipe
  • автоматическая трансформация DTO
  • глобальные пайпы

Типичная проблема дублирования

@Post()
create(@Body() dto: CreateUserDto) {}

Если включены:

app.useGlobalPipes(new ValidationPipe({ transform: true }));

и одновременно вручную вызывается plainToInstance, происходит двойная трансформация:

  • первая — NestJS
  • вторая — пользовательская

Результат:

  • потеря типов
  • некорректная вложенная валидация
  • проблемы с @Type(() => Class)

Конфликты с Zod, Joi и Yup

Разная философия валидации

  • class-validator — декларативные декораторы
  • zod/joi/yup — схемы функций

Основной конфликт — двойная система источника истины.

Типичный антипаттерн

class UserDto {
  @IsString()
  name: string;
}

// параллельно
const schema = z.object({
  name: z.string()
});

Это приводит к:

  • рассинхронизации правил
  • необходимости синхронного обновления двух систем
  • различному поведению runtime/compile-time

Конфликт на уровне типов

Zod и Yup выводят типы автоматически, тогда как class-validator требует отдельного TypeScript слоя. В результате:

  • типы TS ≠ runtime правила
  • увеличивается риск рассинхронизации

Конфликт с Express middleware

В экосистеме Express часто встречаются кастомные валидаторы:

  • middleware проверки body
  • schema validation middleware
  • request sanitizers

Проблема порядка выполнения

app.post("/user", middlewareA, middlewareB, handler);

Если class-validator вызывается внутри handler, а middleware уже модифицировал req.body, возникают расхождения:

  • middleware изменяет структуру
  • DTO ожидает исходную структуру

Результат:

  • частичная валидация
  • ошибки только на runtime

Конфликты с Fastify схемами

Fastify предпочитает JSON Schema:

  • быстрая валидация на уровне маршрута
  • встроенная оптимизация

При добавлении class-validator появляется двойная проверка:

  1. JSON Schema validation (Fastify)
  2. class-validator (DTO layer)

Это приводит к:

  • лишним вычислениям
  • несогласованным ошибкам (разный формат error response)
  • усложнению дебага

Конфликт с сериализацией и class-transformer groups

class-validator поддерживает группы:

@IsString({ groups: ["create"] })
name: string;

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

  • @Expose(), @Exclude() из class-transformer
  • custom serialization logic

возникает расхождение:

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

Это создаёт логический парадокс:

  • валидатор требует поле
  • сериализатор его удаляет

Конфликт версий зависимостей

class-validator чувствителен к:

  • версии TypeScript
  • версии reflect-metadata
  • версии class-transformer

Типичная ситуация:

  • обновление class-transformer → изменения поведения декораторов
  • старый class-validator → некорректная обработка metadata

Последствия:

  • валидаторы перестают срабатывать без явных ошибок
  • ошибки проявляются только в runtime

Конфликт наследования DTO

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

class BaseDto {
  @IsString()
  id: string;
}

class CreateUserDto extends BaseDto {
  @IsString()
  name: string;
}

И одновременной работе с:

  • class-transformer
  • NestJS validation pipe

возникают проблемы:

  • потеря родительских метаданных
  • частичная валидация полей
  • переопределение decorators

Конфликт с автоматической генерацией схем

Некоторые инструменты генерируют схемы из классов:

  • OpenAPI generators
  • Swagger decorators
  • tsoa

class-validator не всегда полностью совместим с этими системами:

  • ограничения не всегда транслируются
  • кастомные валидаторы игнорируются
  • сложные условия (@ValidateIf) теряются

Конфликт нескольких систем ошибок

Каждая библиотека формирует ошибки по-разному:

  • class-validator: массив ValidationError
  • zod: структурированные ошибки с path
  • joi: detailed error objects
  • Fastify: schema-based error format

При объединении в одном API:

  • отсутствует единый формат ответа
  • требуется нормализация слоя ошибок
  • усложняется обработка фронтендом

Конфликт глобальных и локальных валидаторов

В приложениях часто встречается смешивание:

  • глобальных pipe/middleware валидаторов
  • локальных validate() вызовов
await validate(dto);

и одновременно:

app.useGlobalPipes(new ValidationPipe());

Это приводит к:

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

Конфликт производительности при комбинировании библиотек

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

  • class-validator
  • class-transformer
  • JSON schema middleware
  • runtime schema validation

наблюдается:

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

Особенно критично в high-load API, где DTO создаются массово.


Конфликт модели данных: классы против схем

Фундаментальное противоречие:

  • class-validator опирается на классы и декораторы
  • современные альтернативы — на схемы и функции

Это создаёт архитектурный разрыв:

  • классы сложнее сериализуются
  • схемы проще композируются
  • классы хуже подходят для tree-shaking

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