В библиотеке Superstruct ключевая идея построения схем валидации основана на композиции простых примитивов и расширении их через пользовательские функции-валидаторы. Такой подход позволяет создавать строгие, переиспользуемые и легко комбинируемые проверки данных без усложнения основной логики.
Функция-валидатор — это пользовательская функция, которая получает значение и возвращает результат проверки его корректности относительно заданного условия. В контексте Superstruct такая функция интегрируется в систему структур и становится полноценным элементом схемы валидации.
Минимальная логика валидатора сводится к следующему:
true (валидно) или false
(невалидно)Однако в реальных сценариях функция может также формировать расширенные ошибки и участвовать в композиции структур.
Основной механизм создания функции-валидатора в Superstruct — это
define. Он позволяет описывать собственные типы на основе
произвольной логики.
import { define } from 'superstruct'
const PositiveNumber = define('PositiveNumber', (value) => {
return typeof value === 'number' && value > 0
})
В данном примере создаётся новая структура
PositiveNumber, которая пропускает только положительные
числа.
Особенности define:
string,
number, array)Помимо define, в Superstruct существует
refine, который добавляет дополнительное ограничение к уже
существующей структуре.
Используется для создания нового типа с нуля.
const EvenNumber = define('EvenNumber', (value) => {
return typeof value === 'number' && value % 2 === 0
})
Используется для расширения существующего типа.
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 функции-валидаторы в 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
})
В сложных системах функции-валидаторы часто применяются как часть многоуровневой проверки:
string, number)refine)define)object,
array)Такой подход позволяет формировать строгие модели данных без потери гибкости.