Валидация строк

Валидация строковых значений в class-validator строится на декларативном описании ограничений, которые накладываются на свойства классов. Строка рассматривается не как «просто текст», а как объект с набором характеристик: длина, формат, допустимые символы, наличие пробелов, обязательность заполнения и контекст использования.

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

import { IsString } from "class-validator";

class UserDto {
  @IsString()
  username: string;
}

@IsString() проверяет, что значение действительно является строкой в момент валидации. Числа, null, undefined, объекты и массивы будут считаться невалидными.

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

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

IsNotEmpty

import { IsString, IsNotEmpty } from "class-validator";

class UserDto {
  @IsString()
  @IsNotEmpty()
  username: string;
}

@IsNotEmpty() проверяет, что строка существует и содержит хотя бы один символ. Значение "" считается недопустимым, как и null или undefined.

Поведение пустых пробелов

Пробельные строки (" ") формально не считаются пустыми. Это приводит к необходимости дополнительной логики.

import { Matches } from "class-validator";

class UserDto {
  @IsString()
  @Matches(/\S/, { message: "Строка не должна состоять только из пробелов" })
  username: string;
}

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

Ограничение длины строк

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

MinLength и MaxLength

import { MinLength, MaxLength } from "class-validator";

class UserDto {
  @IsString()
  @MinLength(3)
  @MaxLength(20)
  username: string;
}

@MinLength(n) требует минимальное количество символов. @MaxLength(n) ограничивает максимальную длину.

Важно учитывать, что длина считается в символах JavaScript-строки, а не в байтах. Это означает, что эмодзи и некоторые Unicode-символы могут занимать больше одного «визуального символа» при хранении.

Length как объединённая форма

import { Length } from "class-validator";

class UserDto {
  @IsString()
  @Length(3, 20)
  username: string;
}

@Length(min, max) эквивалентен комбинации минимального и максимального ограничения и используется для компактности.

Форматирование строк через регулярные выражения

Регулярные выражения дают максимальную гибкость при описании формата строки. В class-validator для этого используется декоратор @Matches.

Базовый паттерн

import { Matches } from "class-validator";

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

Данный пример ограничивает строку набором символов, формируя типичный формат «логина».

Строгие форматы

Регулярные выражения часто применяются для описания доменных сущностей:

class ProductDto {
  @IsString()
  @Matches(/^SKU-[0-9]{6}$/)
  sku: string;
}

Строка должна строго соответствовать шаблону SKU-XXXXXX.

Проверка на пробельные строки и очистка входных данных

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

Использование transform через class-transformer

import { Transform } from "class-transformer";
import { IsString } from "class-validator";

class UserDto {
  @Transform(({ value }) => typeof value === "string" ? value.trim() : value)
  @IsString()
  username: string;
}

trim() удаляет пробелы по краям строки, снижая вероятность некорректного прохождения @IsNotEmpty().

Опциональные строки

Не все строковые поля являются обязательными. В class-validator для этого используется @IsOptional().

import { IsOptional, IsString, MaxLength } from "class-validator";

class UserDto {
  @IsOptional()
  @IsString()
  @MaxLength(100)
  bio?: string;
}

Поведение @IsOptional() заключается в том, что валидация остальных декораторов не выполняется, если значение равно undefined или null.

Важно: пустая строка "" не считается отсутствием значения.

Различие между IsString и типами TypeScript

TypeScript-типизация не заменяет runtime-валидацию. Следующий код корректен с точки зрения TypeScript, но небезопасен без class-validator:

class UserDto {
  username: string;
}

Если в API приходит 123, TypeScript не защитит от этого. @IsString() выполняет проверку уже во время выполнения.

Комбинации декораторов для реальных сценариев

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

Логин пользователя

class UserDto {
  @Transform(({ value }) => typeof value === "string" ? value.trim() : value)
  @IsString()
  @IsNotEmpty()
  @MinLength(3)
  @MaxLength(20)
  @Matches(/^[a-zA-Z0-9_]+$/)
  username: string;
}

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

  • тип значения
  • отсутствие пустоты
  • длина
  • допустимые символы
  • нормализация пробелов

Проверка строковых идентификаторов

Часто строки используются как идентификаторы сущностей.

class CommentDto {
  @IsString()
  @Matches(/^[a-f0-9]{24}$/)
  postId: string;
}

Такой формат характерен для MongoDB ObjectId.

Частичная валидация через ValidateIf

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

import { ValidateIf, IsString } from "class-validator";

class ProfileDto {
  @ValidateIf(o => o.nickname !== undefined)
  @IsString()
  nickname: string;
}

ValidateIf позволяет включать или отключать проверки динамически.

Особенности работы с Unicode и международными строками

Строки в JavaScript основаны на UTF-16, что влияет на поведение длины и регулярных выражений.

class UserDto {
  @IsString()
  @MaxLength(10)
  name: string;
}

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

Регулярные выражения также должны учитывать Unicode-флаги:

@Matches(/^[\p{L}\s]+$/u)
name: string;

Флаг u включает Unicode-сопоставление, позволяя учитывать буквы разных языков.

Кастомные валидаторы для строковых правил

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

import { ValidatorConstraint, ValidatorConstraintInterface } from "class-validator";

@ValidatorConstraint({ name: "isNoWhitespaceOnly", async: false })
class IsNoWhitespaceOnly implements ValidatorConstraintInterface {
  validate(value: string) {
    return typeof value === "string" && value.trim().length > 0;
  }

  defaultMessage() {
    return "Строка не должна состоять только из пробелов";
  }
}

Применение:

import { Validate } from "class-validator";

class UserDto {
  @IsString()
  @Validate(IsNoWhitespaceOnly)
  username: string;
}

Валидация slug-строк

Slug используется в URL и требует строгого формата.

class ArticleDto {
  @IsString()
  @Matches(/^[a-z0-9]+(?:-[a-z0-9]+)*$/)
  slug: string;
}

Правила:

  • только латиница в нижнем регистре
  • цифры допустимы
  • слова разделяются дефисом
  • запрещены пробелы и спецсимволы

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

Каждый декоратор поддерживает переопределение сообщений об ошибке.

class UserDto {
  @IsString({ message: "Значение должно быть строкой" })
  @MinLength(3, { message: "Минимальная длина — 3 символа" })
  username: string;
}

Сообщения часто используются для формирования API-ответов, пригодных для фронтенда.

Поведение при множественных ошибках

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

Комбинированная валидация строк в class-validator строится на принципе композиции: каждое ограничение добавляет отдельный слой логики, а итоговая модель представляет собой совокупность правил, описывающих допустимое состояние строкового поля в системе.