Многоуровневые конфигурации

Библиотека Ajv строится вокруг концепции экземпляра валидатора. Именно экземпляр определяет базовое поведение всей системы валидации и задаёт глобальные правила обработки схем.

Создание экземпляра выглядит как отправная точка конфигурации:

import Ajv from "ajv";

const ajv = new Ajv({
  strict: true,
  allErrors: true,
  removeAdditional: false,
  useDefaults: true
});

Каждый параметр здесь формирует базовый слой поведения:

strict — включает строгую проверку схем JSON Schema и выявляет потенциальные ошибки проектирования. allErrors — заставляет валидатор собирать все ошибки, а не останавливаться на первой. removeAdditional — управляет удалением лишних полей. useDefaults — активирует подстановку значений default из схемы.

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


Параметры компиляции схемы

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

const validate = ajv.compile({
  type: "object",
  properties: {
    name: { type: "string" }
  }
}, {
  removeAdditional: "all"
});

Параметры компиляции позволяют:

  • локально переопределять обработку дополнительных полей
  • изменять стратегию ошибок
  • адаптировать поведение под конкретную схему

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


Переопределение поведения на уровне схемы

JSON Schema поддерживает расширения через ключи, которые Ajv интерпретирует как директивы поведения. Эти директивы создают ещё один слой конфигурации — внутри самой схемы.

Пример:

{
  "type": "object",
  "properties": {
    "age": {
      "type": "integer",
      "minimum": 18,
      "default": 18
    }
  },
  "required": ["age"]
}

Здесь default работает только при включённом useDefaults на уровне экземпляра. Таким образом возникает зависимость уровней:

  • экземпляр Ajv включает механизм
  • схема описывает данные для применения механизма
  • валидатор объединяет оба уровня

Дополнительно используются расширения через кастомные ключи:

ajv.addKeyword({
  keyword: "range",
  validate: function (schema, data) {
    return data >= schema.min && data <= schema.max;
  }
});

Теперь схема может содержать:

{
  "range": { "min": 10, "max": 20 }
}

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


Глобальные и модульные расширения

Ajv поддерживает подключение модулей, которые влияют на поведение всех последующих схем.

Примеры расширений:

  • форматы (ajv-formats)
  • пользовательские ошибки (ajv-errors)
  • оптимизация рекурсии и ссылок
import addFormats from "ajv-formats";
addFormats(ajv);

После подключения:

{
  "type": "string",
  "format": "email"
}

Формат становится частью глобального контекста экземпляра.

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


Изоляция экземпляров и конфигурационные профили

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

const publicAjv = new Ajv({ strict: false });
const internalAjv = new Ajv({ strict: true });

Это создаёт конфигурационные профили:

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

Каждый экземпляр имеет собственный набор:

  • схем
  • кастомных ключевых слов
  • форматов
  • правил ошибок

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


Наследование и переиспользование схем

Ajv поддерживает $ref, что добавляет ещё один уровень конфигурации — ссылочный.

{
  "$ref": "#/definitions/address"
}

или внешние схемы:

ajv.addSchema(schema, "base");
{
  "$ref": "base#/definitions/user"
}

Таким образом формируется иерархия:

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

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


Управление поведением ошибок как отдельный слой

Ошибки в Ajv не просто результат, а конфигурируемая система.

const ajv = new Ajv({
  allErrors: true,
  verbose: true
});

Дополнительные модули позволяют расширять поведение:

  • форматирование сообщений
  • локализация ошибок
  • кастомные обработчики

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


Динамическая конфигурация через $data

Механизм $data добавляет зависимость значений от других полей:

{
  "properties": {
    "min": { "type": "integer" },
    "value": {
      "type": "integer",
      "minimum": { "$data": "1/min" }
    }
  }
}

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

Для работы требуется включение:

const ajv = new Ajv({ $data: true });

Приоритеты уровней конфигурации

При выполнении валидации действует строгая иерархия:

  1. Экземпляр Ajv (глобальные настройки)
  2. Подключённые модули
  3. Зарегистрированные схемы
  4. Локальные параметры компиляции
  5. Переопределения внутри схемы
  6. Динамические зависимости $data
  7. Кастомные keywords

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


Практика построения слоёв конфигурации

В реальных системах конфигурация Ajv редко остаётся плоской. Обычно используется многоуровневая модель:

  • базовый экземпляр с общими правилами
  • специализированные экземпляры под домены
  • схемы с локальными ограничениями
  • расширения через keywords и модули
  • динамические зависимости через $data

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

  • избегать дублирования схем
  • контролировать строгие и мягкие режимы в одной системе
  • изолировать бизнес-логики валидации
  • масштабировать систему без изменения ядра валидатора

Многоуровневая конфигурация в Ajv фактически превращает JSON Schema из статического описания в управляемую систему правил, где каждый уровень отвечает за свою область ответственности и может эволюционировать независимо от остальных.