В JavaScript числовой тип допускает несколько особых значений,
которые выходят за пределы привычной арифметики: Infinity,
-Infinity, NaN. При построении схем валидации
с использованием Joi важно учитывать, что такие значения не являются
«ошибочными» с точки зрения языка, но часто требуют явного разрешения
или блокировки на уровне бизнес-логики.
Число в JavaScript может принимать следующие специальные состояния:
Infinity — положительная бесконечность, результат
деления положительного числа на ноль-Infinity — отрицательная бесконечностьNaN — «не число», результат некорректных арифметических
операцийВалидационные схемы, ориентированные на прикладные данные (цены, координаты, лимиты), обычно рассматривают эти значения как недопустимые. Однако в системах математических расчётов, симуляциях или потоковой обработке данных они могут быть допустимыми.
В Joi числовая схема создаётся через Joi.number(). По
умолчанию валидатор ориентирован на конечные числа, и специальные
значения требуют отдельного рассмотрения.
import Joi from 'joi';
const schema = Joi.number();
Такая схема допускает любые числовые значения, включая
Infinity и -Infinity, поскольку проверка
ограничивается типом number, а не его диапазоном.
Однако на практике поведение зависит от дополнительных модификаторов и версии Joi, а также от режима строгой проверки.
Для исключения бесконечностей используется явное ограничение диапазона:
const schema = Joi.number().finite();
Метод finite() ограничивает допустимые значения только
конечными числами, исключая:
Infinity-InfinityNaNТаким образом, это основной инструмент для строгой числовой валидации.
Если требуется поддержка бесконечностей, используется расширение
допустимых значений через allow:
const schema = Joi.number().allow(Infinity, -Infinity);
Такая схема явно добавляет оба специальных значения в список разрешённых. Это делает поведение предсказуемым и исключает зависимость от внутренних правил приведения типов.
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;
});
Такая конструкция позволяет:
При использовании строгой валидации (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);
исключает неоднозначность полностью.
Часто встречаются следующие проблемные сценарии:
finite(), что приводит к неожиданному
принятию Infinityallow(Infinity) и диапазонных ограничений
без проверки логикиNaN как индикатора ошибки вместо явного
состоянияС точки зрения валидации важно различать:
Joi предоставляет низкоуровневый контроль над типом данных, но
семантика специальных чисел должна определяться на уровне схемы. Именно
поэтому Infinity и NaN чаще всего не являются
частью доменной модели, а выступают как крайние случаи, требующие явного
разрешения или блокировки.