Валидация входных данных в Superstruct часто строится вокруг двух противоположных стратегий контроля значений: разрешающего (whitelist) и запрещающего (blacklist) подходов. Эти модели определяют, как именно формируется логика допустимости данных и насколько строго система реагирует на неожиданные значения.
Разрешающий подход опирается на принцип «допустимо только явно перечисленное». Любое значение считается недопустимым по умолчанию, если оно не входит в заранее определённый набор.
В контексте Superstruct этот стиль чаще всего реализуется через структуру перечислений:
import { enums } from 'superstruct'
const Status = enums(['idle', 'loading', 'success', 'error'])
Такая структура фиксирует строгий набор допустимых значений. Любая попытка передать значение вне списка приводит к ошибке валидации.
Дополнительная гибкость достигается комбинированием с другими структурами:
import { object, string, enums } from 'superstruct'
const Task = object({
id: string(),
status: enums(['todo', 'in_progress', 'done'])
})
Здесь whitelist-подход локализован на уровне конкретного поля, что позволяет сохранять строгую типизацию без избыточного усложнения всей схемы.
Ключевая особенность такого подхода заключается в предсказуемости: пространство допустимых значений явно ограничено и легко контролируется на уровне схемы.
Whitelist-логика не всегда выражается через enums. Часто используется комбинация базовых типов и уточнений:
import { string, refine } from 'superstruct'
const ShortCode = refine(string(), 'ShortCode', (value) => {
return value.length === 3
})
Здесь набор значений не перечисляется, но жёстко ограничивается правилами. Несмотря на отличие механики, логика остаётся разрешающей: всё, что не удовлетворяет условию, автоматически исключается.
Blacklist-стратегия строится иначе: допустимыми считаются все
значения, кроме явно запрещённых. В Superstruct это реализуется через
refine или кастомные проверки.
import { string, refine } from 'superstruct'
const ForbiddenUsernames = ['admin', 'root', 'system']
const Username = refine(string(), 'Username', (value) => {
return !ForbiddenUsernames.includes(value)
})
В этом случае базовый тип остаётся широким, а ограничения накладываются точечно.
Такой подход часто применяется в ситуациях, где невозможно заранее перечислить все допустимые значения, но можно исключить заведомо нежелательные.
Разрешающая модель формирует закрытую систему допустимых значений. Любое расширение требует изменения схемы. Это делает поведение предсказуемым, но менее гибким.
Запрещающая модель, напротив, формирует открытую систему, где изменения происходят через добавление новых исключений. Это повышает гибкость, но снижает контроль над полным пространством значений.
В реальных схемах Superstruct оба подхода часто сосуществуют. Например, базовая структура может использовать whitelist для критичных полей и blacklist для вторичных ограничений.
import { object, string, enums, refine } from 'superstruct'
const Role = enums(['user', 'moderator', 'admin'])
const User = object({
username: refine(string(), 'username', (v) => v !== 'guest'),
role: Role
})
Здесь роль строго ограничена перечислением, а имя пользователя проходит через запрещающее правило.
Выбор между подходами влияет не только на валидацию, но и на проектирование модели данных.
Whitelist-структуры усиливают контракт между слоями системы: данные становятся строго регламентированными и легко сериализуемыми.
Blacklist-структуры смещают акцент на эволюцию системы: новые ограничения добавляются без необходимости пересматривать базовый набор допустимых значений.
При чрезмерном использовании запрещающего подхода возникает эффект «скрытой экспансии»: фактический набор допустимых значений становится неочевидным без анализа всех правил валидации.
В сложных схемах это приводит к тому, что допустимость значения зависит от совокупности условий, распределённых по коду.
Жёсткие перечисления создают проблему расширяемости. Любое новое значение требует модификации структуры, что может приводить к каскадным изменениям.
Особенно это проявляется в системах с динамическими источниками данных, где полный список значений заранее неизвестен.
В Superstruct выбор стратегии обычно определяется характером данных:
enumsrefineТакое разделение позволяет строить схемы с разной степенью строгости внутри одной системы валидации.
Whitelist-подход упрощает интерпретацию схемы: допустимые значения видны сразу. Это снижает когнитивную нагрузку при сопровождении кода.
Blacklist-подход требует анализа логики функций проверки, что усложняет понимание, но даёт большую свободу выражения сложных условий.
В устойчивых схемах Superstruct whitelist обычно фиксирует фундаментальные ограничения, тогда как blacklist используется для локальных исключений.
Такое разделение позволяет сохранить баланс между жёсткостью контракта и гибкостью бизнес-логики, не перегружая структуру валидации избыточной сложностью.