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

Валидация типов данных в прикладных JavaScript-приложениях решает задачу контроля входных значений на уровне модели данных, исключая распространение некорректных значений в бизнес-логику. В экосистеме TypeScript и JavaScript эта задача часто переносится на уровень рантайма, поскольку статическая типизация не всегда гарантирует корректность данных, поступающих извне (HTTP-запросы, формы, очереди сообщений).

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

Ключевая особенность подхода — перенос описания требований к данным в структуру класса. Вместо ручных проверок вида typeof value === 'string' используются декораторы, которые формируют набор правил.

Пример базовой модели:

import { IsString, IsNumber } fr om "class-validator";

class CreateUserDto {
  @IsString()
  name: string;

  @IsNumber()
  age: number;
}

Каждое поле становится носителем метаданных, описывающих допустимый тип данных. При этом сами декораторы не выполняют проверку немедленно — они лишь регистрируют правила, которые затем интерпретируются валидатором.

Механизм работы с метаданными

В основе работы лежит использование reflect-metadata, позволяющего сохранять информацию о типах и декораторах в рантайме. При объявлении класса библиотека фиксирует:

  • имя свойства
  • список валидаторов
  • дополнительные параметры (например, ограничения диапазонов)

Во время выполнения создаётся описание правил, которое затем используется функцией validate() для анализа объекта.

import { validate } from "class-validator";

const user = new CreateUserDto();
user.name = 123 as any;
user.age = "old" as any;

validate(user).then(errors => {
  console.log(errors);
});

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

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

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

Основные декораторы:

  • @IsString() — проверка, что значение является строкой
  • @IsNotEmpty() — проверка на непустое значение
  • @Length(min, max) — ограничение длины строки
  • @Matches(regex) — проверка по регулярному выражению

Пример комбинированной валидации:

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

class ProductDto {
  @IsString()
  @IsNotEmpty()
  @Length(3, 50)
  title: string;
}

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

Числовые типы и особенности преобразования

Работа с числами в JavaScript осложняется тем, что входящие данные часто приходят в строковом формате. Поэтому проверка типа должна учитывать не только number, но и корректность преобразования.

Основные декораторы:

  • @IsNumber() — проверка числового типа
  • @IsInt() — проверка целого числа
  • @Min(value) — минимальное значение
  • @Max(value) — максимальное значение
import { IsInt, Min, Max } from "class-validator";

class PaginationDto {
  @IsInt()
  @Min(1)
  @Max(100)
  lim it: number;
}

Дополнительно может применяться опция преобразования входных данных (обычно на уровне пайпов или трансформации DTO), чтобы строковые значения приводились к числам до валидации.

Булевы значения и их неоднозначность

Булевы значения в HTTP-контексте часто приходят в виде строк "true" и "false", что требует аккуратной обработки.

Доступные декораторы:

  • @IsBoolean() — проверка булевого типа
import { IsBoolean } from "class-validator";

class FeatureToggleDto {
  @IsBoolean()
  isEnabled: boolean;
}

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

Проверка массивов и вложенных структур

Одна из сильных сторон библиотеки — поддержка вложенной валидации объектов и массивов.

Основные инструменты:

  • @IsArray() — проверка массива
  • @ValidateNested() — рекурсивная валидация вложенных объектов
  • @Type() — указание типа элементов (часто используется с class-transformer)
import { IsArray, ValidateNested, IsString } from "class-validator";
import { Type } from "class-transformer";

class TagDto {
  @IsString()
  name: string;
}

class ArticleDto {
  @IsArray()
  @ValidateNested({ each: true })
  @Type(() => TagDto)
  tags: TagDto[];
}

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

Проверка наличия и обязательности полей

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

  • @IsDefined() — поле должно быть определено
  • @IsOptional() — поле может отсутствовать
  • @IsNotEmpty() — значение не должно быть пустым
import { IsDefined, IsOptional, IsString } from "class-validator";

class UpdateUserDto {
  @IsOptional()
  @IsString()
  nickname?: string;

  @IsDefined()
  id: number;
}

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

Строгая проверка форматов

Часто требуется проверка структурированных строковых значений:

  • email
  • UUID
  • URL
  • даты

Примеры декораторов:

  • @IsEmail()
  • @IsUUID()
  • @IsUrl()
  • @IsDateString()
import { IsEmail, IsUUID } from "class-validator";

class AccountDto {
  @IsEmail()
  email: string;

  @IsUUID()
  id: string;
}

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

Композиция правил и многоуровневая валидация

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

import { IsString, Length, Matches } from "class-validator";

class PasswordDto {
  @IsString()
  @Length(8, 32)
  @Matches(/[A-Z]/)
  value: string;
}

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

Группы валидации и контекст выполнения

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

import { IsString } from "class-validator";

class UserDto {
  @IsString({ groups: ["create"] })
  password: string;

  @IsString({ groups: ["update"] })
  nickname: string;
}

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

Асинхронная проверка и внешние зависимости

Некоторые типы проверок требуют обращения к внешним источникам: базе данных, API или кэшу. Для этого используются асинхронные валидаторы.

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

@ValidatorConstraint({ async: true })
class IsUserAlreadyExist implements ValidatorConstraintInterface {
  async validate(email: string) {
    const user = await database.findUser(email);
    return !user;
  }
}

Асинхронные проверки позволяют интегрировать бизнес-логику в систему валидации без нарушения архитектурных границ слоёв приложения.

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

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

  • свойство, в котором обнаружена ошибка
  • список ограничений
  • описание нарушения
[
  {
    property: "name",
    constraints: {
      isString: "name must be a string"
    }
  }
]

Такая структура облегчает построение пользовательских сообщений и интеграцию с API-ответами.

Частичные объекты и трансформация входных данных

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

Это особенно важно при работе с JSON, где все значения приходят как строки или простые структуры без типизации.

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