Pattern для строк

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


Строковые структуры в Superstruct базируются на базовом типе string(), который проверяет, что значение действительно является строкой. Однако в реальных приложениях почти всегда требуется более строгая проверка: формат email, идентификатор, номер телефона, код продукта или произвольный шаблон.

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

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


Функция pattern

Основной инструмент для работы с шаблонами строк — функция pattern.

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

Сигнатура выглядит следующим образом:

pattern(structure, regex)

Где:

  • structure — базовая структура, чаще всего string()
  • regex — регулярное выражение, которому должна соответствовать строка

Функция возвращает новую структуру, которая проходит проверку только в случае соответствия строки указанному шаблону.


Механика проверки

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

  1. Проверяется тип значения (строка)
  2. Выполняется сопоставление с регулярным выражением
  3. При несоответствии возвращается ошибка валидации

Важно, что регулярное выражение применяется ко всей строке целиком, поэтому часто используются якоря ^ и $, чтобы исключить частичные совпадения.


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

Проверка числовой строки фиксированной длины

import { string, pattern } from 'superstruct'

const ZipCode = pattern(string(), /^[0-9]{6}$/)

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


Ограничение на латинские буквы

const Username = pattern(string(), /^[a-zA-Z]+$/)

Такая структура разрешает только латинские буквы без цифр и символов.


Комбинация букв и цифр

const ProductCode = pattern(string(), /^[A-Z]{3}[0-9]{4}$/)

Пример формата: ABC1234. Первая часть — три заглавные буквы, вторая — четыре цифры.


Работа с более сложными шаблонами

Регулярные выражения позволяют описывать значительно более сложные правила.

Email-формат (упрощённый вариант)

const Email = pattern(
  string(),
  /^[^\s@]+@[^\s@]+\.[^\s@]+$/
)

Хотя реальные email-адреса могут быть гораздо сложнее, такой шаблон используется для базовой проверки структуры.


URL-подобная строка

const Slug = pattern(
  string(),
  /^[a-z0-9]+(?:-[a-z0-9]+)*$/
)

Этот шаблон часто применяется для SEO-дружественных идентификаторов: my-product-name.


Сочетание с дополнительными ограничениями строки

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

Пример комбинирования:

import { string, size, pattern } from 'superstruct'

const Username = size(
  pattern(string(), /^[a-z0-9_]+$/),
  3,
  16
)

Здесь выполняется сразу несколько условий:

  • только строчные латинские буквы, цифры и подчёркивание
  • длина от 3 до 16 символов

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


Ошибки валидации и их особенности

При несоответствии шаблону Superstruct возвращает структурированную ошибку. Она содержит:

  • путь к значению (path)
  • ожидаемое правило
  • фактическое значение

Пример логики ошибки:

  • ожидался шаблон /^[0-9]{6}$/
  • получено значение "12A45"

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


Поведение регулярных выражений

Регулярные выражения в pattern работают в JavaScript-движке, поэтому важно учитывать особенности:

1. Флаг глобальности

Флаг g не должен использоваться, поскольку он может нарушить корректность повторных проверок.

2. Якоря строки

Без ^ и $ проверка может стать частичной:

pattern(string(), /[0-9]+/)

Такое выражение пропустит строку "abc123" — совпадение будет найдено внутри строки. Поэтому корректнее:

pattern(string(), /^[0-9]+$/)

Применение в API-валидации

Структуры с pattern часто используются для входных данных:

  • идентификаторы пользователей
  • коды подтверждения
  • артикулы товаров
  • маршруты URL
  • региональные коды

Пример структуры запроса:

const CreateOrderRequest = object({
  userId: pattern(string(), /^[0-9]{8}$/),
  coupon: optional(pattern(string(), /^[A-Z0-9]{6}$/))
})

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


Сложные композиции шаблонов

В некоторых случаях один шаблон недостаточен. Тогда используется комбинация нескольких структур.

Пример с альтернативными форматами

import { union, string, pattern } from 'superstruct'

const PhoneNumber = union([
  pattern(string(), /^\+7\d{10}$/),
  pattern(string(), /^8\d{10}$/)
])

Здесь допускаются два формата номера телефона:

  • международный
  • локальный

Использование динамических регулярных выражений

Регулярные выражения могут быть созданы динамически:

function startsWith(prefix) {
  return pattern(string(), new RegExp(`^${prefix}.*$`))
}

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


Ограничения подхода

Несмотря на гибкость, использование pattern имеет особенности:

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

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


Практика проектирования строковых структур

При проектировании структур важно соблюдать баланс между строгостью и гибкостью:

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

Пример разделения ответственности:

const RawCode = pattern(string(), /^[A-Z0-9]+$/)
const Code = size(RawCode, 6, 12)

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


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

Регулярные выражения в Superstruct выполняются синхронно. При большом количестве проверок стоит учитывать:

  • сложность выражения влияет на время валидации
  • вложенные структуры увеличивают стоимость проверки
  • повторяющиеся проверки лучше кэшировать на уровне схем

Особенно это важно в системах с массовой обработкой входящих данных.