Infinity и специальные значения

В JavaScript числовой тип допускает несколько особых значений, которые выходят за пределы привычной арифметики: Infinity, -Infinity, NaN. При построении схем валидации с использованием Joi важно учитывать, что такие значения не являются «ошибочными» с точки зрения языка, но часто требуют явного разрешения или блокировки на уровне бизнес-логики.

Число в JavaScript может принимать следующие специальные состояния:

  • Infinity — положительная бесконечность, результат деления положительного числа на ноль
  • -Infinity — отрицательная бесконечность
  • NaN — «не число», результат некорректных арифметических операций

Валидационные схемы, ориентированные на прикладные данные (цены, координаты, лимиты), обычно рассматривают эти значения как недопустимые. Однако в системах математических расчётов, симуляциях или потоковой обработке данных они могут быть допустимыми.

Поведение Joi при работе с Infinity

В Joi числовая схема создаётся через Joi.number(). По умолчанию валидатор ориентирован на конечные числа, и специальные значения требуют отдельного рассмотрения.

Базовая схема числа

import Joi from 'joi';

const schema = Joi.number();

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

Однако на практике поведение зависит от дополнительных модификаторов и версии Joi, а также от режима строгой проверки.

Явное ограничение бесконечных значений

Для исключения бесконечностей используется явное ограничение диапазона:

const schema = Joi.number().finite();

Метод finite() ограничивает допустимые значения только конечными числами, исключая:

  • Infinity
  • -Infinity
  • NaN

Таким образом, это основной инструмент для строгой числовой валидации.

Разрешение Infinity и -Infinity

Если требуется поддержка бесконечностей, используется расширение допустимых значений через allow:

const schema = Joi.number().allow(Infinity, -Infinity);

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

Работа с NaN

NaN имеет особый статус: он не равен самому себе и часто исключается валидаторами автоматически.

В Joi его обработка также требует явного разрешения:

const schema = Joi.number().allow(NaN);

Однако в прикладных схемах NaN почти всегда считается ошибочным состоянием данных. Более строгий вариант:

const schema = Joi.number().finite().strict();

В этом случае любые нестандартные числовые значения исключаются.

Комбинирование правил для специальных значений

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

const schema = Joi.number()
  .allow(Infinity, -Infinity)
  .custom((value, helpers) => {
    if (Number.isNaN(value)) {
      return helpers.error('number.base');
    }
    return value;
  });

Такая конструкция позволяет:

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

Особенности строгого режима

При использовании строгой валидации (strict()), Joi перестаёт выполнять неявные преобразования типов. Это влияет на обработку строковых представлений специальных значений:

Joi.number().strict();

В таком режиме строки "Infinity" или "NaN" не будут интерпретироваться как числовые значения и вызовут ошибку валидации.

Преобразование строковых значений

Без строгого режима Joi может приводить некоторые строковые значения к числам:

  • "123"123
  • "Infinity"Infinity (в зависимости от конфигурации и версии)
  • "NaN"NaN

Это поведение важно учитывать при работе с внешними API, где данные часто приходят в виде строк.

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

const schema = Joi.number().strict().finite();

Использование кастомных правил для контроля границ

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

const schema = Joi.number().custom((value, helpers) => {
  if (value === Infinity || value === -Infinity) {
    return helpers.error('number.infinity');
  }
  if (Number.isNaN(value)) {
    return helpers.error('number.nan');
  }
  return value;
});

Такой подход позволяет:

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

Диапазоны и влияние специальных значений

При использовании min() и max() важно учитывать поведение бесконечностей:

Joi.number().min(0).max(100);

В таком случае:

  • Infinity всегда превышает максимум
  • -Infinity всегда ниже минимума

Однако результат сравнения может зависеть от порядка применения правил и наличия finite().

Более строгая эквивалентная схема:

Joi.number().finite().min(0).max(100);

исключает неоднозначность полностью.

Типичные ошибки при работе с Infinity

Часто встречаются следующие проблемные сценарии:

  • отсутствие finite(), что приводит к неожиданному принятию Infinity
  • смешивание allow(Infinity) и диапазонных ограничений без проверки логики
  • использование NaN как индикатора ошибки вместо явного состояния
  • отсутствие строгого режима при обработке внешних данных

Семантика специальных значений в схемах Joi

С точки зрения валидации важно различать:

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

Joi предоставляет низкоуровневый контроль над типом данных, но семантика специальных чисел должна определяться на уровне схемы. Именно поэтому Infinity и NaN чаще всего не являются частью доменной модели, а выступают как крайние случаи, требующие явного разрешения или блокировки.