Функции-валидаторы

В библиотеке Superstruct ключевая идея построения схем валидации основана на композиции простых примитивов и расширении их через пользовательские функции-валидаторы. Такой подход позволяет создавать строгие, переиспользуемые и легко комбинируемые проверки данных без усложнения основной логики.


Базовая концепция функции-валидатора

Функция-валидатор — это пользовательская функция, которая получает значение и возвращает результат проверки его корректности относительно заданного условия. В контексте Superstruct такая функция интегрируется в систему структур и становится полноценным элементом схемы валидации.

Минимальная логика валидатора сводится к следующему:

  • вход: произвольное значение
  • выход: true (валидно) или false (невалидно)

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


Создание пользовательских структур через define

Основной механизм создания функции-валидатора в Superstruct — это define. Он позволяет описывать собственные типы на основе произвольной логики.

import { define } from 'superstruct'

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

В данном примере создаётся новая структура PositiveNumber, которая пропускает только положительные числа.

Особенности define:

  • создаёт именованную структуру
  • позволяет интегрироваться в систему ошибок Superstruct
  • может использоваться как любой встроенный тип (string, number, array)

Отличие define от refine

Помимо define, в Superstruct существует refine, который добавляет дополнительное ограничение к уже существующей структуре.

define

Используется для создания нового типа с нуля.

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

refine

Используется для расширения существующего типа.

import { number, refine } from 'superstruct'

const EvenNumber = refine(number(), 'EvenNumber', (value) => {
  return value % 2 === 0
})

Ключевое различие:

  • define — самостоятельная структура
  • refine — надстройка над существующей структурой

Использование функции-валидатора в объектах

Функции-валидаторы легко интегрируются в составные структуры, включая объекты и массивы.

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

const Username = define('Username', (value) => {
  return typeof value === 'string' && value.length >= 3
})

const User = object({
  name: Username,
  role: string()
})

В данном случае Username становится повторно используемым строительным блоком.


Композиция валидаторов

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

import { define } from 'superstruct'

const HasAtSymbol = define('HasAtSymbol', (value) => {
  return typeof value === 'string' && value.includes('@')
})

const LongEnough = define('LongEnough', (value) => {
  return typeof value === 'string' && value.length > 5
})

const EmailLike = define('EmailLike', (value) => {
  return HasAtSymbol(value) && LongEnough(value)
})

Композиция может быть как явной (через объединение логики), так и структурной (через вложенные структуры).


Работа с ошибками в функциях-валидаторах

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

При использовании define и refine автоматически создаётся контекст ошибки, включающий:

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

Пример поведения:

const Positive = define('Positive', (value) => value > 0)

Если передано -5, ошибка будет содержать информацию о несоответствии условию Positive.


Типизация и функции-валидаторы (TypeScript)

В связке с TypeScript функции-валидаторы в Superstruct позволяют автоматически выводить типы.

import { define, Infer } from 'superstruct'

const Positive = define<number>('Positive', (value): value is number => {
  return typeof value === 'number' && value > 0
})

type PositiveType = Infer<typeof Positive>

В данном случае PositiveType будет выведен как number, но логически ограниченный положительным значением.


Ограничения и особенности поведения

При использовании функций-валидаторов важно учитывать ряд особенностей:

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

Интеграция с преобразованием данных

Функции-валидаторы могут использоваться совместно с преобразованием значений. Хотя основная задача валидатора — проверка, в связке с другими механизмами Superstruct возможно построение цепочек:

  • преобразование входных данных
  • проверка через валидатор
  • нормализация результата
import { string, define, coerce } from 'superstruct'

const TrimmedNonEmpty = define('TrimmedNonEmpty', (value) => {
  return typeof value === 'string' && value.trim().length > 0
})

Переиспользование валидаторов

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

const Email = define('Email', (value) => {
  return typeof value === 'string' && value.includes('@')
})

const User = object({
  email: Email
})

const Contact = object({
  primaryEmail: Email,
  backupEmail: Email
})

Составные стратегии валидации

В сложных системах функции-валидаторы часто применяются как часть многоуровневой проверки:

  1. базовые типы (string, number)
  2. уточняющие ограничения (refine)
  3. доменные валидаторы (define)
  4. композиционные структуры (object, array)

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