Базовые сообщения

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

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


Структура базового сообщения

В простейшем случае сообщение задаётся непосредственно в момент объявления теста:

test('username', 'Имя пользователя обязательно', () => {
  enforce(value).isNotEmpty();
});

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

Ключевые свойства базового сообщения:

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

Сообщение как часть контракта теста

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

  • какое условие проверяется;
  • что сообщается при его нарушении.
test('password', 'Пароль слишком короткий', () => {
  enforce(value).longerThanOrEquals(8);
});

В данном случае сообщение не описывает техническое условие (>= 8), а формирует пользовательскую интерпретацию результата. Такой подход отделяет бизнес-логику от внутренней реализации проверок.


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

Сообщения могут зависеть от входных данных. Для этого используется функция вместо строки:

test('age', ({ value }) => {
  if (value < 18) {
    return 'Возраст должен быть не менее 18 лет';
  }
  return;
}, () => {
  enforce(value).greaterThanOrEquals(18);
});

Динамическое сообщение позволяет:

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

Часто используется интерполяция значений:

test('username', ({ value }) => {
  return `Имя пользователя "${value}" недопустимо`;
}, () => {
  enforce(value).matches(/^[a-z0-9_]+$/i);
});

Сообщения и результат теста

В Vest результат теста формируется как часть структуры result после выполнения validate. Каждое сообщение связывается с конкретным полем и тестом.

Пример структуры результата:

{
  errors: {
    username: ['Имя пользователя обязательно'],
    password: ['Пароль слишком короткий']
  }
}

Если к одному полю применяется несколько тестов, каждое сообщение добавляется в массив ошибок этого поля. Это важно для последующей агрегации и отображения на интерфейсе.


Приоритет сообщений

При наличии нескольких проваленных проверок порядок сообщений определяется порядком объявления тестов:

test('password', 'Пароль обязателен', () => {
  enforce(value).isNotEmpty();
});

test('password', 'Пароль слишком короткий', () => {
  enforce(value).longerThan(8);
});

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


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

Vest позволяет формировать сообщения в зависимости от контекста выполнения теста через доступ к context:

test('email', ({ field }) => {
  if (field === 'email') {
    return 'Некорректный формат электронной почты';
  }
  return;
}, () => {
  enforce(value).matches(/.+@.+\..+/);
});

Контекст может включать:

  • имя поля;
  • текущее значение;
  • дополнительные параметры теста;
  • метаданные формы.

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

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

group('auth', () => {
  test('email', 'Некорректный email', () => {
    enforce(value).isEmail();
  });

  test('password', 'Пароль обязателен', () => {
    enforce(value).isNotEmpty();
  });
});

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


Сообщения и повторное использование логики

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

const isValidUsername = () => {
  return enforce(value).matches(/^[a-z0-9_]{3,}$/i);
};

test('username', 'Недопустимое имя пользователя', isValidUsername);

Такой подход позволяет:

  • переиспользовать проверки в разных формах;
  • централизовать логику;
  • изменять сообщения независимо от условий.

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

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

const messages = {
  required: 'Поле обязательно для заполнения',
  email: 'Некорректный формат email'
};

test('email', messages.email, () => {
  enforce(value).isEmail();
});

Более сложные системы локализации используют ключи вместо строк:

test('email', 'validation.email.invalid', () => {
  enforce(value).isEmail();
});

Интерпретация ключей выполняется на уровне UI или слоя отображения ошибок.


Сообщения уровня поля и уровня формы

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

Полевая ошибка:

test('username', 'Имя пользователя занято', () => {
  enforce(value).isAvailable();
});

Глобальная ошибка:

test('form', 'Форма содержит ошибки', () => {
  enforce(values).satisfies(allValid);
});

Глобальные сообщения часто используются для отображения состояния формы в целом, например при серверной валидации.


Сообщения при асинхронной валидации

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

test('username', 'Имя уже занято', async () => {
  const available = await api.checkUsername(value);
  enforce(available).isTruthy();
});

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


Поведение пустых сообщений

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

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

Такие тесты применяются в сценариях, где:

  • сообщения генерируются централизованно;
  • используется внешний слой интерпретации ошибок;
  • реализуется строгая типизация ошибок без текстового слоя.

Агрегация и повторяющиеся сообщения

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


Сообщения как часть декларативной модели

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

Каждое сообщение:

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