如何在 Typescript 中轻松重用一组装饰器

Mic*_*iel 5 api validation decorator typescript

TL; 博士

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

问题

每次我的 REST API 收到请求时,它都会验证提供的主体属性(使用class-validator库)。每个路由都有自己专用的验证类(在代码中它们称为 Dtos)(参见示例

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

例子

路线一:公司创建

POST - /api/company
     >> Parameters: name, domain, size, contact
Run Code Online (Sandbox Code Playgroud)
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;
}
Run Code Online (Sandbox Code Playgroud)

路线 2:公司更新

PUT - /api/company
    >> Parameters: id, name, domain, size, contact
Run Code Online (Sandbox Code Playgroud)
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;
}
Run Code Online (Sandbox Code Playgroud)

我在寻找什么

正如您在示例中看到的那样,一个验证类需要使用另一个验证类的属性的情况并不少见。

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

问题:确保装饰器一旦更改/添加其他验证类就知道更新的最佳方法是什么。

有没有办法将它们组合成一个变量/装饰器?感谢任何 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;
}
Run Code Online (Sandbox Code Playgroud)

Eli*_*ski 6

由于装饰器的函数性质,您可以轻松定义自己的装饰器工厂来调用所有必需的验证器:

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);
  }
}
Run Code Online (Sandbox Code Playgroud)

操场

一家小型装饰工厂工厂

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);
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

操场