Форматирование сообщений

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

Базовая модель сообщений

Каждое правило в Vest может возвращать результат в виде проваленной проверки с текстом ошибки. Базовая форма:

test('username', 'Username is required', () => {
  enforce(value).isNotEmpty();
});

В этом случае строка "Username is required" является статическим сообщением. Оно фиксировано и не зависит от входных данных.

Сообщение привязывается к конкретному тесту и сохраняется в структуре результатов, где ключом выступает имя поля и идентификатор проверки.

Динамические сообщения

Часто требуется формировать текст ошибки на основе входного значения. Vest поддерживает передачу функции вместо строки:

test('password', () => {
  enforce(value)
    .longerThan(8)
    .message(() => `Пароль слишком короткий: текущая длина ${value.length}`);
});

Функция сообщения выполняется только при провале теста, что снижает избыточные вычисления и позволяет использовать актуальные данные состояния.

Контекст выполнения и сообщения

В Vest существует механизм контекста, который позволяет передавать внешние параметры в набор тестов. Это особенно важно для локализации и бизнес-правил.

vest.use((data) => ({
  lang: data.lang,
}));

test('email', () => {
  const { lang } = vest.getContext();

  enforce(value).matches(/@/).message(() =>
    lang === 'ru'
      ? 'Некорректный email'
      : 'Invalid email'
  );
});

Контекст позволяет централизованно управлять формулировками без дублирования логики.

Группировка сообщений

Vest организует тесты в группы, что влияет на структуру хранения ошибок. Каждое сообщение относится не только к полю, но и к группе валидации.

group('auth', () => {
  test('email', 'Email invalid', () => {
    enforce(value).matches(/@/);
  });

  test('password', 'Password invalid', () => {
    enforce(value).longerThan(8);
  });
});

Сообщения внутри группы могут агрегироваться и использоваться для отображения блока ошибок формы.

Переопределение сообщений через .message()

Метод .message() позволяет отделить правило проверки от текста ошибки:

test('age', () => {
  enforce(value)
    .isNumber()
    .message('Возраст должен быть числом');

  enforce(value)
    .greaterThanOrEquals(18)
    .message('Возраст должен быть не меньше 18 лет');
});

Каждое условие может иметь собственное сообщение, даже внутри одного теста.

Условные сообщения

Сообщения могут зависеть от условий, включая тип ошибки или состояние данных:

test('username', () => {
  enforce(value)
    .isNotEmpty()
    .message(() => (value ? '' : 'Поле обязательно'));

  enforce(value)
    .longerThan(3)
    .message(() =>
      value.length < 3
        ? 'Слишком короткое имя'
        : 'Ошибка имени'
    );
});

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

Локализация сообщений

Локализация реализуется через вынесение словарей и использование контекста или внешних функций:

const messages = {
  ru: {
    required: 'Поле обязательно',
    email: 'Неверный email',
  },
  en: {
    required: 'Field is required',
    email: 'Invalid email',
  },
};

test('email', () => {
  const { lang } = vest.getContext();

  enforce(value)
    .matches(/@/)
    .message(messages[lang].email);
});

Такой подход позволяет масштабировать систему сообщений без изменения логики тестов.

Переиспользуемые сообщения

Для унификации часто используют фабрики сообщений:

const msg = {
  required: (field) => `${field} обязательно`,
  min: (field, n) => `${field} должно быть не меньше ${n}`,
};

test('password', () => {
  enforce(value)
    .isNotEmpty()
    .message(msg.required('Пароль'));

  enforce(value)
    .longerThan(8)
    .message(msg.min('Пароль', 8));
});

Это уменьшает дублирование и упрощает сопровождение.

Сообщения и асинхронные проверки

Асинхронные тесты могут формировать сообщения после завершения запроса:

test('username', async () => {
  const exists = await api.checkUsername(value);

  enforce(exists === false)
    .message(() => 'Имя пользователя уже занято');
});

Сообщение формируется только после получения результата асинхронной операции, что сохраняет консистентность состояния.

Приоритет сообщений при множественных ошибках

Если несколько проверок одного поля не проходят, Vest сохраняет каждое сообщение отдельно. При отображении можно учитывать порядок выполнения:

  • первое правило может задавать базовую ошибку
  • последующие уточняют причину
  • сообщения не перезаписывают друг друга автоматически
test('password', () => {
  enforce(value)
    .isNotEmpty()
    .message('Пароль обязателен');

  enforce(value)
    .longerThan(8)
    .message('Минимальная длина 8 символов');
});

Интеграция сообщений с результатом валидации

Результат валидации представляет собой структурированный объект, где сообщения связаны с путями полей:

{
  errors: {
    email: ['Invalid email'],
    password: ['Password invalid']
  }
}

Такая структура упрощает интеграцию с UI-фреймворками, где сообщения отображаются рядом с соответствующими полями формы.

Формирование сообщений через условия ветвления

В сложных сценариях сообщение зависит от комбинации правил:

test('amount', () => {
  enforce(value).isNumber();

  if (value < 0) {
    enforce(false).message('Сумма не может быть отрицательной');
  } else if (value > 1000) {
    enforce(false).message('Превышен лимит суммы');
  }
});

Подобный подход используется при реализации бизнес-ограничений, выходящих за рамки простых валидаторов.

Роль сообщений в архитектуре правил

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