Failures в библиотеке Superstruct представляют собой центральный механизм описания ошибок валидации данных. В отличие от простого булевого результата проверки, система failures позволяет получить детализированную картину того, почему структура данных не прошла проверку, на каком уровне вложенности это произошло и какие именно значения вызвали несоответствие.
При выполнении валидации любой структуры через Superstruct
результатом может быть либо успешное значение, либо выброс ошибки типа
StructError. Именно внутри этого исключения и содержится
набор failures — объектов, описывающих каждое отдельное нарушение
правил.
Ключевая особенность заключается в том, что одна проверка может породить сразу несколько failures. Это особенно заметно при работе с массивами, объектами и union-типами, где каждая ветка проверки добавляет собственные данные о несоответствиях.
Внутренне failures представляют собой итерируемую коллекцию, а не просто массив, что позволяет библиотеке эффективно обрабатывать большие структуры без лишнего копирования данных.
Каждый failure в Superstruct описывает конкретную точку несоответствия данных ожидаемой схеме. Структура объекта включает несколько ключевых полей:
path — массив, описывающий путь до значения, вызвавшего
ошибку. Это один из самых важных элементов, поскольку он позволяет точно
определить место в глубоко вложенной структуре.
Пример:
path: ['user', 'address', 'zip']
Такой путь означает, что ошибка произошла в поле zip,
находящемся внутри address, который, в свою очередь,
является частью объекта user.
Путь формируется динамически при обходе структуры и всегда отражает реальную навигацию по данным.
value содержит фактическое значение, которое не прошло
проверку. Это позволяет быстро понять природу ошибки без необходимости
повторного доступа к исходному объекту.
Пример:
value: "12AB"
type указывает на базовый тип структуры, который
ожидался в момент проверки. Это может быть string,
number, array, object или
пользовательский struct.
Важно понимать, что type отражает не JavaScript-тип, а именно тип, заданный в системе Superstruct.
refinement присутствует только в случаях, когда ошибка
возникает на уровне дополнительной проверки (refinement). Это может быть
кастомное правило, заданное через refine.
Пример:
refinement: 'positiveNumber'
Если значение проходит базовую проверку типа, но не удовлетворяет дополнительному условию, именно refinement фиксирует причину.
message — текстовое описание ошибки. Оно может
генерироваться автоматически или задаваться вручную при создании
структуры.
Автоматическое сообщение формируется на основе ожидаемого и полученного значений, а также контекста структуры.
branch — полный путь значений от корня структуры до
текущего узла, включая промежуточные объекты. В отличие от
path, который хранит только ключи, branch содержит реальные
данные на каждом уровне.
Пример:
branch: [{ user: { address: { zip: "12AB" } } }, { address: { zip: "12AB" } }, "12AB"]
Этот механизм особенно полезен при сложных вложенных структурах, где важно видеть не только путь, но и контекст данных.
В некоторых реализациях failure-объектов присутствуют поля:
expected — ожидаемый тип или значениеreceived — фактическое значение или типЭти поля делают ошибки более читаемыми при логировании.
Superstruct не останавливается на первой ошибке (за исключением специальных режимов). Вместо этого она собирает все возможные нарушения в рамках одной проверки.
Это поведение особенно важно для UX-слоёв, где необходимо показать пользователю полный список ошибок формы, а не только первую найденную.
Пример:
{
failures: [
{ path: ['email'], value: 'abc', type: 'string', message: 'Invalid email' },
{ path: ['age'], value: -5, type: 'number', message: 'Must be positive' }
]
}
Такой подход снижает количество повторных итераций проверки и улучшает интерактивную обработку данных.
При валидации объектов failures формируются рекурсивно. Каждый
уровень вложенности добавляет свой сегмент в path и
расширяет branch.
Например, при проверке структуры:
{
user: {
profile: {
name: 123
}
}
}
Failure будет иметь путь:
['user', 'profile', 'name']
и значение:
123
Это делает диагностику ошибок предсказуемой даже в глубоко вложенных схемах.
При работе с массивами failures получают числовые индексы в
path.
Пример:
['items', 2, 'price']
Это означает, что ошибка произошла у третьего элемента массива
items.
Особенность обработки массивов заключается в том, что failures могут быть сгенерированы для каждого элемента независимо, что позволяет получить полную картину некорректных данных.
Union-типы являются одной из самых сложных частей системы проверки. В случае несоответствия ни одному из вариантов union формируется набор failures от каждой ветки проверки.
Каждый failure при этом сохраняет информацию о том, какая именно ветка была проверена.
Это приводит к появлению нескольких параллельных причин ошибки,
которые затем агрегируются в общий StructError.
Сообщения в failure могут быть как автоматически сгенерированными, так и заданными вручную через описание структуры.
Автоматическая генерация основывается на:
При кастомных структурах разработчик может полностью контролировать текст сообщения, что позволяет адаптировать failures под бизнес-логику.
Failures в Superstruct реализованы как итерируемая структура. Это
означает, что их можно обрабатывать через for...of без
преобразования в массив.
Пример обработки:
for (const failure of error.failures()) {
console.log(failure.path, failure.message);
}
Такой подход уменьшает накладные расходы памяти при обработке больших структур.
Внутренне Superstruct может нормализовать failures для унификации формата. Это особенно важно при объединении результатов от разных типов структур.
Нормализация включает:
Failures проектировались с учётом необходимости минимального overhead при массовой валидации. Поэтому:
Это позволяет использовать Superstruct в высоконагруженных сценариях, таких как валидация API-запросов или потоковых данных.
На практике failures чаще всего применяются для:
Часто используется сопоставление failures с UI-полями, где
path.join('.') служит ключом для отображения ошибки.
При использовании пользовательских структур failures могут расширяться дополнительными полями через композицию проверок. Это позволяет внедрять доменные правила без потери стандартного механизма диагностики.
Refinement-ошибки особенно часто используются в этом сценарии, поскольку они позволяют разделить базовую типизацию и бизнес-логику.
При объединении структур failures не теряют контекст. Каждая вложенная структура добавляет свои данные, сохраняя общую цепочку.
Это обеспечивает прозрачную трассировку ошибок даже в сложных
композициях, где одна структура включает другую через
object, array или union.
Superstruct допускает сценарии, где часть структуры валидируется, а часть — игнорируется. В таких случаях failures формируются только для проверяемых узлов, не затрагивая остальную структуру.
Это особенно важно при инкрементальной валидации данных, поступающих частями.