如何使一组装饰器在 Typescript 中易于重用
How to make a group of decorators easily reusable in Typescript
TL;DR
How to group decorators (from a library) together into 1 re-usable decorator
问题
每次我的 REST API 收到请求时,它都会验证提供的正文属性(使用 class-validator
库)。每个路由都有自己专用的验证class(在代码中它们称为 Dtos)(参见示例)
每个提供的正文 属性 都有一些验证规则,这些规则有时会变得非常复杂,其他工程师应该能够轻松地重复使用这些验证规则 .
例子
路线 1:创建公司
POST - /api/company
>> Parameters: name, domain, size, contact
class CreateCompanyDto implements Dto {
@IsString({message: 'Must be text format'})
@MinLength(2, { message: "Must have at least 2 characters" })
@MaxLength(20, { message: "Can't be longer than 20 characters" })
@IsDefined({ message: 'Must specify a receiver' })
public name!: string;
@MaxLength(253, { message: "Can't be longer than 253 characters" })
@IsFQDN({}, {message: 'Must be a valid domain name'})
@IsDefined({ message: 'Must specify a domain' })
public domain!: string;
@MaxLength(30, { message: "Can't be longer than 30 characters" })
@IsString({message: 'Must be text format'})
@IsDefined({ message: 'Must specify a company size' })
public size!: string;
@IsPhoneNumber(null, {message: 'Must be a valid phone number'})
@IsDefined({ message: 'Must specify a phone number' })
public contact!: string;
}
路线 2:公司更新
PUT - /api/company
>> Parameters: id, name, domain, size, contact
class UpdateCompanyDto implements Dto {
@IsUUID()
@IsDefined({ message: 'Must be defined' })
public id!: string;
@IsString({ message: 'Must be text format' })
@MinLength(2, { message: "Must have at least 2 characters" })
@MaxLength(20, { message: "Can't be longer than 20 characters" })
@IsOptional()
public name!: string;
@MaxLength(253, { message: "Can't be longer than 253 characters" })
@IsFQDN({}, { message: 'Must be a valid domain name' })
@IsOptional()
public domain!: string;
@MaxLength(30, { message: "Can't be longer than 30 characters" })
@IsString({ message: 'Must be text format' })
@IsOptional()
public size!: string;
@IsPhoneNumber(null, { message: 'Must be a valid phone number' })
@IsOptional()
public contact!: string;
}
我在寻找什么
正如您在示例中看到的那样,一个验证 class 需要使用另一个验证 class.
的属性的情况并不少见
问题在于,如果工程师在随机验证 class 中将 1 条验证规则添加到 属性,其他验证 classes 将不会动态更新。
问题:什么是最好的方法来确保一旦装饰器得到 changed/added 其他验证 classes 知道更新。
有什么方法可以将它们组合成一个 variable/decorator 吗?感谢任何 Typescript 专家的帮助!
可接受的结果:
class CreateCompanyDto implements Dto {
@IsCompanyName({required: true})
public name!: string;
@IsCompanyDomain({required: true})
public domain!: string;
@isCompanySize({required: true})
public size!: string;
@isCompanyContact({required: true})
public contact!: string;
}
class UpdateCompanyDto implements Dto {
@IsCompanyId({required: true})
public id!: string;
@IsCompanyName({required: false})
public name!: string;
@IsCompanyDomain({required: false})
public domain!: string;
@isCompanySize({required: false})
public size!: string;
@isCompanyContact({required: false})
public contact!: string;
}
由于装饰器的功能特性,您可以轻松定义自己的装饰器工厂来调用所有必需的验证器:
export function IsCompanyName({ required }: { required: boolean }): PropertyDecorator {
return function (target: any,
propertyKey: string | symbol): void {
IsString({ message: 'Must be text format' })(target, propertyKey);
MinLength(2, { message: "Must have at least 2 characters" })(target, propertyKey);
MaxLength(20, { message: "Can't be longer than 20 characters" })(target, propertyKey);
if (required)
IsDefined({ message: 'Must specify a receiver' })(target, propertyKey);
else
IsOptional()(target, propertyKey);
}
}
小装修厂工厂
export function ValidatorComposer(validators: PropertyDecorator[], name: string): (options: { required: boolean }) => PropertyDecorator {
return function ({ required }: { required: boolean }) {
return function (target: any,
propertyKey: string | symbol): void {
validators.forEach((validator) => validator(target, propertyKey));
if (required)
IsDefined({ message: 'Must specify a ' + name })(target, propertyKey);
else
IsOptional()(target, propertyKey);
}
}
}
您可以通过定义自己的装饰器来链接装饰器。请注意,我之前没有使用 class-validator
库,因此您需要对此进行测试。但这就是它的样子:
在这里,我定义了一个名为 IsCompanyName
的装饰器,并让它在您的示例中调用验证装饰器。我定义了 ValidationOptions
接口来传递给装饰器。是否需要此 opts
参数由您决定。如果未指定该选项,您也可以定义默认行为。在我的示例中,我将其设为可选,并使其表现得默认是必需的。只有在定义了 opts
并且 required === false
时,我才调用 IsOptional
装饰器。不然我不叫了
interface ValidationOptions {
required?: boolean
}
function IsCompanyName(opts?: ValidationOptions) {
return function (target: any, propertyKey: string) {
IsString({ message: 'Must be text format' })(target, propertyKey);
MinLength(2, { message: "Must have at least 2 characters" })(target, propertyKey);
MaxLength(20, { message: "Can't be longer than 20 characters" })(target, propertyKey);
IsDefined({ message: 'Must specify a receiver' })(target, propertyKey);
if (opts && opts.required === false) {
IsOptional()(target, propertyKey);
}
}
}
TL;DR
How to group decorators (from a library) together into 1 re-usable decorator
问题
每次我的 REST API 收到请求时,它都会验证提供的正文属性(使用 class-validator
库)。每个路由都有自己专用的验证class(在代码中它们称为 Dtos)(参见示例)
每个提供的正文 属性 都有一些验证规则,这些规则有时会变得非常复杂,其他工程师应该能够轻松地重复使用这些验证规则 .
例子
路线 1:创建公司
POST - /api/company
>> Parameters: name, domain, size, contact
class CreateCompanyDto implements Dto {
@IsString({message: 'Must be text format'})
@MinLength(2, { message: "Must have at least 2 characters" })
@MaxLength(20, { message: "Can't be longer than 20 characters" })
@IsDefined({ message: 'Must specify a receiver' })
public name!: string;
@MaxLength(253, { message: "Can't be longer than 253 characters" })
@IsFQDN({}, {message: 'Must be a valid domain name'})
@IsDefined({ message: 'Must specify a domain' })
public domain!: string;
@MaxLength(30, { message: "Can't be longer than 30 characters" })
@IsString({message: 'Must be text format'})
@IsDefined({ message: 'Must specify a company size' })
public size!: string;
@IsPhoneNumber(null, {message: 'Must be a valid phone number'})
@IsDefined({ message: 'Must specify a phone number' })
public contact!: string;
}
路线 2:公司更新
PUT - /api/company
>> Parameters: id, name, domain, size, contact
class UpdateCompanyDto implements Dto {
@IsUUID()
@IsDefined({ message: 'Must be defined' })
public id!: string;
@IsString({ message: 'Must be text format' })
@MinLength(2, { message: "Must have at least 2 characters" })
@MaxLength(20, { message: "Can't be longer than 20 characters" })
@IsOptional()
public name!: string;
@MaxLength(253, { message: "Can't be longer than 253 characters" })
@IsFQDN({}, { message: 'Must be a valid domain name' })
@IsOptional()
public domain!: string;
@MaxLength(30, { message: "Can't be longer than 30 characters" })
@IsString({ message: 'Must be text format' })
@IsOptional()
public size!: string;
@IsPhoneNumber(null, { message: 'Must be a valid phone number' })
@IsOptional()
public contact!: string;
}
我在寻找什么
正如您在示例中看到的那样,一个验证 class 需要使用另一个验证 class.
的属性的情况并不少见问题在于,如果工程师在随机验证 class 中将 1 条验证规则添加到 属性,其他验证 classes 将不会动态更新。
问题:什么是最好的方法来确保一旦装饰器得到 changed/added 其他验证 classes 知道更新。
有什么方法可以将它们组合成一个 variable/decorator 吗?感谢任何 Typescript 专家的帮助!
可接受的结果:
class CreateCompanyDto implements Dto {
@IsCompanyName({required: true})
public name!: string;
@IsCompanyDomain({required: true})
public domain!: string;
@isCompanySize({required: true})
public size!: string;
@isCompanyContact({required: true})
public contact!: string;
}
class UpdateCompanyDto implements Dto {
@IsCompanyId({required: true})
public id!: string;
@IsCompanyName({required: false})
public name!: string;
@IsCompanyDomain({required: false})
public domain!: string;
@isCompanySize({required: false})
public size!: string;
@isCompanyContact({required: false})
public contact!: string;
}
由于装饰器的功能特性,您可以轻松定义自己的装饰器工厂来调用所有必需的验证器:
export function IsCompanyName({ required }: { required: boolean }): PropertyDecorator {
return function (target: any,
propertyKey: string | symbol): void {
IsString({ message: 'Must be text format' })(target, propertyKey);
MinLength(2, { message: "Must have at least 2 characters" })(target, propertyKey);
MaxLength(20, { message: "Can't be longer than 20 characters" })(target, propertyKey);
if (required)
IsDefined({ message: 'Must specify a receiver' })(target, propertyKey);
else
IsOptional()(target, propertyKey);
}
}
小装修厂工厂
export function ValidatorComposer(validators: PropertyDecorator[], name: string): (options: { required: boolean }) => PropertyDecorator {
return function ({ required }: { required: boolean }) {
return function (target: any,
propertyKey: string | symbol): void {
validators.forEach((validator) => validator(target, propertyKey));
if (required)
IsDefined({ message: 'Must specify a ' + name })(target, propertyKey);
else
IsOptional()(target, propertyKey);
}
}
}
您可以通过定义自己的装饰器来链接装饰器。请注意,我之前没有使用 class-validator
库,因此您需要对此进行测试。但这就是它的样子:
在这里,我定义了一个名为 IsCompanyName
的装饰器,并让它在您的示例中调用验证装饰器。我定义了 ValidationOptions
接口来传递给装饰器。是否需要此 opts
参数由您决定。如果未指定该选项,您也可以定义默认行为。在我的示例中,我将其设为可选,并使其表现得默认是必需的。只有在定义了 opts
并且 required === false
时,我才调用 IsOptional
装饰器。不然我不叫了
interface ValidationOptions {
required?: boolean
}
function IsCompanyName(opts?: ValidationOptions) {
return function (target: any, propertyKey: string) {
IsString({ message: 'Must be text format' })(target, propertyKey);
MinLength(2, { message: "Must have at least 2 characters" })(target, propertyKey);
MaxLength(20, { message: "Can't be longer than 20 characters" })(target, propertyKey);
IsDefined({ message: 'Must specify a receiver' })(target, propertyKey);
if (opts && opts.required === false) {
IsOptional()(target, propertyKey);
}
}
}