Метаданные валидации, которые создаются декораторами
class-validator, формируют структурированный слой описания
данных, пригодный не только для проверки входных значений, но и для
автоматической генерации документации. Каждое правило, заданное через
декораторы, сохраняется в глобальном хранилище метаданных библиотеки и
может быть извлечено во время выполнения приложения.
Основная идея автогенерации документации строится на том, что валидационные декораторы уже содержат достаточный набор информации о структуре данных:
Пример модели:
import { IsString, IsInt, Min, Max, Length, IsOptional } from "class-validator";
class CreateUserDto {
@IsString()
@Length(3, 30)
username;
@IsInt()
@Min(0)
@Max(120)
age;
@IsOptional()
@IsString()
bio;
}
Каждый декоратор добавляет запись в внутреннее хранилище
MetadataStorage, которое используется при вызове
validate() и может быть использовано для построения
документации.
class-validator предоставляет доступ к метаданным через
getMetadataStorage():
import { getMetadataStorage } from "class-validator";
const metadataStorage = getMetadataStorage();
const metadata = metadataStorage.getTargetValidationMetadatas(
CreateUserDto,
"",
false,
false
);
Результат представляет собой массив объектов, описывающих каждое правило:
type — тип валидатора (isString, min, max и т.д.)propertyName — имя поляconstraints — параметры декоратораvalidationTypeOptions — дополнительные настройкиЭти данные являются основой для автоматического построения схемы документации.
Для генерации документации требуется преобразование метаданных в промежуточную структуру — модель полей.
Алгоритм включает:
propertyNameПример структуры:
{
username: {
type: "string",
required: true,
minLength: 3,
maxLength: 30
},
age: {
type: "number",
required: true,
min: 0,
max: 120
}
}
Определение типа поля часто требует дополнительной логики, так как
class-validator не хранит явный тип JavaScript. Обычно
используется комбинация:
reflect-metadata (design:type)import "reflect-metadata";
const type = Reflect.getMetadata("design:type", CreateUserDto.prototype, "username");
Одним из наиболее распространённых форматов документации является JSON Schema. Преобразование метаданных в JSON Schema позволяет использовать документацию в:
Пример генерации:
function buildJsonSchema(dtoClass) {
const metadataStorage = getMetadataStorage();
const metadatas = metadataStorage.getTargetValidationMetadatas(
dtoClass,
"",
false,
false
);
const schema = { type: "object", properties: {}, required: [] };
for (const meta of metadatas) {
const prop = meta.propertyName;
if (!schema.properties[prop]) {
schema.properties[prop] = {};
}
switch (meta.type) {
case "isString":
schema.properties[prop].type = "string";
break;
case "minLength":
schema.properties[prop].minLength = meta.constraints[0];
break;
case "maxLength":
schema.properties[prop].maxLength = meta.constraints[0];
break;
case "isInt":
schema.properties[prop].type = "integer";
break;
case "min":
schema.properties[prop].minimum = meta.constraints[0];
break;
case "max":
schema.properties[prop].maximum = meta.constraints[0];
break;
}
if (meta.type === "isDefined") {
schema.required.push(prop);
}
}
return schema;
}
Такая схема может быть сериализована и использована как документация API.
При использовании class-validator вместе с
class-transformer в серверных фреймворках возможно
автоматическое построение OpenAPI спецификации.
Типовой подход:
components.schemasПример результата OpenAPI:
{
"User": {
"type": "object",
"properties": {
"username": {
"type": "string",
"minLength": 3,
"maxLength": 30
},
"age": {
"type": "integer",
"minimum": 0,
"maximum": 120
}
},
"required": ["username", "age"]
}
}
В экосистемах, подобных NestJS, подобная генерация часто выполняется
через дополнительные слои, но базовый источник данных остаётся тем же —
метаданные class-validator.
Сложные DTO содержат вложенные структуры:
class Address {
@IsString()
city;
}
class User {
@ValidateNested()
address;
}
Для генерации документации требуется рекурсивный обход:
ValidateNestedfunction processNested(dtoClass) {
const schema = buildJsonSchema(dtoClass);
const metadataStorage = getMetadataStorage();
const nested = metadataStorage.getTargetValidationMetadatas(
dtoClass,
"",
false,
false
).filter(m => m.type === "validateNested");
for (const n of nested) {
const childType = Reflect.getMetadata("design:type", dtoClass.prototype, n.propertyName);
schema.properties[n.propertyName] = processNested(childType);
}
return schema;
}
Кастомные декораторы создаются через
registerDecorator:
import { registerDecorator } from "class-validator";
function IsEven() {
return function (object, propertyName) {
registerDecorator({
name: "isEven",
target: object.constructor,
propertyName,
validator: {
validate(value) {
return value % 2 === 0;
}
}
});
};
}
Для документации необходимо явно описывать семантику таких правил, поскольку библиотека не хранит смысловую интерпретацию валидатора.
Расширенный подход:
const validatorDocs = {
isEven: {
type: "number",
description: "Чётное число"
}
};
При генерации документации кастомные правила сопоставляются с этим справочником.
В реальных приложениях документация формируется не только из
class-validator, но и из:
class-transformer (трансформация типов)Итоговая модель строится как объединение слоёв:
При конфликте правил приоритет обычно отдаётся наиболее строгим ограничениям (например, минимальная длина из нескольких источников).
При большом количестве DTO генерация документации может стать затратной операцией. Оптимизация достигается через:
getMetadataStorage()const schemaCache = new Map();
function getSchema(dtoClass) {
if (schemaCache.has(dtoClass)) {
return schemaCache.get(dtoClass);
}
const schema = buildJsonSchema(dtoClass);
schemaCache.set(dtoClass, schema);
return schema;
}
Использование class-validator как источника документации
имеет структурные ограничения:
Несмотря на это, подход остаётся практичным в системах, где DTO уже являются центральной моделью данных, а валидация определяет контракт API.