Регулярные выражения

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

Основной инструмент для применения регулярных выражений в Joi — метод pattern. Он позволяет задать правило соответствия строки определённому регулярному выражению.

import Joi from 'joi';

const schema = Joi.string().pattern(/^[a-zA-Z0-9]{6,12}$/);

В данном случае строка должна состоять только из латинских букв и цифр, а её длина ограничена диапазоном от 6 до 12 символов.

Регулярное выражение работает по стандартным правилам JavaScript RegExp, включая якоря ^ и $, которые фиксируют соответствие всей строки целиком, а не её части.

Использование именованных правил

Метод pattern допускает передачу объекта параметров, где можно задать имя правила. Это полезно для более понятного описания ошибок.

const schema = Joi.string().pattern(/^\d{4}-\d{2}-\d{2}$/, { name: 'date' });

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

Валидация телефонных номеров

Регулярные выражения часто применяются для проверки телефонных номеров, особенно в упрощённых форматах.

const phoneSchema = Joi.string().pattern(/^\+?[0-9]{10,15}$/);

Здесь допускается необязательный знак + и от 10 до 15 цифр подряд.

При более сложных требованиях, например разделении кода страны и номера, используется более детализированное выражение:

const phoneSchema = Joi.string().pattern(/^\+\d{1,3}-\d{4,14}$/);

Проверка сложности паролей

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

const passwordSchema = Joi.string().pattern(
  /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[\W_]).{8,}$/
);

Разбор выражения:

  • (?=.*[a-z]) — минимум одна строчная буква
  • (?=.*[A-Z]) — минимум одна заглавная буква
  • (?=.*\d) — минимум одна цифра
  • (?=.*[\W_]) — минимум один специальный символ
  • {8,} — длина не менее 8 символов

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

Альтернативные варианты и ограничения формата

Метод pattern может использоваться совместно с другими строковыми правилами Joi:

const usernameSchema = Joi.string()
  .min(3)
  .max(20)
  .pattern(/^[a-zA-Z0-9_]+$/);

Здесь комбинируются ограничения длины и допустимого набора символов.

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

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

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

const schema = Joi.string()
  .pattern(/[a-z]/)
  .pattern(/[A-Z]/)
  .pattern(/[0-9]/);

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

Для управления сообщениями об ошибках используется метод messages:

const schema = Joi.string()
  .pattern(/[a-z]/)
  .messages({
    'string.pattern.base': 'Должна содержать строчные буквы'
  });

Однако при нескольких шаблонах одинаковый ключ ошибки будет применяться ко всем, поэтому требуется более точная настройка.

Кастомизация сообщений об ошибках

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

const schema = Joi.string()
  .pattern(/^\d+$/, { name: 'digits' })
  .messages({
    'string.pattern.name': 'Значение должно содержать только цифры'
  });

Также можно использовать базовый ключ:

.messages({
  'string.pattern.base': 'Неверный формат строки'
});

Это сообщение применяется ко всем регулярным выражениям без имени.

Инверсия совпадения

В некоторых сценариях требуется запретить определённый шаблон. Для этого используется параметр invert.

const schema = Joi.string().pattern(/admin/, { invert: true });

Такое правило запрещает появление подстроки admin в любом месте строки.

Инверсия особенно полезна при фильтрации нежелательных слов, запрещённых паттернов или подозрительных последовательностей.

Экранирование и составление сложных выражений

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

const schema = Joi.string().pattern(new RegExp('^\\d{3}-\\d{2}-\\d{4}$'));

В строковом представлении каждый обратный слеш требует удвоения, что часто становится источником ошибок при сложных шаблонах.

Использование литералов /.../ снижает вероятность ошибок и повышает читаемость.

Регулярные выражения и Unicode

Joi полностью поддерживает JavaScript RegExp, включая флаги Unicode.

const schema = Joi.string().pattern(/^\p{L}+$/u);

Здесь допускаются только буквы любых языков. Флаг u включает поддержку Unicode-свойств.

Это особенно важно при работе с международными данными, где ограничение [a-zA-Z] оказывается недостаточным.

Проверка email через регулярные выражения

Хотя Joi предоставляет встроенный метод .email(), иногда используются кастомные выражения:

const emailSchema = Joi.string().pattern(
  /^[^\s@]+@[^\s@]+\.[^\s@]+$/
);

Такой вариант охватывает базовую структуру email, но не учитывает все спецификации RFC, поэтому используется только для упрощённой проверки.

Комбинация регулярных выражений с условной логикой

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

const schema = Joi.object({
  type: Joi.string().valid('short', 'long'),
  value: Joi.string().when('type', {
    is: 'short',
    then: Joi.string().pattern(/^\d{3}$/),
    otherwise: Joi.string().pattern(/^\d{10}$/)
  })
});

Здесь формат строки зависит от значения другого поля.

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

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

  • вложенных квантификаторов ((a+)+)
  • чрезмерных альтернатив ((a|b|c|...)+)
  • отсутствия якорей ^ и $

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

Оптимальная практика — использовать простые и строго ограниченные выражения.

Типичные ошибки при использовании pattern

Одной из частых ошибок является отсутствие якорей:

Joi.string().pattern(/\d+/)

Такое выражение пропустит строки вида abc123xyz, поскольку совпадение ищется внутри строки.

Корректный вариант:

Joi.string().pattern(/^\d+$/)

Другой распространённый случай — избыточно сложные выражения, которые проще заменить комбинацией встроенных методов Joi (alphanum, min, max).

Сочетание с другими строковыми методами Joi

Регулярные выражения в Joi не являются единственным инструментом ограничения строк. Они часто комбинируются с другими методами:

Joi.string()
  .alphanum()
  .min(5)
  .max(15)
  .pattern(/^[a-zA-Z]/);

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

Расширенные шаблоны и lookahead конструкции

Joi полностью поддерживает lookahead и lookbehind (в зависимости от версии движка JavaScript):

Joi.string().pattern(/^(?=.*\d)(?=.*[a-zA-Z]).+$/);

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

В сложных схемах это помогает уменьшить количество отдельных правил.

Итеративное построение регулярных правил

При разработке схемы часто применяется поэтапное усложнение регулярного выражения:

  1. Проверка базовой структуры
  2. Добавление ограничений на символы
  3. Добавление контекстных правил
  4. Интеграция с другими Joi-валидациями

Такой подход снижает риск ошибок и упрощает отладку.

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

Метод .describe() в Joi позволяет анализировать структуру схемы, включая регулярные выражения:

const schema = Joi.string().pattern(/\d+/);
console.log(schema.describe());

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