В библиотеке class-validator каждый кастомный валидатор получает полный контекст текущей операции валидации через объект аргументов. Этот механизм позволяет работать не только со значением отдельного поля, но и с полным объектом данных, его структурой, соседними свойствами и метаданными декоратора.
Ключевой особенностью системы является то, что валидатор не изолирован от модели данных: он всегда выполняется в контексте объекта, на котором происходит проверка.
При запуске валидации библиотека формирует структуру, содержащую информацию о:
Эта структура передаётся в кастомные валидаторы через интерфейс
ValidationArguments.
Основной способ доступа к данным контекста — использование объекта
ValidationArguments, который передаётся в метод
validate.
Он содержит следующие ключевые поля:
Именно поле object является ключевым для доступа к
другим свойствам модели.
Кастомный валидатор в class-validator обычно реализуется через класс,
реализующий интерфейс ValidatorConstraintInterface.
import {
ValidatorConstraint,
ValidatorConstraintInterface,
ValidationArguments,
} from "class-validator";
@ValidatorConstraint({ name: "CustomRule", async: false })
export class CustomRule implements ValidatorConstraintInterface {
validate(value: any, args: ValidationArguments) {
const fullObject = args.object;
const propertyName = args.property;
return true;
}
defaultMessage(args: ValidationArguments) {
return "Ошибка валидации";
}
}
Поле args.object содержит исходный объект, переданный в
validate() на верхнем уровне.
Наиболее частое применение доступа к объекту — проверка зависимых свойств.
Например, проверка совпадения пароля и подтверждения пароля:
import {
ValidatorConstraint,
ValidatorConstraintInterface,
ValidationArguments,
} from "class-validator";
@ValidatorConstraint({ name: "MatchPassword", async: false })
export class MatchPassword implements ValidatorConstraintInterface {
validate(value: any, args: ValidationArguments) {
const obj = args.object as any;
return value === obj.password;
}
defaultMessage() {
return "Пароли не совпадают";
}
}
В этом случае:
value — значение поля подтверждения;obj.password — доступ к другому полю того же
объекта.Функция registerDecorator позволяет создавать декораторы
без явного класса валидатора.
import {
registerDecorator,
ValidationOptions,
ValidationArguments,
} from "class-validator";
export function IsGreaterThan(property: string, options?: ValidationOptions) {
return function (object: Object, propertyName: string) {
registerDecorator({
name: "IsGreaterThan",
target: object.constructor,
propertyName,
constraints: [property],
options,
validator: {
validate(value: any, args: ValidationArguments) {
const [relatedPropertyName] = args.constraints;
const relatedValue = (args.object as any)[relatedPropertyName];
return typeof value === "number" &&
typeof relatedValue === "number" &&
value > relatedValue;
},
},
});
};
}
Здесь ключевой момент — использование args.constraints
для передачи имени сравниваемого свойства, а доступ к объекту
осуществляется через args.object.
Когда валидируемая структура содержит вложенные объекты,
args.object всегда ссылается на текущий уровень, на котором
выполняется проверка.
class Address {
street: string;
}
class User {
address: Address;
}
При валидации поля address.street:
args.object будет содержать объект
Address;User напрямую отсутствует на этом уровне.Для доступа к родительскому объекту необходимо валидировать на уровне класса, а не вложенного свойства.
Поле targetName содержит строковое имя класса, что
полезно для логирования или универсальных валидаторов.
validate(value: any, args: ValidationArguments) {
const className = args.targetName;
return true;
}
Поле target (в зависимости от версии и конфигурации)
может содержать ссылку на прототип класса, однако его использование
ограничено и не всегда стабильно при трансформациях объектов.
Несмотря на наличие args.object, существуют важные
ограничения:
class-transformer;Разделение контекста на value и object
позволяет отделить локальную и глобальную логику:
Пример:
validate(value: string, args: ValidationArguments) {
return value.length > 0 && args.object !== null;
}
Такой подход делает возможной проверку бизнес-правил, зависящих от нескольких полей одновременно.
constraints позволяет передавать произвольные параметры
в декоратор.
validate(value: number, args: ValidationArguments) {
const [min, max] = args.constraints;
return value >= min && value <= max;
}
Пример вызова:
@IsInRange(10, 100)
price: number;
Таким образом, валидатор получает как сам объект, так и конфигурацию поведения.
При использовании class-transformer объект в
args.object может быть уже преобразован в экземпляр
класса:
import { plainToInstance } from "class-transformer";
const user = plainToInstance(User, plainObject);
В этом случае:
Для повышения точности работы с объектом часто применяется явное приведение типов:
validate(value: any, args: ValidationArguments) {
const obj = args.object as User;
return obj.email !== value;
}
Без приведения типов доступ к полям остаётся нестрогим, что снижает безопасность кода в сложных моделях.
Наиболее распространённые сценарии:
isActive === true, поле
обязательно);validate(value: string, args: ValidationArguments) {
const obj = args.object as any;
if (obj.isRequired) {
return value !== undefined && value !== null && value !== "";
}
return true;
}
Здесь логика валидатора полностью зависит от состояния объекта, а не только от отдельного поля.
Во время выполнения:
args.object.Изменение объекта внутри валидатора приводит к непредсказуемым результатам, поскольку один и тот же объект используется в цепочке проверок.
ValidationArguments выступает центральным контейнером
контекста выполнения, обеспечивая:
Именно через него реализуется возможность построения сложной бизнес-валидации без выхода за пределы декларативного подхода.