Debugging схем

В процессе работы с библиотекой Joi ключевым элементом диагностики схем становится объект ошибки, возвращаемый методами validate и validateAsync. Стандартная ошибка содержит поле details, представляющее массив объектов, каждый из которых описывает отдельное нарушение правил схемы.

Каждый элемент details включает:

  • message — человекочитаемое описание проблемы;
  • path — путь к узлу данных, где произошла ошибка;
  • type — тип нарушения (например, string.min, number.base);
  • context — дополнительный контекст (ожидаемое значение, фактическое значение и параметры правила).

Именно анализ type и path позволяет точно локализовать ошибку в сложных вложенных структурах.


Режим abortEarly и накопление ошибок

По умолчанию Joi останавливает проверку при первой найденной ошибке. Это поведение определяется опцией abortEarly.

schema.validate(data, { abortEarly: false })

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

Типичные сценарии:

  • при разработке схем — всегда используется false;
  • в production — часто используется true для ускорения проверки.

Локализация ошибки через path

Поле path является основным инструментом навигации по структуре данных.

Пример:

details: [
  {
    path: ['user', 'profile', 'age'],
    type: 'number.min'
  }
]

Такой результат указывает на точное место нарушения внутри вложенного объекта.

При сложных структурах (массивы объектов, динамические ключи) path становится критически важным для построения диагностических логов.


Использование describe для анализа схемы

Метод describe() позволяет получить декларативное описание схемы без выполнения валидации.

const description = schema.describe()

В возвращаемой структуре присутствуют:

  • типы полей;
  • применённые правила;
  • вложенные схемы;
  • модификаторы (required, optional, default).

Этот механизм используется для:

  • отладки сложных схем;
  • генерации документации;
  • анализа несовместимых правил.

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

Конфликт правил

Частая проблема возникает при наложении взаимоисключающих ограничений:

Joi.number().min(10).max(5)

Такая схема всегда невалидна, однако ошибка может быть неочевидной при динамической генерации.


Неверная структура вложенных объектов

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

Joi.object({
  user: Joi.object({
    age: Joi.number()
  })
})

и данных:

{ user: null }

В этом случае ошибка возникает на уровне object.base, а не внутри age, что затрудняет диагностику без анализа type.


Ошибки типов данных

Joi строго различает типы. Например, строка "123" не считается числом:

  • number.base — передано значение неверного типа;
  • string.base — ожидалась строка, получен другой тип.

Кастомные сообщения и их влияние на диагностику

Использование messages() позволяет переопределять текст ошибок:

Joi.string().min(3).messages({
  'string.min': 'Слишком короткое значение'
})

Однако при чрезмерной кастомизации теряется диагностическая информация, особенно type. Это усложняет автоматическую обработку ошибок.

Оптимальная практика — сохранять оригинальный type, изменяя только текст сообщения.


Async-валидация и ловля ошибок

Метод validateAsync возвращает промис, что требует явной обработки ошибок:

try {
  await schema.validateAsync(data)
} catch (err) {
  console.log(err.details)
}

Особенности диагностики:

  • ошибки имеют ту же структуру, что и синхронные;
  • важно отслеживать unhandled rejection;
  • полезно логировать весь объект ошибки, а не только message.

Использование stripUnknown и влияние на отладку

Опция stripUnknown удаляет поля, отсутствующие в схеме.

Joi.object().unknown(false).validate(data)

При отладке это может приводить к эффекту «исчезающих данных», когда входной объект изменяется до завершения анализа.

Для диагностики:

  • временно отключается stripUnknown;
  • сравнивается исходный и итоговый объект.

Контекст ошибок и динамические схемы

Поле context содержит параметры, переданные в правило.

Пример:

Joi.string().min(5)

Ошибка:

context: {
  limit: 5,
  value: 'abc'
}

В сложных схемах context становится основным источником понимания причины сбоя, особенно при использовании кастомных валидаторов через custom().


Логирование ошибок в сложных схемах

Для масштабных систем важно стандартизировать логирование:

  • path используется как ключ маршрутизации;
  • type — как код ошибки;
  • context — как диагностические данные.

Пример структуры лога:

{
  "field": "user.profile.age",
  "errorType": "number.min",
  "expected": 18,
  "received": 15
}

Такой формат позволяет интегрировать Joi-ошибки в системы мониторинга.


Отладка вложенных массивов и объектов

При работе с массивами объектов ошибки часто дублируются по индексу:

path: ['items', 3, 'price']

Это означает четвёртый элемент массива. При динамической генерации данных важно учитывать:

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

Проверка схем через assert

Метод assert используется для мгновенной валидации:

Joi.assert(data, schema)

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


Диагностика через schema.extract

Метод extract(path) позволяет изолировать часть схемы:

schema.extract('user.profile')

Это применяется для:

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

Проблемы с пересекающимися правилами

При комбинировании .required(), .optional(), .default() часто возникают неоднозначные состояния:

  • значение отсутствует;
  • значение задано явно как undefined;
  • значение заменено через default.

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


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

Функция custom() добавляет произвольную логику:

Joi.string().custom((value, helpers) => {
  if (value === 'x') return helpers.error('any.invalid')
  return value
})

При отладке таких схем важно:

  • явно логировать входные значения;
  • использовать helpers.error() с уникальными кодами;
  • избегать подавления стандартных ошибок.

Сравнение ожидаемого и фактического результата

Один из ключевых методов диагностики — сравнение:

  • исходных данных;
  • нормализованного результата Joi;
  • объекта ошибок.

Это позволяет выявить трансформации, вызванные default, cast или convert.


Ошибки преобразования типов

Joi выполняет неявное приведение типов при включённой опции convert.

Joi.number().validate("123")

Возможные проблемы:

  • строка преобразуется в число;
  • null становится 0 или остаётся null в зависимости от схемы;
  • потеря информации о первоначальном типе.

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


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

Эффективная отладка Joi-схем строится вокруг трёх уровней:

  • структура ошибки (details, path, type);
  • структура схемы (describe, extract);
  • поведение преобразований (convert, default, stripUnknown).

Совокупный анализ этих элементов позволяет точно локализовать источник несоответствия между схемой и данными без необходимости внешних инструментов.