Ref и ссылки на другие поля

Валидация данных часто выходит за рамки проверки одного отдельного значения. Во многих случаях необходимо учитывать взаимосвязь между полями: сравнивать значения, проверять зависимости, создавать условные ограничения. В библиотеке Joi для этого используется механизм ссылок (ref), позволяющий обращаться к другим полям внутри объекта схемы.

Основы Joi.ref

Joi.ref() создаёт ссылку на другое поле в объекте, которое может быть использовано в правилах валидации.

Простейший пример сравнения двух полей:

import Joi from 'joi';

const schema = Joi.object({
  password: Joi.string().min(8).required(),
  confirmPassword: Joi.string().valid(Joi.ref('password')).required()
});

В данном случае поле confirmPassword должно строго совпадать со значением password. Если значения различаются, валидация завершится ошибкой.

Ключевая особенность Joi.ref заключается в том, что это не обычное значение, а динамическая ссылка, которая разрешается во время выполнения валидации.

Пути доступа к полям

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

const schema = Joi.object({
  user: Joi.object({
    password: Joi.string().required()
  }),
  confirmPassword: Joi.string().valid(Joi.ref('user.password')).required()
});

Здесь Joi.ref('user.password') указывает на вложенное поле внутри объекта user.

Поддерживаются также массивы:

Joi.ref('items.0.name')

Такой путь обращается к первому элементу массива items и его полю name.

Использование ref в сравнительных операциях

Joi.ref часто применяется в сочетании с методами сравнения:

const schema = Joi.object({
  startDate: Joi.date().required(),
  endDate: Joi.date().greater(Joi.ref('startDate')).required()
});

В этом примере endDate обязан быть больше startDate, что типично для диапазонов дат.

Доступны и другие сравнения:

  • greater(Joi.ref(...))
  • less(Joi.ref(...))
  • min(Joi.ref(...))
  • max(Joi.ref(...))

Пример числовых ограничений:

const schema = Joi.object({
  minValue: Joi.number().required(),
  maxValue: Joi.number().min(Joi.ref('minValue'))
});

Доступ к значениям через контекст

Ссылки могут использоваться не только внутри объекта, но и с доступом к внешнему контексту валидации. Это позволяет подставлять значения из context.

const schema = Joi.object({
  price: Joi.number().max(Joi.ref('$maxPrice'))
});

При валидации:

schema.validate(
  { price: 100 },
  { context: { maxPrice: 150 } }
);

Значение $maxPrice подставляется из контекста. Префикс $ указывает, что ссылка берётся не из объекта данных, а из внешнего контекста.

Условные зависимости с ref

Ссылки активно применяются в условной логике через when.

const schema = Joi.object({
  role: Joi.string().required(),
  accessLevel: Joi.number().when('role', {
    is: 'admin',
    then: Joi.number().min(10),
    otherwise: Joi.number().max(5)
  })
});

Здесь логика зависит от значения другого поля, но можно использовать и ссылки:

const schema = Joi.object({
  threshold: Joi.number().required(),
  value: Joi.number().when(Joi.ref('threshold'), {
    is: Joi.number().min(100),
    then: Joi.number().min(50)
  })
});

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

Алиасы и управление значением ссылки

Joi.ref поддерживает дополнительные параметры, влияющие на поведение ссылки.

Доступ к значению через .value

Joi.ref('price', { adjust: value => value * 2 })

Функция adjust позволяет преобразовать значение перед использованием.

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

Joi.ref('optionalField', { default: 0 })

Если поле отсутствует, будет использовано значение по умолчанию.

Сравнение с самим собой

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

Joi.valid(Joi.ref('.'))

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

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

ref работает внутри массивов и позволяет ссылаться на элементы относительно текущей позиции:

Joi.array().items(
  Joi.object({
    min: Joi.number(),
    max: Joi.number().min(Joi.ref('min'))
  })
);

Каждый объект массива проверяется независимо, но ссылки остаются локальными для элемента.

Особенности разрешения ссылок

Механизм работы Joi.ref включает несколько этапов:

  • построение схемы без вычислений
  • выполнение валидации
  • разрешение ссылок в момент проверки
  • подстановка значений в правила

Это означает, что значение ссылки всегда актуально на момент валидации, а не на момент объявления схемы.

Типичные ошибки при использовании ссылок

Некорректное использование ref часто связано с:

  • неверным путём к полю (user.passwordd)
  • обращением к несуществующим структурам
  • попыткой использовать ref вне контекста объекта
  • конфликтами типов при сравнении

Пример ошибки:

Joi.string().valid(Joi.ref('age'))

если age является числом, а поле строкой, сравнение может быть некорректным.

Ссылки в сложных схемах

В больших схемах ref позволяет создавать взаимосвязанные структуры:

const schema = Joi.object({
  minAge: Joi.number().required(),
  maxAge: Joi.number().min(Joi.ref('minAge')),
  user: Joi.object({
    age: Joi.number().min(Joi.ref('..minAge')).max(Joi.ref('..maxAge'))
  })
});

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

Поведение при преобразовании данных

Если схема включает преобразования (.default(), .cast(), .alter()), ссылки разрешаются после применения этих операций, что важно учитывать при построении сложной логики валидации.

Joi.number().default(10)

Если на это поле ссылаются другие поля, они будут использовать уже преобразованное значение.

Роль ref в архитектуре схем

Механизм ссылок является основой для построения зависимых моделей данных:

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

Без ref многие сценарии межполевой логики потребовали бы ручной проверки вне схемы, что снижает декларативность валидации.