@IsNotIn

Декоратор @IsNotIn в библиотеке class-validator используется для валидации значений на принадлежность к «запрещённому набору». Его задача — убедиться, что проверяемое значение не входит в заранее заданный массив.

Основная идея заключается в инверсии проверки принадлежности: если значение найдено в списке — валидация считается проваленной.


Сигнатура и базовая форма

@IsNotIn(values: any[], validationOptions?: ValidationOptions)
  • values — массив значений, которые считаются недопустимыми
  • validationOptions — объект настройки сообщения и поведения ошибки

Базовая логика работы

Внутри механизм реализует простую проверку:

  • значение извлекается из объекта
  • выполняется сравнение через строгую проверку принадлежности (Array.includes)
  • при совпадении хотя бы с одним элементом массива возвращается ошибка валидации

Таким образом, @IsNotIn является противоположностью @IsIn.


Простейший пример использования

import { IsNotIn } from 'class-validator';

class CreateUserDto {
  @IsNotIn(['admin', 'root', 'superuser'])
  username: string;
}

В этом примере поле username не может принимать значения admin, root, superuser.


Поведение при разных типах данных

Проверка выполняется строго, без приведения типов:

@IsNotIn([1, 2, 3])
value: number;
  • value = 1 → ошибка
  • value = "1" → допустимо (разные типы)
  • value = 4 → допустимо

Используется строгое сравнение аналогичное ===.


Настройка сообщения об ошибке

import { IsNotIn } from 'class-validator';

class ProductDto {
  @IsNotIn(['test', 'demo'], {
    message: 'Недопустимое значение для поля name'
  })
  name: string;
}

Сообщение может быть строкой или функцией:

message: ({ value }) => `Значение "${value}" запрещено`

Валидация с динамическими списками

Список запрещённых значений может формироваться динамически:

const bannedNames = ['admin', 'system', 'null'];

class UserDto {
  @IsNotIn(bannedNames)
  username: string;
}

При этом массив фиксируется в момент объявления класса, а не пересчитывается на каждый запрос.


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

По умолчанию class-validator не выполняет проверку, если значение отсутствует, если не заданы дополнительные ограничения:

class UserDto {
  @IsNotIn(['admin'])
  username?: string;
}
  • undefined → пропуск проверки
  • null → поведение зависит от наличия @IsDefined, @IsOptional

Для строгой проверки обычно комбинируется:

import { IsNotIn, IsDefined } from 'class-validator';

class UserDto {
  @IsDefined()
  @IsNotIn(['admin'])
  username: string;
}

Комбинирование с другими декораторами

@IsNotIn часто используется вместе с другими ограничениями:

import { IsString, MinLength, IsNotIn } from 'class-validator';

class AccountDto {
  @IsString()
  @MinLength(3)
  @IsNotIn(['guest', 'anonymous'])
  nickname: string;
}

Порядок декораторов не влияет на итоговую логику, но влияет на читаемость и диагностику ошибок.


Использование в DTO-структурах

Типичный сценарий — проверка входных данных:

class RegisterDto {
  @IsNotIn(['admin', 'root'])
  login: string;

  @IsNotIn(['123456', 'password', 'qwerty'])
  password: string;
}

Каждое поле проверяется независимо, и ошибки формируются отдельно.


Внутреннее поведение при трансформации данных

При использовании class-transformer данные сначала приводятся к экземпляру класса, затем выполняется валидация.

Важно учитывать:

  • строки из JSON всегда остаются строками
  • числа могут быть преобразованы при включённом transform: true
  • сравнение остаётся строгим

Пример:

// вход
{ value: "1" }
@IsNotIn([1])
value: number;

При трансформации в число проверка станет невалидной, иначе — допустимой.


Ошибки и их структура

При нарушении правила возвращается объект ошибки:

{
  "property": "username",
  "constraints": {
    "isNotIn": "username should not be one of the following values: admin, root"
  }
}

Ключ isNotIn формируется автоматически и может быть переопределён через message.


Кастомизация через ValidationOptions

@IsNotIn(['a', 'b'], {
  message: 'Недопустимое значение',
  context: {
    errorCode: 'FORBIDDEN_VALUE'
  }
})
field: string;

context позволяет передавать дополнительные метаданные в систему обработки ошибок.


Особенности работы с массивами

@IsNotIn не предназначен для проверки массива значений целиком. Он проверяет одно значение, а не элементы массива.

Неправильное ожидание:

@IsNotIn(['a', 'b'])
tags: string[]; // некорректная логика использования

В этом случае валидация сравнивает сам массив с элементами, что не даёт ожидаемого результата.

Для массивов требуется использование @ArrayNotContains или кастомной валидации.


Различие между @IsIn и @IsNotIn

  • @IsIn — значение должно присутствовать в списке
  • @IsNotIn — значение не должно присутствовать в списке
@IsIn(['user', 'admin'])
role: string;

@IsNotIn(['banned', 'deleted'])
status: string;

Эти декораторы часто используются совместно для разных полей модели.


Поведение при пустом массиве запрещённых значений

@IsNotIn([])
value: string;

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


Использование в доменной логике

Декоратор часто применяется для ограничения:

  • системных идентификаторов
  • зарезервированных слов
  • технических значений
  • запрещённых пользовательских имён
const RESERVED_WORDS = ['null', 'undefined', 'admin', 'system'];

class ProfileDto {
  @IsNotIn(RESERVED_WORDS)
  slug: string;
}

Частые ошибки при применении

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

Поведение при строгой типизации TypeScript

Типизация не влияет на runtime-проверку. Даже при:

@IsNotIn(['1', '2'])
value: string;

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


Производственные особенности

При большом количестве правил:

  • проверка @IsNotIn имеет сложность O(n)
  • производительность зависит от длины массива запрещённых значений
  • для больших списков предпочтительнее использование Set и кастомных валидаторов
const banned = new Set(['a', 'b', 'c']);

Совместимость с другими механизмами валидации

@IsNotIn корректно работает в связке с:

  • синхронными валидаторами
  • асинхронными валидаторами (не напрямую зависит)
  • пайпами трансформации данных
  • NestJS ValidationPipe (при использовании в рамках DTO)