Принцип работы валидации

Валидация в Superstruct строится вокруг декларативного описания структур данных и последовательного применения проверок к входящим значениям. Библиотека рассматривает данные не как абстрактные объекты, а как набор ограничений, которые должны быть выполнены в момент исполнения кода.

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

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

const User = object({
  name: string(),
  age: number()
})

В этом примере создаётся структура User, которая определяет форму объекта. Важно, что само описание не выполняет проверку — оно лишь задаёт правила, которые будут применены позже.

Отделение описания от исполнения

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

import { validate } from 'superstruct'

const [error, result] = validate({ name: 'Alex', age: 30 }, User)

Такой подход позволяет переиспользовать одну и ту же структуру в разных частях приложения без дублирования логики.

Последовательность этапов валидации

Процесс проверки данных в Superstruct проходит несколько логических этапов.

1. Коэрция (преобразование) На этом этапе значение может быть приведено к ожидаемому виду. Например, строка может быть преобразована в число, если используется соответствующий коэрс-структ.

import { coerce, number, string } from 'superstruct'

const NumberFromString = coerce(number(), string(), (value) => Number(value))

2. Базовая проверка типа После возможного преобразования выполняется проверка соответствия базовому типу: строка, число, массив, объект и т.д.

3. Проверка ограничений На этом этапе применяются дополнительные условия: длина строки, диапазоны значений, регулярные выражения, пользовательские предикаты.

import { size, string } from 'superstruct'

const Username = size(string(), 3, 20)

4. Функциональная валидация (refinement) Финальный этап позволяет наложить произвольную бизнес-логику через функцию проверки.

import { refine, string } from 'superstruct'

const EvenLengthString = refine(string(), 'EvenLengthString', (value) => {
  return value.length % 2 === 0
})

Механизм ошибок и их структура

При провале проверки Superstruct формирует структурированную ошибку, содержащую информацию о пути до проблемного значения и причине несоответствия.

Типичная ошибка включает:

  • путь к полю (path)
  • ожидаемый тип или правило
  • фактическое значение
  • описание причины
import { assert } from 'superstruct'

assert({ name: 123 }, User)

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

Иерархия и композиция структур

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

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

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

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

Объединение и пересечение типов

Для описания альтернативных или комбинированных форм данных используются специальные структуры.

Union (объединение типов)

import { union } from 'superstruct'

const ID = union([string(), number()])

Intersection (пересечение типов)

import { intersection } from 'superstruct'

const A = object({ a: string() })
const B = object({ b: number() })

const AB = intersection([A, B])

Union определяет допустимость одного из вариантов, тогда как intersection требует соблюдения всех условий одновременно.

Пользовательские структуры

Superstruct позволяет создавать полностью кастомные валидаторы, расширяя базовую систему типов.

import { define } from 'superstruct'

const PositiveNumber = define('PositiveNumber', (value) => {
  return typeof value === 'number' && value > 0
})

Такие структуры становятся полноценными элементами системы и могут участвовать в композиции наравне с встроенными типами.

Интеграция с TypeScript и вывод типов

Одной из особенностей Superstruct является автоматический вывод TypeScript-типов из описанных структур. Это позволяет синхронизировать runtime-валидацию и compile-time типизацию.

import { Infer, object, string, number } from 'superstruct'

const User = object({
  name: string(),
  age: number()
})

type UserType = Infer<typeof User>

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

Поведение при провале валидации

В зависимости от используемого метода проверки, библиотека может вести себя по-разному:

  • assert — выбрасывает исключение
  • validate — возвращает кортеж [error, result]
  • is — возвращает boolean
import { is } from 'superstruct'

is('hello', string()) // true
is(123, string()) // false

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

Детализация пути ошибки

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

{
  user: {
    profile: {
      age: 'not-a-number'
    }
  }
}

Ошибка будет содержать путь вида user.profile.age, что существенно упрощает диагностику.

Ленивая оценка и композиционная природа

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

Каждая структура является функцией, возвращающей новое описание, что делает систему функционально-композиционной и предсказуемой.

Итоговая модель работы

Вся система валидации строится на трёх базовых принципах:

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

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