@Matches для регулярных выражений

Декоратор @Matches применяется для валидации строковых значений на соответствие регулярному выражению и является одним из базовых инструментов проверки форматов данных в моделях, описанных через class-validator. Его основная задача — гарантировать, что входное значение строго соответствует заданному шаблону, будь то формат email, телефонного номера, идентификатора, кода или любого другого структурированного текста.

@Matches используется для проверки строки с помощью регулярного выражения JavaScript. В отличие от более высокоуровневых валидаторов, таких как @IsEmail или @IsUUID, данный декоратор предоставляет полный контроль над логикой проверки.

Базовая форма применения:

import { Matches } from "class-validator";

class UserDto {
  @Matches(/^[a-zA-Z0-9]+$/, {
    message: "username может содержать только латинские буквы и цифры",
  })
  username: string;
}

Регулярное выражение передаётся первым аргументом, а объект настроек — вторым.

Синтаксис и структура

Общий формат:

@Matches(pattern: RegExp, validationOptions?: ValidationOptions)

Параметры

  • pattern — экземпляр RegExp, задающий правило проверки
  • validationOptions — опциональный объект конфигурации поведения валидатора

Основные поля validationOptions:

  • message — пользовательское сообщение об ошибке
  • groups — группы валидации
  • each — применяется к каждому элементу массива (при необходимости)
  • context — дополнительный контекст

Принцип работы

При валидации class-validator вызывает метод test() у регулярного выражения:

pattern.test(value)

Если результат false, проверка считается проваленной.

Важно учитывать, что поведение может меняться при использовании флага g (global), так как он влияет на состояние lastIndex.

Особенность с флагом g

@Matches(/^[a-z]+$/g)

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

Типичные сценарии использования

Проверка имени пользователя

class CreateUserDto {
  @Matches(/^[a-z0-9_]{3,16}$/, {
    message: "username должен содержать 3–16 символов: a-z, 0-9, _",
  })
  username: string;
}

Регулярное выражение ограничивает длину и набор допустимых символов.

Проверка телефонного номера

class ContactDto {
  @Matches(/^\+?[0-9]{10,15}$/, {
    message: "телефон должен содержать от 10 до 15 цифр и может начинаться с +",
  })
  phone: string;
}

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

Проверка кода подтверждения

class VerifyDto {
  @Matches(/^[0-9]{6}$/, {
    message: "код должен состоять из 6 цифр",
  })
  code: string;
}

Фиксированная длина и строгий цифровой формат.

Работа с регистрами и флагами

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

@Matches(/^[A-Z]+$/)
uppercaseCode: string;

Для игнорирования регистра применяется флаг i:

@Matches(/^[a-z]+$/i)
name: string;

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

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

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

class TagsDto {
  @Matches(/^[a-z]+$/, { each: true })
  tags: string[];
}

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

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

Валидация может включать составные шаблоны.

Проверка URL

class LinkDto {
  @Matches(
    /^(https?:\/\/)?([\w-]+\.)+[\w-]+(\/[\w\-._~:/?#[\]@!$&'()*+,;=]*)?$/,
    {
      message: "некорректный URL",
    }
  )
  url: string;
}

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

Ошибки и сообщения

Если значение не проходит проверку, class-validator возвращает объект ошибки:

{
  "property": "username",
  "constraints": {
    "matches": "username должен содержать 3–16 символов: a-z, 0-9, _"
  }
}

Поле constraints.matches формируется автоматически либо переопределяется через message.

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

1. Читаемость регулярных выражений

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

const USERNAME_REGEX = /^[a-z0-9_]{3,16}$/;

class UserDto {
  @Matches(USERNAME_REGEX)
  username: string;
}

2. Производительность

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

3. Отсутствие семантики

@Matches проверяет только форму строки, но не её смысл. Например, строка может соответствовать шаблону email-подобного выражения, но не быть реальным email.

Комбинирование с другими декораторами

Часто используется совместно с базовыми проверками типа:

class ProductDto {
  @IsString()
  @Matches(/^[A-Z0-9-]+$/)
  sku: string;
}

Такое сочетание обеспечивает как типовую проверку, так и структурное ограничение.

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

В слоях DTO регулярные выражения позволяют фиксировать контракт данных:

  • идентификаторы сущностей
  • коды стран
  • внутренние артикулы
  • алиасы и slug-поля
class CategoryDto {
  @Matches(/^[a-z0-9-]+$/)
  slug: string;
}

Slug-формат обеспечивает единообразие URL-адресов и маршрутов.

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

Использование неподходящих якорей

Отсутствие ^ и $ приводит к частичным совпадениям:

@Matches(/[a-z]+/)

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

Избыточная сложность

Регулярные выражения, реализующие полноценную бизнес-логику, усложняют сопровождение и тестирование.

Неправильное экранирование

В строках JavaScript требуется двойное экранирование:

@Matches(/^\\d+$/)

в то время как в литерале RegExp:

@Matches(/^\d+$/)

Практика организации валидаторов

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

export const REGEX = {
  USERNAME: /^[a-z0-9_]{3,16}$/,
  PHONE: /^\+?[0-9]{10,15}$/,
};

И используются централизованно во всех DTO.

Поведение при преобразовании типов

@Matches применяется только к строкам. При передаче значения другого типа (например, числа) поведение зависит от предварительной трансформации class-transformer. Без преобразования возможны некорректные результаты или пропуск валидации.

Совместимость с трансформацией данных

При использовании class-transformer важно учитывать порядок обработки:

  1. трансформация (plain → class)
  2. валидация (class-validator)

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

Расширенные сценарии

Условная валидация через группы

class PaymentDto {
  @Matches(/^[0-9]{16}$/, { groups: ["card"] })
  cardNumber: string;
}

Позволяет применять разные правила в зависимости от сценария.

Контекстная валидация

@Matches(/^[A-Z]+$/, {
  context: { type: "uppercase-code" },
})
code: string;

Контекст используется при кастомной обработке ошибок.

Итоговая роль в архитектуре DTO

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