Интерполяция значений в сообщениях

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

Интерполяция особенно полезна в случаях:

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

Базовый синтаксис интерполяции

Yup использует шаблоны вида:

${parameter}

Подстановка выполняется автоматически при возникновении ошибки.

Пример:

import * as yup from 'yup';

const schema = yup.string().min(5, 'Минимальная длина: ${min}');

Проверка:

await schema.validate('abc');

Результат:

Минимальная длина: 5

Доступные параметры интерполяции

Каждый метод Yup передаёт собственный набор параметров.

min()

yup.string().min(5, 'Минимум ${min} символов')

Доступно:

${min}

max()

yup.string().max(10, 'Максимум ${max} символов')

Доступно:

${max}

length()

yup.string().length(8, 'Длина должна быть ${length}')

Доступно:

${length}

matches()

yup
  .string()
  .matches(/^\d+$/, 'Значение "${value}" должно содержать только цифры');

Доступно:

${value}

oneOf()

yup
  .string()
  .oneOf(['admin', 'user'], 'Допустимые значения: ${values}');

Доступно:

${values}

Результат:

Допустимые значения: admin, user

number().min()

yup
  .number()
  .min(18, 'Возраст должен быть не меньше ${min}');

date().min()

yup
  .date()
  .min(new Date('2025-01-01'), 'Дата должна быть позже ${min}');

Использование ${value}

${value} содержит текущее проверяемое значение.

Пример:

const schema = yup.string().email(
  'Адрес "${value}" не является корректным email'
);

Проверка:

await schema.validate('wrong-email');

Ошибка:

Адрес "wrong-email" не является корректным email

Использование ${path}

${path} содержит путь к полю.

Особенно полезно при вложенных объектах.

Пример:

const schema = yup.object({
  user: yup.object({
    email: yup.string().required('${path} обязательно')
  })
});

Ошибка:

user.email обязательно

Интерполяция внутри вложенных объектов

const schema = yup.object({
  profile: yup.object({
    username: yup
      .string()
      .min(4, 'Поле ${path} должно содержать минимум ${min} символа')
  })
});

Ошибка:

Поле profile.username должно содержать минимум 4 символа

Интерполяция в кастомных test()

Метод test() позволяет передавать собственные параметры для шаблонов.

Базовый пример

const schema = yup.string().test(
  'starts-with',
  'Значение должно начинаться с "${prefix}"',
  function (value) {
    const prefix = 'JS';

    if (!value?.startsWith(prefix)) {
      return this.createError({
        message: 'Значение должно начинаться с "${prefix}"',
        params: { prefix }
      });
    }

    return true;
  }
);

Ошибка:

Значение должно начинаться с "JS"

params в createError()

Ключ params определяет набор переменных для интерполяции.

params: {
  key: value
}

Пример:

const schema = yup.number().test(
  'range',
  'Число должно быть между ${min} и ${max}',
  function (value) {
    const min = 10;
    const max = 20;

    if (value < min || value > max) {
      return this.createError({
        params: { min, max }
      });
    }

    return true;
  }
);

Интерполяция нескольких значений

const schema = yup.string().test(
  'complex',
  'Поле ${field} должно содержать от ${min} до ${max} символов',
  function (value) {
    const min = 3;
    const max = 10;

    if (value.length < min || value.length > max) {
      return this.createError({
        params: {
          field: this.path,
          min,
          max
        }
      });
    }

    return true;
  }
);

Интерполяция в required()

const schema = yup.string().required(
  'Поле "${path}" обязательно'
);

Использование label()

Метод label() позволяет задавать человекочитаемое имя поля.

const schema = yup
  .string()
  .label('Имя пользователя')
  .required('${label} обязательно');

Ошибка:

Имя пользователя обязательно

label() во вложенных схемах

const schema = yup.object({
  login: yup
    .string()
    .label('Логин')
    .min(5, '${label} должен содержать минимум ${min} символов')
});

Интерполяция в when()

const schema = yup.object({
  type: yup.string(),

  code: yup.string().when('type', {
    is: 'admin',
    then: schema =>
      schema.required(
        'Для типа "${value}" поле обязательно'
      )
  })
});

Контекстная интерполяция

Yup поддерживает передачу контекста через validate().

Передача context

await schema.validate(data, {
  context: {
    role: 'admin'
  }
});

Использование context в test()

const schema = yup.string().test(
  'role-check',
  'Требуется роль ${role}',
  function (value) {
    const role = this.options.context.role;

    if (value !== role) {
      return this.createError({
        params: { role }
      });
    }

    return true;
  }
);

Интерполяция в setLocale()

Глобальные сообщения Yup также поддерживают шаблоны.

import { setLocale } from 'yup';

setLocale({
  string: {
    min: 'Минимум ${min} символов'
  }
});

Глобальные шаблоны сообщений

setLocale({
  mixed: {
    required: 'Поле ${path} обязательно'
  },

  number: {
    min: 'Минимальное значение: ${min}',
    max: 'Максимальное значение: ${max}'
  }
});

Интерполяция массивов

const schema = yup.array().min(
  2,
  'Минимальное количество элементов: ${min}'
);

Интерполяция в object-схемах

const schema = yup.object().shape({
  name: yup.string().required('${path} обязательно'),
  age: yup.number().min(18, '${path}: минимум ${min}')
});

Использование функций вместо строк

В Yup сообщение может быть функцией.

Пример

const schema = yup.string().min(5, ({ min }) => {
  return `Минимальная длина: ${min}`;
});

Аргументы функции сообщения

Функция получает объект параметров:

({
  value,
  originalValue,
  path,
  spec,
  label,
  min,
  max
})

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

const schema = yup.string().min(5, ({ min, value }) => {
  return `"${value}" слишком короткое. Минимум: ${min}`;
});

Интерполяция и локализация

Шаблоны значительно упрощают мультиязычную поддержку.

setLocale({
  mixed: {
    required: '${label} является обязательным'
  }
});

Смена языка:

setLocale({
  mixed: {
    required: '${label} is required'
  }
});

Использование интерполяции с transform()

const schema = yup
  .string()
  .transform(value => value?.trim())
  .min(3, 'После обработки длина должна быть больше ${min}');

Интерполяция в асинхронных test()

const schema = yup.string().test(
  'username',
  'Пользователь "${username}" уже существует',
  async function (value) {
    const exists = true;

    if (exists) {
      return this.createError({
        params: {
          username: value
        }
      });
    }

    return true;
  }
);

Использование this.path

const schema = yup.string().test(
  'custom',
  '${field} заполнено неверно',
  function () {
    return this.createError({
      params: {
        field: this.path
      }
    });
  }
);

Интерполяция в nullable()

const schema = yup
  .string()
  .nullable()
  .required('${path} не должно быть null');

Комбинирование параметров

const schema = yup.string().test(
  'full',
  'Поле ${field}: минимум ${min}, максимум ${max}',
  function (value) {
    return this.createError({
      params: {
        field: this.path,
        min: 5,
        max: 10
      }
    });
  }
);

Формирование универсальных сообщений

function minLengthMessage(field, min) {
  return `${field} должно содержать минимум ${min} символов`;
}

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

yup.string().min(5, minLengthMessage('Имя', 5));

Частые ошибки

Отсутствие params

Неправильно:

return this.createError({
  message: 'Минимум ${min}'
});

${min} не будет заменён.

Правильно:

return this.createError({
  message: 'Минимум ${min}',
  params: {
    min: 5
  }
});

Неверное имя параметра

message: 'Минимум ${minimum}'
params: { min: 5 }

Подстановка не выполнится.


Использование недоступных параметров

yup.string().required('${min}')

Метод required() не предоставляет min.


Практический пример сложной схемы

const schema = yup.object({
  username: yup
    .string()
    .label('Имя пользователя')
    .min(
      5,
      '${label} должно содержать минимум ${min} символов'
    )
    .max(
      20,
      '${label} должно содержать максимум ${max} символов'
    ),

  password: yup
    .string()
    .label('Пароль')
    .test(
      'strong-password',
      '${label} слишком простой',
      function (value) {
        const hasNumber = /\d/.test(value);
        const hasUppercase = /[A-Z]/.test(value);

        if (!hasNumber || !hasUppercase) {
          return this.createError({
            params: {
              label: 'Пароль'
            }
          });
        }

        return true;
      }
    )
});

Практический пример с контекстом

const schema = yup.string().test(
  'access',
  'Для доступа требуется уровень ${level}',
  function (value) {
    const level = this.options.context.level;

    if (value !== level) {
      return this.createError({
        params: { level }
      });
    }

    return true;
  }
);

Проверка:

await schema.validate('user', {
  context: {
    level: 'admin'
  }
});

Ошибка:

Для доступа требуется уровень admin

Рекомендации по оформлению сообщений

Использование label()

.label('Email')

делает ошибки значительно понятнее, чем:

user.profile.contacts.email

Унификация шаблонов

Хорошая практика:

'${label} обязательно'
'${label} должно быть больше ${min}'
'${label} должно быть меньше ${max}'

Минимизация жёстко заданных строк

Лучше:

'Минимум ${min} символов'

чем:

'Минимум 5 символов'

Поддерживаемые переменные интерполяции

Наиболее распространённые:

Переменная Описание
${value} Текущее значение
${path} Путь к полю
${label} Человекочитаемое имя
${min} Минимальное значение
${max} Максимальное значение
${length} Точная длина
${values} Список допустимых значений

Полный пример

import * as yup from 'yup';

const schema = yup.object({
  email: yup
    .string()
    .label('Email')
    .email('"${value}" не является корректным email')
    .required('${label} обязателен'),

  age: yup
    .number()
    .label('Возраст')
    .min(18, '${label} должен быть не меньше ${min}')
    .max(65, '${label} должен быть не больше ${max}')
});

schema.validate({
  email: 'wrong-email',
  age: 15
});

Ошибки:

"wrong-email" не является корректным email
Возраст должен быть не меньше 18