Кастомные сообщения об ошибках

Валидация данных в 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 позволяет поддерживать этот подход за счёт гибкой системы возврата строк и контекста, где каждая ошибка может нести семантическую нагрузку: код, уровень критичности, локализацию или инструкцию по исправлению.