Валидация данных в Joi строится вокруг декларативного описания
структуры. Каждая схема представляет собой независимое описание
ограничений, которое можно комбинировать с другими схемами для получения
более сложной логики проверки. Одним из ключевых механизмов композиции
выступает метод concat, позволяющий объединять схемы одного
типа без потери их индивидуальных правил.
Композиция в контексте Joi особенно важна при построении масштабируемых систем валидации, где одни и те же базовые структуры данных расширяются в разных частях приложения.
Метод concat используется для объединения двух схем
одного типа в новую схему, содержащую правила обеих исходных.
Сигнатура:
schema.concat(otherSchema)
Важно учитывать, что объединение возможно только для совместимых типов схем. Например, объект может быть объединён с объектом, строка со строкой, число с числом.
Пример объединения объектных схем:
const Joi = require('joi');
const baseSchema = Joi.object({
id: Joi.number().integer().required(),
createdAt: Joi.date().required()
});
const extendedSchema = Joi.object({
name: Joi.string().min(3).max(30).required(),
email: Joi.string().email().required()
});
const finalSchema = baseSchema.concat(extendedSchema);
В результате finalSchema будет проверять объект,
содержащий все поля из обеих схем.
При использовании concat Joi выполняет объединение на
уровне описания правил каждого поля.
Для объектов:
Пример пересечения правил:
const schemaA = Joi.object({
age: Joi.number().min(0)
});
const schemaB = Joi.object({
age: Joi.number().max(100)
});
const schemaC = schemaA.concat(schemaB);
Результирующее поле age будет иметь одновременно
ограничения:
При пересечении схем с одинаковыми ключами происходит не замена, а объединение ограничений. Однако некоторые комбинации могут приводить к конфликтам:
const schemaA = Joi.object({
status: Joi.string().valid('active')
});
const schemaB = Joi.object({
status: Joi.string().valid('inactive')
});
const schemaC = schemaA.concat(schemaB);
В данном случае значение status должно одновременно
удовлетворять двум несовместимым множествам допустимых значений. Такая
схема становится невыполнимой.
Метод concat применим не только к объектам, но и к
другим типам схем.
const base = Joi.string().min(5);
const extended = Joi.string().max(10);
const result = base.concat(extended);
Итоговая схема задаёт диапазон длины строки от 5 до 10 символов.
const base = Joi.number().integer();
const extended = Joi.number().positive();
const result = base.concat(extended);
Результат объединяет оба ограничения: число должно быть целым и положительным.
const base = Joi.array().items(Joi.number());
const extended = Joi.array().min(2);
const result = base.concat(extended);
Полученная схема требует массив чисел длиной не менее двух элементов.
На практике concat часто используется для построения
схем по принципу слоёв: базовая структура + расширения под конкретные
сценарии.
const baseUser = Joi.object({
id: Joi.number().required(),
role: Joi.string().required()
});
const contactInfo = Joi.object({
email: Joi.string().email(),
phone: Joi.string()
});
const profile = Joi.object({
nickname: Joi.string()
});
const fullUserSchema = baseUser
.concat(contactInfo)
.concat(profile);
Такой подход позволяет разделять ответственность между частями схемы и переиспользовать их в разных контекстах.
При объединении схем с вложенными объектами происходит глубокое
слияние только на уровне ключей, но не автоматическая рекурсивная
композиция вложенных object схем.
const schemaA = Joi.object({
user: Joi.object({
id: Joi.number().required()
})
});
const schemaB = Joi.object({
user: Joi.object({
name: Joi.string().required()
})
});
const result = schemaA.concat(schemaB);
В результате поле user будет содержать оба правила, но
итоговое поведение зависит от того, как Joi объединит внутренние схемы
объектов.
concat не поддерживает прямое переопределение правил в
классическом смысле. Вместо этого:
Это означает, что попытка “заменить” правило через
concat не приведёт к удалению старого ограничения.
При работе с alternatives композиция схем требует
особого внимания:
const base = Joi.alternatives().try(
Joi.string(),
Joi.number()
);
const extended = Joi.alternatives().try(
Joi.string().email()
);
const result = base.concat(extended);
В таких случаях итоговая схема расширяет набор допустимых альтернатив, не исключая исходные варианты.
Метод concat часто применяется для построения схем в
модульной архитектуре:
Пример структуры:
const idSchema = Joi.object({
id: Joi.number().required()
});
const timestampSchema = Joi.object({
createdAt: Joi.date(),
updatedAt: Joi.date()
});
const entitySchema = idSchema.concat(timestampSchema);
Joi.string().concat(Joi.number())
Такая операция невозможна из-за различия типов схем.
Конфликт валидных значений Пересечение
valid() с несовместимыми значениями приводит к невыполнимым
схемам.
Ожидание замены правил concat не
удаляет старые ограничения, а только добавляет новые.
Попытка глубокого рекурсивного слияния Вложенные объекты не всегда объединяются так, как предполагается логикой приложения.
Использование concat эффективно в сочетании с разбиением
схем на логические блоки:
Такой подход формирует предсказуемую и расширяемую структуру валидации без дублирования описаний.