@IsEnum

Назначение и логика работы

Декоратор @IsEnum в библиотеке class-validator используется для проверки того, что значение свойства принадлежит заранее определённому перечислению (enum). Он обеспечивает строгую валидацию входных данных на уровне типов и значений, что особенно важно при работе с DTO в серверных приложениях.

Основная задача — гарантировать, что переданное значение строго совпадает с одним из допустимых значений enum, независимо от того, используется строковый или числовой формат перечисления.


Базовый принцип проверки

@IsEnum сравнивает значение свойства с набором значений, извлечённых из объекта enum. В зависимости от типа enum (строковый или числовой), поведение отличается:

  • для строковых enum сравнение идёт по строкам;
  • для числовых enum учитывается двустороннее отображение TypeScript enum-объектов;
  • любые значения вне набора считаются невалидными.

Синтаксис декоратора

IsEnum(entity: object, validationOptions?: ValidationOptions)

Параметры:

  • entity — объект enum, с которым производится сравнение;
  • validationOptions — дополнительные настройки поведения валидации (сообщения, условия, группы).

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

import { IsEnum } from 'class-validator';

enum UserRole {
  Admin = 'admin',
  User = 'user',
  Guest = 'guest',
}

class CreateUserDto {
  @IsEnum(UserRole)
  role: UserRole;
}

В этом случае role может принимать только значения 'admin', 'user', 'guest'.


Поведение со строковыми enum

Строковые enum являются наиболее предсказуемым вариантом для @IsEnum, так как не создают обратного маппинга.

enum Direction {
  Up = 'UP',
  Down = 'DOWN',
  Left = 'LEFT',
  Right = 'RIGHT',
}

class MoveDto {
  @IsEnum(Direction)
  direction: Direction;
}

Корректные значения строго ограничены строками, определёнными в enum.


Поведение с числовыми enum

Числовые enum в TypeScript создают двустороннюю структуру:

enum Status {
  Active,
  Inactive,
}

Фактически объект enum выглядит так:

{
  0: "Active",
  1: "Inactive",
  Active: 0,
  Inactive: 1
}

@IsEnum учитывает только корректные числовые значения, игнорируя строковые ключи при проверке входных данных.

class AccountDto {
  @IsEnum(Status)
  status: Status;
}

Допустимыми значениями будут 0 и 1.


Валидация входных данных из JSON

При работе с HTTP-запросами данные приходят как строки, поэтому возникает важный нюанс:

  • строковый enum требует точного совпадения строк;
  • числовой enum может потребовать преобразования типов.

Пример проблемного случая:

{
  "status": "1"
}

Даже если 1 допустим как число, строка "1" не пройдет проверку без трансформации.


Использование с кастомными сообщениями

validationOptions позволяют задавать собственные сообщения об ошибках.

import { IsEnum } from 'class-validator';

class PaymentDto {
  @IsEnum(PaymentMethod, {
    message: 'Недопустимый способ оплаты',
  })
  method: PaymentMethod;
}

Сообщение можно сделать динамическим:

message: (args) =>
  `${args.property} должен быть одним из допустимых значений enum`

Работа с массивами enum

Для проверки массивов используется комбинация @IsEnum и @IsArray с each: true.

import { IsEnum, IsArray } from 'class-validator';

enum Tag {
  News = 'news',
  Sport = 'sport',
  Tech = 'tech',
}

class ArticleDto {
  @IsArray()
  @IsEnum(Tag, { each: true })
  tags: Tag[];
}

Каждый элемент массива проверяется отдельно.


Валидация вложенных структур

При использовании enum внутри вложенных объектов декоратор сохраняет локальную область действия:

class ProfileDto {
  @IsEnum(UserRole)
  role: UserRole;
}

class UserDto {
  profile: ProfileDto;
}

Для корректной работы дополнительно применяются @ValidateNested() и @Type() из class-transformer.


Поведение с undefined и null

@IsEnum не допускает null и undefined по умолчанию.

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

import { IsEnum, IsOptional } from 'class-validator';

class FilterDto {
  @IsOptional()
  @IsEnum(Status)
  status?: Status;
}

Использование с union-подобными enum

Иногда enum используется как замена union типов:

type Role = 'admin' | 'user' | 'guest';

Однако @IsEnum работает только с объектами enum, поэтому применяется альтернативный подход:

const Role = {
  Admin: 'admin',
  User: 'user',
  Guest: 'guest',
} as const;

И затем:

@IsEnum(Role)
role: typeof Role[keyof typeof Role];

Особенности трансформации данных

При использовании class-transformer важно учитывать порядок обработки:

  1. plain object → class instance
  2. преобразование типов
  3. проверка @IsEnum

Если преобразование отключено, числовые значения могут приходить как строки и не проходить валидацию.


Типичные ошибки при использовании

Несовпадение типов

@IsEnum(Status)
status: Status;

Ошибка возникает, если приходит "Active" вместо Status.Active или 0.


Использование объекта вместо enum

const Status = {
  ACTIVE: 'active',
  INACTIVE: 'inactive',
};

Без as const TypeScript не гарантирует корректную типизацию, что может привести к неожиданным значениям.


Передача лишних значений

Любые значения вне enum автоматически считаются ошибкой:

  • дополнительные строки;
  • числа вне диапазона;
  • boolean значения.

Поведение в строгих схемах валидации

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

@IsString()
@IsEnum(UserRole)
role: UserRole;

@IsEnum становится финальной проверкой допустимых значений после базовой проверки типа.


Оптимизация использования

В крупных проектах рекомендуется:

  • централизовать enum-определения;
  • избегать смешанных типов enum (string + number);
  • использовать строковые enum для API контрактов;
  • применять @IsEnum только на границе входных данных.

Совместимость с OpenAPI / Swagger

При интеграции с Swagger через @nestjs/swagger, enum автоматически документируется:

@ApiProperty({ enum: UserRole })
@IsEnum(UserRole)
role: UserRole;

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