如何使一组装饰器在Typescript中易于重用 TL;博士

如何使一组装饰器在Typescript中易于重用 TL;博士,typescript,api,validation,decorator,Typescript,Api,Validation,Decorator,如何将装饰器(来自库)组合成一个可重用的装饰器 问题 每次我的RESTAPI收到请求时,它都会验证提供的主体属性(使用类验证程序库)。每个路由都有自己的专用验证类(在代码中称为DTO)(参见示例) 每个提供的body属性都有一些验证规则,这些规则有时会变得非常复杂,其他工程师应该能够轻松地重复使用这些验证规则 例子 路线1:公司创建 POST - /api/company >> Parameters: name, domain, size, contact 路线2:公司更

如何将装饰器(来自库)组合成一个可重用的装饰器

问题 每次我的RESTAPI收到请求时,它都会验证提供的主体属性(使用
类验证程序
库)。每个路由都有自己的专用验证类(在代码中称为DTO)(参见示例

每个提供的body属性都有一些验证规则,这些规则有时会变得非常复杂,其他工程师应该能够轻松地重复使用这些验证规则

例子 路线1:公司创建

POST - /api/company
     >> Parameters: name, domain, size, contact
路线2:公司更新

PUT - /api/company
    >> Parameters: id, name, domain, size, contact
我在寻找什么 正如您在示例中所看到的,一个验证类需要使用另一个验证类的属性并不少见

问题在于,如果工程师向随机验证类中的属性添加1条验证规则,其他验证类将不会动态更新

问题:什么是确保在修改/添加装饰程序后,其他验证类知道更新的最佳方法

有没有办法将它们组合成一个变量/装饰器?感谢任何打字大师的帮助

可接受的结果:

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;
}

由于decorators的功能特性,您可以轻松定义自己的decorator工厂,只需调用所有必需的验证器:

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);
  }
}

小型装饰厂

您可以通过定义自己的装饰器来链接装饰器。请注意,我以前没有使用
类验证器
库,因此需要对此进行测试。但这就是它的样子:

在这里,我定义了一个名为
IsCompanyName
的装饰器,并让它在您的示例中调用验证装饰器。我定义了
ValidationOptions
接口以传递给装饰器。由您决定是否需要此
选项
参数。如果未指定该选项,则由您定义默认行为。在我的示例中,我将其设置为可选的,并使其表现为默认情况下是必需的。仅当定义了
opts
并且
required===false时,我才调用
isonational
decorator。否则我就不叫它了

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);
        }
    }
}
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);
    }
  }
}
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);
        }
    }
}