Сообщения об ошибках в кастомных валидаторах

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


Базовый механизм формирования ошибки

Кастомный валидатор в Yup задаётся через test, который принимает имя проверки и функцию-валидатор:

Yup.string().test(
  'is-even-length',
  'Длина строки должна быть чётной',
  (value) => {
    if (!value) return true;
    return value.length % 2 === 0;
  }
);

В этом варианте строка 'Длина строки должна быть чётной' является статическим сообщением об ошибке, которое возвращается при провале проверки.

Однако такой подход ограничен: сообщение не зависит от входных данных и контекста.


Использование динамических сообщений

Функция-валидатор может возвращать не только true/false, но и управлять ошибкой через this.createError:

Yup.string().test(
  'min-words',
  function (value) {
    const min = 3;

    if (!value) return true;

    const words = value.trim().split(/\s+/);

    if (words.length < min) {
      return this.createError({
        message: `Минимальное количество слов: ${min}. Текущее: ${words.length}`,
      });
    }

    return true;
  }
);

Здесь используется контекст this, который предоставляет доступ к:

  • path — путь к полю в объекте
  • parent — родительский объект
  • originalValue — исходное значение
  • createError — фабрика ошибок

Такой подход позволяет формировать сообщения, зависящие от состояния данных.


Контекстные данные в сообщениях

Кастомные ошибки часто строятся на основе контекста схемы:

Yup.number().test(
  'max-by-role',
  function (value) {
    const { role } = this.parent;
    const max = role === 'admin' ? 1000 : 100;

    if (value > max) {
      return this.createError({
        message: `Превышено допустимое значение (${max}) для роли ${role}`,
      });
    }

    return true;
  }
);

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


Параметризация сообщений через options

createError поддерживает дополнительные поля, влияющие на структуру ошибки:

return this.createError({
  message: 'Некорректное значение',
  path: this.path,
});

path задаёт конкретное поле, к которому привязывается ошибка. Это важно при вложенных объектах, где ошибка может быть зарегистрирована на уровне глубже, чем сама схема.


Возврат строки vs createError

В Yup существует два подхода:

1. Простое сообщение

return false;

или

return 'Ошибка';

В этом случае Yup автоматически оборачивает результат в ValidationError.

2. Полный контроль через createError

return this.createError({ message: 'Ошибка' });

Этот способ предпочтителен при сложной логике, так как позволяет:

  • задавать path
  • использовать динамические данные
  • контролировать вложенные ошибки

Ошибки с несколькими условиями

В одном валидаторе может быть несколько причин ошибки:

Yup.string().test(
  'complex-check',
  function (value) {
    if (!value) {
      return this.createError({ message: 'Значение обязательно' });
    }

    if (value.length < 5) {
      return this.createError({ message: 'Минимальная длина 5 символов' });
    }

    if (!/^[a-z]+$/.test(value)) {
      return this.createError({ message: 'Допустимы только латинские буквы' });
    }

    return true;
  }
);

Важно учитывать, что Yup по умолчанию останавливается на первой ошибке, если не изменён режим abortEarly.


Влияние abortEarly на ошибки кастомных валидаторов

При abortEarly: true возвращается первая ошибка:

Yup.string().test('t', function (value) {
  if (!value) return this.createError({ message: 'A' });
  if (value.length < 5) return this.createError({ message: 'B' });
  if (!value.includes('x')) return this.createError({ message: 'C' });
  return true;
});

При abortEarly: false могут быть собраны все ошибки, если они возникают на разных уровнях схемы.


Асинхронные кастомные ошибки

Кастомные валидаторы могут быть асинхронными:

Yup.string().test(
  'exists',
  async function (value) {
    const exists = await checkUser(value);

    if (!exists) {
      return this.createError({
        message: 'Пользователь не найден',
      });
    }

    return true;
  }
);

Асинхронная функция должна возвращать Promise<boolean | ValidationError>.


Структура ValidationError

При генерации ошибки Yup формирует объект:

  • message — текст ошибки
  • path — путь к полю
  • value — значение
  • type — тип валидатора
  • inner — вложенные ошибки (для объектов и массивов)

Кастомные валидаторы влияют на message, path и type, если они заданы вручную.


Переопределение типа ошибки

Тип ошибки может быть задан явно:

return this.createError({
  message: 'Недопустимое значение',
  type: 'custom-range-error',
});

Это позволяет различать ошибки на уровне обработки формы или API.


Интеграция с локализацией

Часто сообщения об ошибках выносятся в словари:

const messages = {
  required: 'Поле обязательно',
  min: (min) => `Минимум ${min} символов`,
};

Yup.string().test('min', function (value) {
  if (value.length < 3) {
    return this.createError({
      message: messages.min(3),
    });
  }
  return true;
});

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


Использование setLocale и кастомные тесты

Глобальные сообщения Yup можно переопределять:

Yup.setLocale({
  mixed: {
    required: 'Обязательное поле',
  },
});

Однако кастомные валидаторы имеют приоритет, так как createError всегда перекрывает локализацию.


Ошибки в вложенных структурах

При работе с объектами важно корректно задавать path:

Yup.object({
  user: Yup.object({
    age: Yup.number().test(
      'adult',
      function (value) {
        if (value < 18) {
          return this.createError({
            message: 'Возраст должен быть 18+',
            path: 'user.age',
          });
        }
        return true;
      }
    ),
  }),
});

Без явного path Yup автоматически определяет путь, но в сложных схемах это может привести к неточной привязке ошибки.


Приоритет сообщений в цепочках валидации

В Yup валидаторы выполняются в порядке объявления:

Yup.string()
  .min(5, 'Слишком коротко')
  .test('custom', function (value) {
    return this.createError({ message: 'Кастомная ошибка' });
  });

Если срабатывает кастомный test, он полностью перекрывает предыдущие сообщения.


Использование значения и оригинального значения

Контекст позволяет различать трансформированное и исходное значение:

Yup.string()
  .transform((value) => value.trim())
  .test('compare', function (value) {
    if (this.originalValue !== value) {
      return this.createError({
        message: 'Значение было изменено перед валидацией',
      });
    }
    return true;
  });

Это полезно при сложных цепочках transform.


Итоговая модель поведения ошибок в кастомных валидаторах

Механизм формирования сообщений об ошибках в кастомных проверках Yup опирается на три уровня контроля:

  • простое логическое возвращение true/false
  • возврат строки как автоматического сообщения
  • использование this.createError для полного управления структурой ошибки

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