В библиотеке Superstruct каждая ошибка валидации представлена
объектом StructError. Этот объект содержит полную
информацию о том, где именно произошла ошибка и почему проверка не была
пройдена.
Ключевые поля ошибки:
Пример:
import { object, string, validate } from "superstruct";
const User = object({
name: string(),
});
const [error] = validate({ name: 123 }, User);
console.log(error);
Результат будет содержать структурированное описание, где
path укажет на ["name"], а value
будет равен 123.
По умолчанию сообщения ошибок в Superstruct формируются на английском языке и ориентированы на разработчика, а не на конечного пользователя. Это создаёт необходимость трансформации ошибок в человекочитаемый и локализованный формат.
Типичная проблема:
Expected a string, but received: 123
В реальных интерфейсах требуется:
Superstruct позволяет переопределять сообщения на уровне каждой
структуры через функцию message.
import { string } from "superstruct";
const Name = string();
const NameWithMessage = string([
(value) => typeof value === "string" || "Имя должно быть строкой",
]);
Такой подход позволяет локализовать сообщения на уровне конкретного валидатора, но становится неудобным при большом количестве полей.
Основной способ локализации — постобработка ошибок после валидации.
import { validate } from "superstruct";
const [error] = validate(data, UserStruct);
if (error) {
const localized = localizeError(error);
}
Функция локализации:
function localizeError(error) {
const dictionary = {
string: "Ожидается строка",
number: "Ожидается число",
required: "Поле обязательно",
};
return {
path: error.path.join("."),
message: dictionary[error.type] || "Ошибка валидации",
value: error.value,
};
}
Такой подход отделяет бизнес-логику валидации от слоя представления.
При использовании вложенных объектов путь ошибки становится составным:
const Profile = object({
user: object({
name: string(),
}),
});
Если ошибка возникает в name, путь будет:
["user", "name"]
Для локализации это важно, поскольку позволяет:
Пример преобразования:
function formatPath(path) {
return path.join(".");
}
Результат: user.name
Расширенный способ локализации основан на словаре переводов:
const messages = {
ru: {
string: "Ожидается строка",
number: "Ожидается число",
boolean: "Ожидается логическое значение",
},
en: {
string: "Expected a string",
number: "Expected a number",
boolean: "Expected a boolean",
},
};
Функция выбора языка:
function translateError(error, locale = "ru") {
const dict = messages[locale];
return dict[error.type] || "Ошибка валидации";
}
Такой слой позволяет масштабировать систему на множество языков без изменения схем.
Стандартная ошибка Superstruct не содержит пользовательских подписей полей. Для реальных приложений требуется добавление контекста:
const fieldLabels = {
name: "Имя пользователя",
age: "Возраст",
};
Комбинированная локализация:
function enhanceError(error) {
const field = error.path[error.path.length - 1];
const label = fieldLabels[field] || field;
return `${label}: ${translateError(error)}`;
}
Результат:
Имя пользователя: Ожидается строка
При массовой валидации удобно агрегировать ошибки по полям:
function groupErrors(errors) {
return errors.reduce((acc, err) => {
const key = err.path.join(".");
acc[key] = acc[key] || [];
acc[key].push(err.message);
return acc;
}, {});
}
Это упрощает интеграцию с формами и UI-библиотеками.
При создании пользовательских структур можно сразу внедрять локализацию:
import { define } from "superstruct";
const PositiveNumber = define("PositiveNumber", (value) => {
if (typeof value !== "number") return "Ожидается число";
if (value <= 0) return "Число должно быть больше нуля";
return true;
});
Такой подход переносит ответственность за сообщение прямо в валидатор.
В сложных системах часто используется единый трансформер:
function transformStructError(error) {
return {
field: error.path.join("."),
code: error.type,
message: translateError(error),
raw: error.value,
};
}
Это позволяет унифицировать формат ошибок между разными модулями приложения.
При работе с массивами пути ошибок включают индексы:
["users", 0, "email"]
Локализация должна учитывать числовые сегменты:
function formatPath(path) {
return path
.map((segment) => (typeof segment === "number" ? `[${segment}]` : segment))
.join(".");
}
Результат:
users[0].email
В системах с Superstruct важно разделять два уровня сообщений:
Технические:
Expected string, received number
Пользовательские:
Введите текстовое значение
Такое разделение позволяет не смешивать внутреннюю диагностику и внешний UX.
Гибкая архитектура обычно включает три слоя:
Пример пайплайна:
const [error] = validate(data, Schema);
if (error) {
const localized = transformStructError(error);
showErrors(localized);
}
Иногда сообщение зависит от значения:
(value) => value.length > 5 || "Максимальная длина — 5 символов";
Для локализации:
(value) =>
value.length > 5 ||
`Слишком длинное значение: ${value.length}`;
Для масштабных приложений формируется единый контракт:
{
field: string,
message: string,
code: string,
value: any
}
Superstruct предоставляет данные, но финальная форма всегда определяется уровнем локализации.
При интеграции Superstruct в крупные системы важно учитывать:
Такая архитектура позволяет использовать одну и ту же валидацию для разных языков интерфейса без дублирования логики.