Кастомные декораторы в Class-validator строятся на основе функций-фабрик, которые позволяют инкапсулировать логику валидации и одновременно передавать в неё параметры извне. Такой подход делает декораторы переиспользуемыми и конфигурируемыми, превращая их в полноценные инструменты описания бизнес-правил на уровне модели.
Передача параметров начинается с объявления функции, возвращающей сам декоратор. Эта функция принимает аргументы, которые затем фиксируются в замыкании и используются внутри валидатора.
import { registerDecorator, ValidationOptions, ValidationArguments } fr om "class-validator";
export function IsPositiveWithThreshold(threshold: number, options?: ValidationOptions) {
return function (object: Object, propertyName: string) {
registerDecorator({
name: "IsPositiveWithThreshold",
target: object.constructor,
propertyName: propertyName,
options: options,
constraints: [threshold],
validator: {
validate(value: any, args: ValidationArguments) {
const [min] = args.constraints;
return typeof value === "number" && value > 0 && value >= min;
}
}
});
};
}
Ключевой момент заключается в том, что параметр
threshold не передаётся напрямую в validate, а
сохраняется в constraints.
Поле constraints в registerDecorator
является стандартным механизмом передачи произвольных значений в
валидатор.
constraints: [threshold]
Эти значения затем доступны через объект
ValidationArguments:
validate(value: any, args: ValidationArguments) {
const [min] = args.constraints;
return value >= min;
}
Структура constraints позволяет передавать несколько
параметров одновременно:
constraints: [min, max, step]
и извлекать их по позиции:
const [min, max, step] = args.constraints;
Такой подход делает декоратор полностью параметризуемым без необходимости создавать дополнительные поля или конфигурационные объекты.
Помимо constraints, параметры декоратора доступны через
замыкание. Это позволяет комбинировать два подхода: фиксированные
параметры и динамические данные из объекта.
export function StartsWith(prefix: string, options?: ValidationOptions) {
return function (object: Object, propertyName: string) {
registerDecorator({
name: "StartsWith",
target: object.constructor,
propertyName,
options,
validator: {
validate(value: any) {
return typeof value === "string" && value.startsWith(prefix);
}
}
});
};
}
Здесь prefix доступен напрямую внутри
validate, без использования constraints. Такой
подход удобен, когда параметров немного и они не требуют
структурирования.
ValidationArguments расширяет возможности
параметризации, предоставляя доступ к:
object)property)value)constraintsvalidate(value: any, args: ValidationArguments) {
const object = args.object;
const propertyName = args.property;
const [limit] = args.constraints;
}
Это позволяет строить декораторы, зависящие не только от переданных параметров, но и от состояния всего объекта.
Когда параметров становится больше двух-трёх, использование массива
constraints теряет читаемость. В таких случаях применяется
упаковка параметров в объект.
type RangeOptions = {
min: number;
max: number;
inclusive?: boolean;
};
Фабрика декоратора:
export function IsInRange(range: RangeOptions, options?: ValidationOptions) {
return function (object: Object, propertyName: string) {
registerDecorator({
name: "IsInRange",
target: object.constructor,
propertyName,
options,
constraints: [range],
validator: {
validate(value: any, args: ValidationArguments) {
const config = args.constraints[0] as RangeOptions;
if (config.inclusive) {
return value >= config.min && value <= config.max;
}
return value > config.min && value < config.max;
}
}
});
};
}
Такой способ обеспечивает расширяемость без изменения сигнатуры декоратора.
Второй аргумент фабрики декоратора обычно используется для передачи стандартных опций валидации:
each, always и др.)export function IsEven(divisor: number, options?: ValidationOptions) {
return function (object: Object, propertyName: string) {
registerDecorator({
name: "IsEven",
target: object.constructor,
propertyName,
options,
constraints: [divisor],
validator: {
validate(value: number) {
return value % divisor === 0;
}
}
});
};
}
Здесь options не участвуют в логике проверки, но влияют
на поведение системы валидации в целом.
Типизация играет ключевую роль в корректной передаче параметров в кастомные декораторы.
export function MinLengthWithStep(min: number, step: number, options?: ValidationOptions) {
return function (object: Object, propertyName: string) {
registerDecorator({
name: "MinLengthWithStep",
target: object.constructor,
propertyName,
options,
constraints: [min, step],
validator: {
validate(value: string, args: ValidationArguments) {
const [minLen, stepVal] = args.constraints;
return (
typeof value === "string" &&
value.length >= minLen &&
value.length % stepVal === 0
);
}
}
});
};
}
Использование явных типов для параметров предотвращает ошибки при масштабировании библиотеки декораторов.
Параметры, переданные в декоратор, могут интерпретироваться динамически в зависимости от состояния объекта.
validate(value: any, args: ValidationArguments) {
const [getLimit] = args.constraints;
const lim it = typeof getLimit === "function"
? getLimit(args.object)
: getLimit;
return value <= limit;
}
Такой подход позволяет передавать не только статические значения, но и функции, вычисляющие параметры на основе объекта.
При использовании нескольких декораторов на одном свойстве каждый из
них получает собственный набор параметров и контекст. Порядок передачи
параметров не зависит от порядка объявления декораторов, но зависит от
их внутренней регистрации через registerDecorator.
Каждый валидатор изолирован:
constraintsValidationArgumentsvalidateВ сложных системах валидации параметры могут использоваться для:
Пример условного поведения:
export function IsGreaterThan(field: string, options?: ValidationOptions) {
return function (object: Object, propertyName: string) {
registerDecorator({
name: "IsGreaterThan",
target: object.constructor,
propertyName,
options,
constraints: [field],
validator: {
validate(value: number, args: ValidationArguments) {
const relatedValue = (args.object as any)[args.constraints[0]];
return typeof value === "number" && value > relatedValue;
}
}
});
};
}
Здесь параметр field определяет динамическую зависимость
между свойствами объекта.
Внутренняя архитектура кастомных декораторов в Class-validator опирается на три основных механизма:
constraints для структурированной передачи значений в
валидаторValidationArguments для доступа к контексту
выполненияСовмещение этих механизмов позволяет создавать гибкие, масштабируемые и строго типизированные правила валидации, адаптируемые под любые уровни сложности бизнес-логики.