Форматирование ошибок для UI

Библиотека Validator.js предоставляет набор функций для проверки строковых значений: email, URL, длина строки, числовые диапазоны, форматы дат и множество других предикатов. Однако сама библиотека не занимается формированием сообщений для пользовательского интерфейса. Она возвращает булевы значения или вспомогательные результаты, оставляя задачу интерпретации и представления ошибок на уровне приложения.

При построении интерфейсов критически важным становится не только факт наличия ошибки, но и способ её отображения: текст, структура, локализация, контекст поля и формат передачи в UI-слой.


Базовая модель ошибок валидации

На уровне бизнес-логики валидация часто строится как набор независимых проверок:

import validator from "validator";

function validateUser(data) {
  return {
    email: validator.isEmail(data.email),
    password: validator.isLength(data.password, { min: 8 }),
    age: validator.isInt(data.age, { min: 18 })
  };
}

Такой подход удобен для вычислений, но непригоден для интерфейса, поскольку:

  • отсутствуют текстовые описания ошибок
  • нет привязки к конкретному полю в UI
  • невозможно локализовать сообщения
  • сложно отображать несколько ошибок на одно поле

Для UI требуется преобразование булевых результатов в структурированный формат.


Стандартная структура ошибки для интерфейса

Практика фронтенд-разработки использует унифицированную модель ошибок:

{
  field: "email",
  code: "INVALID_EMAIL",
  message: "Некорректный формат email",
  value: "test@"
}

Такая структура позволяет:

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

При использовании Validator.js обычно формируется слой преобразования:

import validator from "validator";

function validate(data) {
  const errors = [];

  if (!validator.isEmail(data.email)) {
    errors.push({
      field: "email",
      code: "INVALID_EMAIL",
      message: "Некорректный формат email"
    });
  }

  return errors;
}

Привязка ошибок к полям формы

В интерфейсах формы ошибок важно группировать по ключу поля. Это упрощает рендеринг и позволяет компонентам работать независимо.

Группировка ошибок

function groupErrors(errors) {
  return errors.reduce((acc, error) => {
    if (!acc[error.field]) {
      acc[error.field] = [];
    }
    acc[error.field].push(error);
    return acc;
  }, {});
}

Результат:

{
  email: [
    { code: "INVALID_EMAIL", message: "Некорректный формат email" }
  ],
  password: [
    { code: "TOO_SHORT", message: "Минимум 8 символов" }
  ]
}

Такой формат напрямую используется UI-компонентами форм.


Преобразование Validator.js в коды ошибок

Validator.js возвращает только boolean, поэтому необходимо явно сопоставлять правила с кодами ошибок.

const errorMap = {
  email: {
    invalid: "INVALID_EMAIL"
  },
  password: {
    tooShort: "PASSWORD_TOO_SHORT"
  }
};

Пример преобразования:

function validate(data) {
  const errors = [];

  if (!validator.isEmail(data.email)) {
    errors.push({
      field: "email",
      code: errorMap.email.invalid,
      message: "Некорректный email"
    });
  }

  if (!validator.isLength(data.password, { min: 8 })) {
    errors.push({
      field: "password",
      code: errorMap.password.tooShort,
      message: "Пароль должен содержать минимум 8 символов"
    });
  }

  return errors;
}

Разделение машинных кодов и пользовательских сообщений

Жёсткое связывание текста ошибки с логикой валидации приводит к проблемам локализации и поддержки. Поэтому используется разделение:

  • code — стабильный идентификатор ошибки
  • message — отображаемый текст

Пример:

const messages = {
  ru: {
    INVALID_EMAIL: "Некорректный формат электронной почты",
    PASSWORD_TOO_SHORT: "Минимальная длина пароля — 8 символов"
  },
  en: {
    INVALID_EMAIL: "Invalid email format",
    PASSWORD_TOO_SHORT: "Password must be at least 8 characters"
  }
};

Функция локализации:

function formatError(error, lang = "ru") {
  return {
    ...error,
    message: messages[lang][error.code] || error.message
  };
}

Контекстные ошибки и параметры валидации

Некоторые правила Validator.js требуют параметров (например, длина строки или диапазоны чисел). Эти параметры полезно включать в структуру ошибки.

{
  field: "password",
  code: "PASSWORD_TOO_SHORT",
  message: "Минимум 8 символов",
  meta: {
    min: 8,
    actual: 5
  }
}

Это позволяет UI отображать более информативные сообщения:

  • текущая длина
  • допустимый диапазон
  • ограничения поля

Агрегация ошибок на уровне формы

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

Плоский формат

[
  { field: "email", code: "INVALID_EMAIL" },
  { field: "password", code: "TOO_SHORT" }
]

Структурированный формат

{
  email: { code: "INVALID_EMAIL" },
  password: { code: "TOO_SHORT" }
}

Плоский формат удобен для логирования и API, структурированный — для UI.


Интеграция с серверной валидацией

При использовании Validator.js на сервере (например, в Node.js) формат ошибок должен совпадать с фронтендом.

Пример Express-обработчика:

app.post("/register", (req, res) => {
  const errors = validate(req.body);

  if (errors.length > 0) {
    return res.status(400).json({
      errors: errors.map(formatError)
    });
  }

  res.sendStatus(200);
});

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


Нормализация ошибок для UI-компонентов

UI-слои часто ожидают стабильную структуру независимо от источника данных. Поэтому вводится слой нормализации:

function normalizeApiErrors(response) {
  return response.errors.reduce((acc, error) => {
    acc[error.field] = formatError(error);
    return acc;
  }, {});
}

Это упрощает интеграцию с формами React, Vue или других фреймворков.


Приоритеты ошибок

При нескольких нарушениях одного поля важно определить порядок отображения.

const priority = {
  REQUIRED: 1,
  INVALID_FORMAT: 2,
  TOO_SHORT: 3
};

Сортировка:

function sortErrors(errors) {
  return errors.sort((a, b) => priority[a.code] - priority[b.code]);
}

UI отображает только наиболее критичную ошибку или список в порядке значимости.


Lazy-формирование сообщений

Для оптимизации можно хранить не готовый текст, а функцию генерации сообщения:

const messages = {
  PASSWORD_TOO_SHORT: (meta) =>
    `Минимум ${meta.min} символов, сейчас ${meta.actual}`
};

Использование:

function formatError(error) {
  const msg = messages[error.code];

  return {
    ...error,
    message: typeof msg === "function" ? msg(error.meta) : msg
  };
}

Такой подход особенно полезен при динамических ограничениях.


Отображение ошибок в UI-компонентах

Формат данных напрямую влияет на структуру компонентов формы.

Пример состояния формы:

{
  values: {
    email: "",
    password: ""
  },
  errors: {
    email: { message: "Некорректный email" },
    password: { message: "Минимум 8 символов" }
  }
}

Компонент поля использует только собственный ключ:

  • значение
  • ошибка
  • статус валидности

Логирование и трассировка ошибок

Помимо UI-отображения, структурированные ошибки Validator.js используются для аналитики:

{
  field: "email",
  code: "INVALID_EMAIL",
  timestamp: 1710000000,
  route: "/register"
}

Это позволяет:

  • выявлять проблемные поля
  • анализировать частоту ошибок
  • улучшать UX форм

Объединение клиентской и серверной модели

Единая модель ошибок упрощает архитектуру:

  • Validator.js используется для базовых проверок
  • сервер формирует стандартизированные коды
  • UI отображает локализованные сообщения
  • аналитика использует машинные коды

Согласованность достигается через единый контракт:

{
  field,
  code,
  message,
  meta
}

Такой подход устраняет разрыв между слоями приложения и обеспечивает предсказуемое поведение интерфейса при любых сценариях валидации.