Failover

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

Основной уровень отказоустойчивости в Joi обеспечивается методом default(), который подставляет значение при отсутствии входных данных или при их undefined.

import Joi from 'joi';

const schema = Joi.object({
  role: Joi.string().default('guest'),
  retries: Joi.number().integer().min(0).default(3),
  enabled: Joi.boolean().default(true)
});

const result = schema.validate({});
// role: 'guest', retries: 3, enabled: true

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

Важно учитывать, что default() не срабатывает при невалидных значениях — только при отсутствии или undefined, что определяет его как частичный механизм отказоустойчивости.

Альтернативные схемы через alternatives()

Полноценный failover-механизм реализуется через Joi.alternatives(). Он позволяет определить несколько схем, которые проверяются последовательно до первого успешного совпадения.

const schema = Joi.alternatives().try(
  Joi.string().email(),
  Joi.string().uri(),
  Joi.string().pattern(/^[a-zA-Z0-9_-]+$/)
);

schema.validate('example@example.com');
schema.validate('https://site.com');
schema.validate('user_123');

Поведение try():

  • проверка идёт по порядку
  • возвращается первое успешно валидированное значение
  • при полном несоответствии возвращается ошибка

Это основной механизм структурного failover, когда вход может иметь несколько допустимых форматов.

Failover через try с нормализацией данных

Failover часто дополняется преобразованием входа перед проверкой:

const schema = Joi.alternatives().try(
  Joi.number(),
  Joi.string().pattern(/^\d+$/).custom((value) => parseInt(value, 10))
);

В этом случае строковое значение "123" автоматически приводится к числу, если первая схема не проходит.

Подобный подход используется для:

  • API, принимающих гибкие типы
  • миграции старых форматов данных
  • интеграции с внешними источниками

Условный failover через when()

Метод when() позволяет переключать схему в зависимости от состояния других полей, реализуя контекстный failover.

const schema = Joi.object({
  type: Joi.string().valid('email', 'phone'),
  value: Joi.when('type', {
    is: 'email',
    then: Joi.string().email(),
    otherwise: Joi.string().pattern(/^\+?[0-9]{10,15}$/)
  })
});

Здесь failover происходит не при ошибке, а при изменении контекста: схема адаптируется к типу данных.

Многоуровневый failover с вложенными alternatives

Сложные системы используют вложенные альтернативы для каскадного перехода:

const schema = Joi.alternatives().try(
  Joi.object({
    source: Joi.string().valid('internal'),
    id: Joi.number().integer()
  }),
  Joi.object({
    source: Joi.string().valid('external'),
    uuid: Joi.string().guid()
  }),
  Joi.object({
    source: Joi.string().valid('legacy'),
    legacyId: Joi.string()
  })
);

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

Failover с использованием custom()

Когда стандартных средств недостаточно, применяется custom() для ручной логики переключения.

const schema = Joi.any().custom((value, helpers) => {
  if (typeof value === 'string' && value.startsWith('{')) {
    try {
      return JSON.parse(value);
    } catch (e) {
      return helpers.error('any.invalid');
    }
  }
  return value;
});

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

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

Failover валидации и преобразования

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

const schema = Joi.string()
  .trim()
  .lowercase()
  .empty('')
  .default('unknown');

Здесь реализуется цепочка:

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

Подобная комбинация снижает вероятность ошибок на уровне данных.

Приоритет схем в alternatives

Порядок схем в try() определяет стратегию failover:

const schema = Joi.alternatives().try(
  Joi.number().strict(),
  Joi.number().unsafe(),
  Joi.string().pattern(/^\d+$/)
);
  • строгая проверка выполняется первой
  • менее строгая — второй
  • строковый fallback — последним

Это позволяет контролировать деградацию качества данных.

Failover при работе с объектами

Валидация объектов часто требует комбинированного подхода:

const schema = Joi.object({
  config: Joi.alternatives().try(
    Joi.object({
      mode: Joi.string().valid('safe'),
      timeout: Joi.number().default(1000)
    }),
    Joi.object({
      mode: Joi.string().valid('fast'),
      timeout: Joi.number().default(100)
    })
  )
});

Каждый режим содержит собственные параметры, а failover переключает конфигурацию целиком.

Обработка ошибок как часть стратегии failover

Joi не выполняет автоматическое подавление ошибок, но их можно преобразовывать в fallback-значения через validate:

const { error, value } = schema.validate(input);

const safeValue = error
  ? { mode: 'default', value: null }
  : value;

Это внешний слой failover-логики, применяемый при интеграции с бизнес-логикой.

Сочетание failover и строгой типизации

Failover в Joi не отменяет валидацию, а дополняет её:

const schema = Joi.alternatives().try(
  Joi.object({
    version: Joi.number().valid(2),
    data: Joi.object()
  }),
  Joi.object({
    version: Joi.number().valid(1),
    payload: Joi.string()
  })
);

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

Стратегии проектирования failover-схем

На практике выделяются несколько подходов:

  • линейный failover через try()
  • контекстный failover через when()
  • трансформационный failover через custom()
  • дефолтный failover через default()

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