Assign и extend

В библиотеке Superstruct ключевая идея построения схем валидации основана на композиции: более сложные структуры собираются из простых, а не описываются монолитно. Это снижает дублирование, упрощает сопровождение и делает схемы более предсказуемыми.

Композиция особенно важна в проектах, где данные имеют несколько пересекающихся слоёв: пользовательские сущности, системные метаданные, конфигурации и расширенные профили. Для этих задач используются механизмы assign и extend.


assign: объединение структур в единый контракт

assign используется для объединения нескольких структур в одну, где итоговая схема требует выполнения всех правил одновременно.

Базовая идея

assign формирует новую структуру, которая:

  • объединяет поля всех входных структур;
  • проверяет каждое поле согласно его исходному описанию;
  • требует, чтобы объект удовлетворял всем условиям сразу.

Пример использования

import { object, string, number, assign } from 'superstruct';

const UserBase = object({
  id: number(),
  name: string(),
});

const UserMeta = object({
  role: string(),
});

const User = assign(UserBase, UserMeta);

В этом случае итоговая структура User требует наличие:

  • id (число)
  • name (строка)
  • role (строка)

Поведение при конфликте полей

Если в объединяемых структурах встречаются одинаковые поля, поведение зависит от типа структур:

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

assign как инструмент строгой композиции

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

Типичные сценарии:

  • объединение базовой модели и дополнительных обязательных свойств;
  • построение DTO-структур для API;
  • формирование итоговых объектов из нескольких источников данных.

extend: расширение базовой структуры

extend используется для создания новой структуры на основе существующей с добавлением или переопределением полей.

В отличие от assign, он ориентирован на развитие базовой модели, а не на жесткое объединение.

Базовая концепция

extend:

  • наследует все поля исходной структуры;
  • добавляет новые поля;
  • позволяет переопределять правила существующих полей.

Пример расширения

import { object, string, number, extend } from 'superstruct';

const BaseUser = object({
  id: number(),
  name: string(),
});

const ExtendedUser = extend(BaseUser, {
  email: string(),
});

Результирующая структура требует:

  • id
  • name
  • email

Переопределение полей через extend

Одной из ключевых возможностей является изменение поведения уже существующих полей.

const Base = object({
  id: number(),
  name: string(),
});

const Modified = extend(Base, {
  name: string(),
});

Хотя в примере тип совпадает, механизм позволяет заменять более сложные правила:

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

extend как механизм эволюции моделей

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

Типичные случаи:

  • расширение пользовательского профиля;
  • добавление полей конфигурации;
  • адаптация структуры под разные версии API.

Различия assign и extend

Семантический уровень

  • assign — строгая композиция нескольких независимых структур;
  • extend — развитие одной базовой структуры.

Поведение

  • assign требует полного соответствия всем входным схемам;
  • extend наследует основу и добавляет изменения поверх неё.

Пример различия

// assign: объединение равноправных структур
const A = assign(User, Meta);

// extend: развитие одной базовой структуры
const B = extend(User, {
  meta: Meta,
});

Композиция вложенных структур

Обе операции часто используются совместно с объектами, содержащими вложенные структуры.

const Address = object({
  city: string(),
  zip: string(),
});

const User = object({
  id: number(),
  name: string(),
  address: Address,
});

Расширение:

const ExtendedUser = extend(User, {
  role: string(),
});

Объединение:

const FullUser = assign(User, object({
  role: string(),
}));

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

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

  • базовые модели домена;
  • транспортные DTO;
  • схемы API запросов;
  • формы валидации пользовательского ввода.

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


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

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

const Timestamped = object({
  createdAt: string(),
  updatedAt: string(),
});

const User = extend(
  object({
    id: number(),
    name: string(),
  }),
  Timestamped
);

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

  • уменьшать дублирование;
  • централизовать правила валидации;
  • поддерживать согласованность данных.

Взаимодействие с другими валидаторами Superstruct

В экосистеме Superstruct assign и extend обычно используются вместе с:

  • object для описания структур;
  • string, number, boolean для базовых типов;
  • пользовательскими валидаторами для доменной логики.

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


Поведение при валидации составных структур

При использовании assign и extend валидация происходит рекурсивно:

  • сначала проверяются базовые структуры;
  • затем применяются добавленные поля;
  • затем проверяются вложенные объекты.

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