@IsPositive, @IsNegative

Библиотека class-validator предоставляет набор декораторов для декларативной валидации свойств классов. Среди числовых валидаторов особое место занимают @IsPositive и @IsNegative, предназначенные для проверки знака числового значения. Эти декораторы применяются к числовым полям DTO-моделей и позволяют строго ограничивать допустимые значения на уровне модели данных, до попадания в бизнес-логику.


Базовая концепция числовой валидации

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

Для чисел библиотека предоставляет такие базовые проверки, как:

  • проверка типа (@IsNumber)
  • диапазоны (@Min, @Max)
  • знаковые ограничения (@IsPositive, @IsNegative)

Декораторы @IsPositive и @IsNegative относятся к группе ограничений по знаку числа и работают поверх уже корректного числового значения.


Декоратор @IsPositive

Назначение

@IsPositive проверяет, что значение строго больше нуля. Ноль не считается положительным числом и не проходит валидацию.

Поведение

Валидация проходит успешно, если:

  • значение типа number
  • значение > 0

Валидация проваливается, если:

  • значение равно 0
  • значение отрицательное
  • значение не является числом (если не включены преобразования типов)

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

import { IsPositive } from 'class-validator';

export class CreateProductDto {
  @IsPositive()
  price: number;
}

В данном примере поле price должно быть строго положительным числом. Попытка передать 0 или отрицательное значение приведёт к ошибке валидации.


Поведение с различными входными значениями

Значение Результат
10 проходит
0 ошибка
-5 ошибка
3.14 проходит
“10” ошибка (без transform)

Важный нюанс работы с трансформацией типов

При использовании ValidationPipe в рамках NestJS или аналогичных механизмов, часто включается преобразование типов:

new ValidationPipe({
  transform: true
})

В этом случае строковые значения могут быть преобразованы в числа перед валидацией. Тогда "10" будет преобразовано в 10 и успешно пройдёт проверку @IsPositive.


Декоратор @IsNegative

Назначение

@IsNegative проверяет, что значение строго меньше нуля. Ноль не считается отрицательным значением.

Поведение

Валидация проходит успешно, если:

  • значение типа number
  • значение < 0

Валидация проваливается, если:

  • значение равно 0
  • значение положительное
  • значение не является числом (без трансформации)

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

import { IsNegative } from 'class-validator';

export class TransactionDto {
  @IsNegative()
  balanceChange: number;
}

Здесь поле balanceChange допускает только отрицательные значения, например списания или корректировки в сторону уменьшения.


Поведение с различными значениями

Значение Результат
-10 проходит
-0.5 проходит
0 ошибка
15 ошибка
” -10 ” ошибка (без transform)

Внутренняя логика проверки

Оба декоратора реализуют простую проверку сравнения:

  • @IsPositive эквивалентен проверке value > 0
  • @IsNegative эквивалентен проверке value < 0

При этом библиотека дополнительно:

  • проверяет тип значения (или полагается на предварительную трансформацию)
  • игнорирует NaN как валидное число
  • не приводит типы самостоятельно без участия class-transformer

Совместное использование с @IsNumber

В реальных DTO почти всегда используется комбинирование декораторов:

import { IsNumber, IsPositive } from 'class-validator';

export class PaymentDto {
  @IsNumber()
  @IsPositive()
  amount: number;
}

Причина комбинирования

@IsPositive не гарантирует, что значение является числом. Он лишь проверяет знак. Поэтому без @IsNumber возможны некорректные состояния, особенно при отключённой трансформации:

  • true > 0 → может вести себя непредсказуемо
  • "" > 0 → приводит к странным результатам в JavaScript

Комбинация обеспечивает строгую типовую защиту.


Поведение с NaN и Infinity

Особое внимание требуется при работе с нечисловыми значениями JavaScript.

NaN

@IsPositive()
value: number;
  • NaN всегда считается невалидным
  • проверка NaN > 0 возвращает false

Infinity

  • Infinity проходит @IsPositive
  • -Infinity проходит @IsNegative

Это связано с семантикой JavaScript сравнений, а не с логикой библиотеки.


Использование в бизнес-логике

Финансовые модели

export class WithdrawDto {
  @IsNegative()
  amount: number;
}

Применение @IsNegative позволяет явно ограничить направление движения средств — только списание.


Игровые системы

export class ScoreAdjustmentDto {
  @IsPositive()
  bonus: number;

  @IsNegative()
  penalty: number;
}

Такое разделение позволяет строго контролировать влияние операций на счёт игрока.


Геометрические и физические модели

  • @IsPositive применяется к длинам, массам, времени
  • @IsNegative — к смещениям, потерям, отрицательным изменениям

Ошибки валидации и сообщения

По умолчанию class-validator возвращает стандартные сообщения:

  • value must be a positive number
  • value must be a negative number

Сообщения можно переопределять:

@IsPositive({ message: 'Значение должно быть больше нуля' })
price: number;

Или с параметризацией:

@IsNegative({ message: 'Поле $property должно быть отрицательным' })

Ограничения и особенности

Отсутствие проверки нуля

Оба декоратора строго исключают ноль:

  • 0 не положительное
  • 0 не отрицательное

Это часто требует дополнительной логики, если ноль допустим:

import { Min } from 'class-validator';

@Min(0)
value: number;

Не заменяют @Min и @Max

@IsPositive и @IsNegative не дают диапазонного контроля. Они не заменяют:

  • @Min
  • @Max

Например:

@IsPositive()
@Max(100)
value: number;

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

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

@IsNumber()
@IsPositive()
@Max(50)
value: number;

Логика проверки:

  1. Проверка типа
  2. Проверка знака
  3. Проверка диапазона

Взаимодействие с массивами

При работе с массивами требуется дополнительный декоратор:

import { IsPositive, IsNumber, IsArray } from 'class-validator';

export class BulkDto {
  @IsArray()
  @IsNumber({}, { each: true })
  @IsPositive({ each: true })
  values: number[];
}

Без each: true проверка будет применена к массиву как к объекту, а не к элементам.


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

Отсутствие IsNumber

@IsPositive()
value: number;

Проблема: строка "10" может пройти преобразование неожиданным образом или привести к некорректной логике.


Ожидание включения нуля

Частая ошибка — ожидание, что 0 считается положительным или отрицательным.


Игнорирование transform

Без включённого transform валидация может работать с сырыми строками из запроса:

ValidationPipe({
  transform: false
})

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


Практическая модель DTO с полным контролем

import { IsNumber, IsPositive, IsNegative, IsOptional } from 'class-validator';

export class AccountAdjustmentDto {
  @IsNumber()
  @IsPositive()
  credit: number;

  @IsNumber()
  @IsNegative()
  debit: number;

  @IsOptional()
  @IsNumber()
  @IsPositive()
  bonus?: number;
}

Такая структура позволяет строго разделить финансовые операции по знаку и избежать неоднозначности на уровне входных данных.