В Superstruct композиция валидаторов является основным механизмом построения сложных схем валидации из простых примитивов. Вместо того чтобы описывать каждое правило проверки вручную, библиотека позволяет собирать их как строительные блоки, комбинируя структуры, уточнения и трансформации в единые выразительные типы.
Каждый валидатор в Superstruct — это функция, которая принимает значение и либо возвращает его в нормализованном виде, либо выбрасывает ошибку. Композиция строится вокруг идеи последовательного применения таких функций:
Такой подход позволяет выстраивать декларативные схемы, где каждый слой отвечает за свою часть логики.
objectОдним из самых распространённых способов композиции является использование объектных структур. Они позволяют комбинировать независимые валидаторы по ключам.
import { object, string, number } from 'superstruct'
const User = object({
name: string(),
age: number(),
})
Каждое поле внутри объекта само по себе является валидатором. Это означает, что композиция происходит на уровне структуры данных: объект становится контейнером для других структур.
Важная особенность заключается в том, что каждый валидатор внутри объекта остаётся независимым. Ошибка в одном поле не влияет на остальные, что упрощает диагностику.
partial для частичной композицииКогда необходимо сделать часть структуры необязательной, применяется
partial. Это пример композиции, при которой исходная
структура модифицируется без её переписывания.
import { partial } from 'superstruct'
const OptionalUser = partial(User)
Здесь происходит преобразование уже существующего валидатора в новый, где все поля становятся опциональными. Такой подход позволяет переиспользовать схемы без дублирования логики.
extendКомпозиция также может происходить через расширение базовых структур. Это полезно, когда требуется создать новый тип на основе существующего.
import { extend, string } from 'superstruct'
const BaseUser = object({
name: string(),
})
const AdminUser = extend(BaseUser, {
role: string(),
})
Механика расширения позволяет наращивать структуру, сохраняя совместимость с исходным валидатором. Это особенно важно в доменных моделях, где базовые сущности часто переиспользуются.
intersection)Одним из наиболее мощных инструментов композиции является пересечение. Оно объединяет несколько валидаторов в один, требующий соблюдения всех условий одновременно.
import { intersection, object, string, number } from 'superstruct'
const HasName = object({
name: string(),
})
const HasAge = object({
age: number(),
})
const Person = intersection([HasName, HasAge])
Здесь итоговая структура должна удовлетворять обоим валидаторам. Такой подход используется для строгой типизации сущностей, где несколько аспектов данных описаны отдельно.
union)Если требуется разрешить несколько возможных форм данных, используется объединение.
import { union, string, number } from 'superstruct'
const StringOrNumber = union([string(), number()])
Композиция через union строит валидатор, который пробует
каждый вариант по очереди до первого успешного совпадения.
Особенность такого подхода заключается в порядке проверки: более специфичные структуры рекомендуется размещать раньше, чтобы избежать неоднозначности.
lazy) и рекурсияКомпозиция становится особенно важной при работе с рекурсивными
данными. Для этого используется lazy, который откладывает
вычисление структуры.
import { lazy, object, array, string } from 'superstruct'
const Node = lazy(() =>
object({
value: string(),
children: array(Node),
})
)
Без ленивой композиции невозможно корректно описать самоссылочные
структуры. Здесь Node определяется через саму себя, но
вычисление происходит только при валидации.
refineКомпозиция валидаторов часто требует добавления бизнес-логики. Для
этого используется refine, который накладывает
дополнительные ограничения поверх базовой структуры.
import { refine, string } from 'superstruct'
const EvenLengthString = refine(string(), 'EvenLengthString', (value) => {
return value.length % 2 === 0
})
refine не заменяет базовый валидатор, а оборачивает его,
создавая новый слой проверки. Это ключевой механизм расширения логики
без изменения исходных структур.
coerceКомпозиция может включать не только проверку, но и приведение данных.
coerce позволяет преобразовывать вход перед валидацией.
import { coerce, number, string } from 'superstruct'
const NumberFromString = coerce(number(), string(), (value) => {
return Number(value)
})
Здесь происходит цепочка: сначала значение приводится к нужному виду, затем применяется основная структура. Такой подход делает схемы более устойчивыми к внешним данным.
Одним из ключевых принципов является повторное использование валидаторов как строительных блоков. Это позволяет избегать дублирования логики и создавать иерархию типов.
const Name = string()
const User = object({
name: Name,
})
const Comment = object({
author: Name,
})
В этом случае Name становится базовым валидатором,
который используется в разных контекстах без изменений.
Композиция в Superstruct не ограничивается плоскими объектами. Валидаторы могут быть вложены на любую глубину:
const Post = object({
title: string(),
author: object({
name: string(),
id: number(),
}),
tags: array(string()),
})
Каждый уровень структуры представляет собой самостоятельную композицию, что позволяет строить сложные доменные модели.
На практике часто требуется сочетать преобразования и ограничения в одном валидаторе. Superstruct позволяет строить такие цепочки через композицию обёрток.
const TrimmedString = coerce(
refine(string(), 'TrimmedString', (v) => v.length > 0),
string(),
(value) => value.trim()
)
Здесь сначала выполняется преобразование, затем проверка, создавая многоуровневую композицию логики.
При использовании Superstruct валидаторы перестают быть изолированными функциями проверки и превращаются в элементы модели данных. Их композиция позволяет:
Каждый валидатор становится частью более крупной системы правил, где важна не только проверка значений, но и способ их объединения в единые схемы.