Валидация данных в Superstruct строится вокруг идеи строгих, но расширяемых проверок, где каждое несоответствие сопровождается диагностическим сообщением. В стандартной конфигурации библиотека генерирует сообщения автоматически, однако в реальных приложениях почти всегда требуется управление текстом ошибок: унификация формата, локализация, бизнес-специфичные пояснения или скрытие технических деталей.
Superstruct предоставляет несколько уровней кастомизации: от простого
переопределения сообщений на уровне базовых структур до полного контроля
над логикой формирования ошибок через refine и
пользовательские структуры.
Большинство встроенных типов позволяют передавать пользовательское сообщение в качестве последнего аргумента. Это наиболее простой способ адаптировать ошибки под предметную область.
import { string, size, number, min, max } from "superstruct";
const Name = size(string(), 3, 20, "Имя должно содержать от 3 до 20 символов");
const Age = min(number(), 18, "Возраст должен быть не менее 18 лет");
В этом случае сообщение полностью заменяет стандартное описание ошибки. Такой подход удобен для стабильных правил, которые не требуют динамической информации о значении.
Некоторые структуры позволяют более тонкую настройку через дополнительные параметры.
import { string, pattern } from "superstruct";
const Email = pattern(
string(),
/^[^\s@]+@[^\s@]+\.[^\s@]+$/,
"Некорректный формат email адреса"
);
Здесь сообщение связано не только с типом ошибки, но и с бизнес-ожиданием. Такой стиль часто применяется в формах ввода и API-валидации.
refine для динамических сообщенийНаиболее гибкий механизм кастомизации ошибок — функция
refine. Она позволяет не только проверять значение, но и
возвращать текст ошибки в зависимости от контекста.
import { refine, string } from "superstruct";
const Username = refine(string(), "Username", (value) => {
if (value.includes(" ")) {
return "Имя пользователя не должно содержать пробелы";
}
if (value.length < 4) {
return "Имя пользователя слишком короткое";
}
return true;
});
Поведение функции:
true — значение валидноstring — сообщение об ошибкеfalse — стандартное сообщение SuperstructТаким образом, возврат строки становится основным инструментом формирования диагностического текста.
При необходимости можно учитывать не только само значение, но и путь структуры или вложенность данных.
import { refine, number } from "superstruct";
const PositiveNumber = refine(number(), "PositiveNumber", (value, context) => {
if (value < 0) {
return `Ошибка в поле ${context.path.join(".")}: число должно быть положительным`;
}
return true;
});
context.path позволяет локализовать ошибку в глубоко
вложенных объектах, что особенно важно при работе с API-ответами и
сложными DTO.
defineКогда логика проверки становится сложной, используется
define. Это низкоуровневый механизм, позволяющий создать
полностью собственный тип с управлением ошибками.
import { define } from "superstruct";
const EvenNumber = define("EvenNumber", (value) => {
if (typeof value !== "number") {
return "Значение должно быть числом";
}
if (value % 2 !== 0) {
return "Число должно быть чётным";
}
return true;
});
Особенность define заключается в том, что возвращаемая
строка автоматически интерпретируется как сообщение об ошибке. Это
делает API единообразным с refine, но на более низком
уровне абстракции.
При работе со сложными структурами можно задавать сообщения на уровне полей, сохраняя независимость каждой ошибки.
import { object, string, number } from "superstruct";
const User = object({
name: string(),
age: number(),
});
Добавление кастомных сообщений:
import { size, min } from "superstruct";
const User = object({
name: size(string(), 2, 30, "Имя указано некорректно"),
age: min(number(), 18, "Пользователь должен быть совершеннолетним"),
});
Каждое поле получает собственное сообщение, что упрощает агрегацию ошибок на уровне UI.
Вложенные структуры наследуют правила сообщений от своих компонентов. Это означает, что ошибка внутри объекта сохраняет локализованное сообщение.
const Address = object({
city: string(),
});
const User = object({
address: Address,
});
При ошибке внутри city сообщение формируется на уровне
string() или его кастомной версии, что сохраняет
предсказуемость поведения.
Хотя Superstruct генерирует ошибки на уровне структур, часто
требуется единый формат представления. Обычно это реализуется через
функцию обработки результата assert или
create.
import { assert } from "superstruct";
try {
assert(data, User);
} catch (e) {
console.log(e.failures());
}
Каждое нарушение содержит:
Это позволяет стандартизировать вывод без изменения самих структур.
На практике часто требуется преобразование технических сообщений в пользовательский формат.
Пример преобразования:
function formatError(failure) {
return `${failure.path.join(".")}: ${failure.message}`;
}
Такой подход позволяет отделить логику валидации от слоя представления.
Кастомные сообщения легко адаптируются под мультиязычные приложения, если использовать фабрики сообщений.
const messages = {
ru: {
required: "Поле обязательно",
email: "Некорректный email",
},
en: {
required: "Field is required",
email: "Invalid email",
},
};
const Email = pattern(
string(),
/^[^\s@]+@[^\s@]+\.[^\s@]+$/,
messages.ru.email
);
Для масштабных проектов обычно используется функция выбора языка на уровне контекста приложения.
В более сложных сценариях сообщения формируются на основе внешних параметров:
const minLengthMessage = (n) =>
`Минимальная длина строки — ${n} символов`;
const Password = size(string(), 8, 100, minLengthMessage(8));
Такой подход устраняет дублирование и повышает консистентность сообщений.
В зрелых системах сообщения об ошибках перестают быть просто текстом и становятся частью бизнес-логики. Superstruct позволяет поддерживать этот подход за счёт гибкой системы возврата строк и контекста, где каждая ошибка может нести семантическую нагрузку: код, уровень критичности, локализацию или инструкцию по исправлению.