Поведение, при котором данные проходят валидацию несмотря на
очевидное несоответствие правилам, почти всегда связано не с ошибкой
декораторов, а с контекстом выполнения проверки и тем, в каком виде
объект попадает в class-validator.
Валидация в этой библиотеке опирается на два ключевых условия: наличие корректно созданного экземпляра класса и фактическое присутствие метаданных декораторов, которые применяются к этому экземпляру. Нарушение любого из этих условий приводит к тому, что проверки либо пропускаются, либо не срабатывают вовсе.
Одна из самых частых причин — передача «сырых» объектов, пришедших, например, из HTTP-запроса.
class CreateUserDto {
@IsEmail()
email: string;
@Length(8)
password: string;
}
При проверке:
const dto = {
email: "not-an-email",
password: "123"
};
validate(dto);
валидация не даст ожидаемого результата, поскольку объект не содержит
метаданных класса CreateUserDto. Декораторы работают только
с экземплярами, созданными через new.
Корректный вариант требует преобразования:
const dto = plainToInstance(CreateUserDto, {
email: "not-an-email",
password: "123"
});
и только затем:
validate(dto);
Отсутствие этого шага приводит к ситуации, когда проверки формально вызываются, но не имеют к чему применяться.
Библиотека class-validator часто используется вместе с
class-transformer. Без него входные данные остаются
обычными объектами JavaScript.
Критичный момент проявляется при использовании вложенных структур:
class Profile {
@IsString()
city: string;
}
class User {
@ValidateNested()
profile: Profile;
}
Без plainToInstance вложенный объект не превращается в
Profile, и @ValidateNested() фактически не
запускает внутреннюю проверку.
Опция skipMissingProperties изменяет поведение валидации
таким образом, что отсутствующие поля не приводят к ошибке.
validate(dto, { skipMissingProperties: true });
При этом поле может быть обязательным с точки зрения бизнес-логики:
class User {
@IsNotEmpty()
username: string;
}
Если username отсутствует, но включён
skipMissingProperties, ошибка не возникнет. Это создаёт
иллюзию «успешной» валидации при неполных данных.
TypeScript-типизация не влияет на runtime-валидацию. Особенно критично это проявляется с числами и булевыми значениями, приходящими из JSON.
class Product {
@IsNumber()
price: number;
}
При входных данных:
{ price: "100" }
валидация может пройти успешно, если включено преобразование типов:
enableImplicitConversion: true
В таком случае строка "100" автоматически преобразуется
в число, и проверка @IsNumber() проходит.
Декоратор @ValidateIf позволяет полностью исключать поле
из проверки при выполнении условия.
class User {
@ValidateIf(o => o.isAdmin === true)
@IsEmail()
email: string;
}
Если условие возвращает false, остальные декораторы
игнорируются. Это часто становится причиной ситуации, когда поле кажется
«невалидным», но ошибка не появляется.
Различие между null и undefined влияет на
поведение многих валидаторов.
class Example {
@IsNotEmpty()
value: string;
}
undefined может игнорироваться при включённых опциях
пропускаnull часто требует отдельного декоратора
@IsNotEmpty({ each: false }) или
@IsDefinedОтсутствие явной проверки @IsDefined() приводит к тому,
что поле считается допустимо отсутствующим.
Использование групп позволяет включать и выключать правила в зависимости от сценария.
class User {
@IsEmail({}, { groups: ["create"] })
email: string;
}
Если при вызове валидации не указаны группы:
validate(dto, { groups: ["update"] });
правила для create не выполняются. Это создаёт ситуацию,
когда часть ограничений «исчезает» в зависимости от контекста.
Даже при наличии @ValidateNested() проверка не
выполняется без указания типа вложенного объекта.
class User {
@ValidateNested()
profile: Profile;
}
Без @Type(() => Profile) из
class-transformer вложенный объект остаётся обычным
литералом:
class User {
@ValidateNested()
@Type(() => Profile)
profile: Profile;
}
Отсутствие этого преобразования приводит к тому, что вложенная структура фактически игнорируется.
Опции whitelist и forbidNonWhitelisted
влияют не на «валидацию», а на очистку входного объекта.
validate(dto, {
whitelist: true
});
При включённом whitelist лишние поля удаляются, но не
вызывают ошибку. Это часто воспринимается как «валидация прошла», хотя
фактически данные были изменены.
Для строгого поведения требуется:
validate(dto, {
whitelist: true,
forbidNonWhitelisted: true
});
class-validator не добавляет поля автоматически. Если
свойство не существует в объекте, декораторы не активируются.
class User {
@IsInt()
age: number;
}
При объекте:
{}
без @IsDefined() ошибка может не появиться в зависимости
от конфигурации, поскольку валидатор не всегда считает отсутствие
свойства нарушением.
При использовании глобальных пайпов (например, в NestJS) часто включается:
transform: true
или:
transformOptions: {
enableImplicitConversion: true
}
Это изменяет входные данные до валидации. В результате:
undefinedФактический объект, который проходит валидацию, уже отличается от исходного запроса, что приводит к неожиданному «успеху» проверок.
Ситуации, когда данные проходят валидацию вопреки ожиданиям, почти всегда связаны не с самими декораторами, а с одним из факторов:
validate()class-transformergroups,
ValidateIf)@Type и
@ValidateNestedПоведение библиотеки строго соответствует полученному runtime-объекту, а не исходной структуре данных или TypeScript-типам.