midway icon indicating copy to clipboard operation
midway copied to clipboard

问题:含有子类的验证和类型转换

Open ghostoy opened this issue 2 years ago • 4 comments

  • Node Version: 14
  • Midway Version(Decorator/Core): 3.4.0
  • Component Name/Version: validate 和 core
  • Platform: any
  • Mini Showcase Repository:

复杂的对象可能需要多个子类来进行不同的验证方式,允许用户传入任何一种合法的格式,例如

// 基类
class Human {
  echo() {
    return '一个人类';
  }
}

// 子类1
class HumanByAge extends Human {
  @Rule(RuleType.string().valid('婴儿', '儿童', '成年人', '老人').required()
  age: string;

  echo() {
    return '一个' + this.age;
  }
}

// 子类2
class HumanByLocation extends Human {
  @Rule(RuleType.string().valid('亚洲', '欧洲', '北美洲', '南美洲', '非洲', '大洋洲').required()
  location: stirng;

  echo() {
    return '一个生活在' + this.location + '的人类';
  }
}

问题1:假如子类对象是包含在DTO对象内的

class HumanDTO {
  // 这里怎样实现根据实际类型来分别验证,并将human转成正确子类
  @Rule()
  human: Human;

  // 其他
}

@Provide()
@Controller('/')
export class APIController {
  @Post('/echo')
  @Validate()
  async echo(@Body(ALL) human: HumanDTO) {
    return human.human.echo();
  }
}

问题2:假如子类是直接作为DTO对象传入Controller参数的

@Provide()
@Controller('/')
export class APIController {
  @Post('/echo')
  @Validate()
  async echo(@Body('human') human: Human) {
    // 这里如何验证human,并将human转成正确的子类
    return human.echo();
  }
}

ghostoy avatar Jun 29 '22 08:06 ghostoy

这种例子有很多,比如

  • DB Connection可以是host, port的对象,也可以是一个uri连接字符串,或者sqlite那种包含database文件名的
  • 用户设备可以是UserAgent格式的字符串,或者包含device, platform, version的对象
  • 一本书可以是一个ISBN号码,或者包含国家、出版社、版本、年份、名称、作者的对象

ghostoy avatar Jun 29 '22 09:06 ghostoy

如果是子类,我理解就是面向不同 DTO 的单独校验了,直接在装饰器上做并不是适合了,还是要靠 validateService 的 API 来单独校验了。

czy88840616 avatar Jun 29 '22 09:06 czy88840616

对于问题1,我现在有个办法就是给Human增加一个type字段声明类别,然后用joi.when判断类型,用getClassMetadata(RULES_KEY, SubClass)来获取验证规则,以及用class-transformer的discriminator来转换成实际的子类对象。

class HumanDTO {
  @Rule(RuleType.when('human.type', {
    switch: [
      {
        is: 'by-age',
        then: RuleType.object(getClassMetadata(RULES_KEY, HumanByAge).meta({ id: HumanByAge.name })
      },
      {
        is: 'by-location',
        then: RuleType.object(getClassMetadata(RULES_KEY, HumanByLocation).meta({ id: HumanByLocation.name })
      }
    ],
    break: true
  }))
  @Type(() => Human, {
    discriminator: {
      property: 'type',
      subTypes: [
        { value: HumanByAge, name: 'by-age' },
        { value: HumanByLocation, name: 'by-location' },
      ]
    }
  })
  human: Human;
}

这个办法有2个问题:

  1. 必须把type内嵌到human对象里
  2. 只能使用单一的字段区分子类类型
  3. 重复写@Rule@Type规则,分别判断子类,如果@Rule规则可以自动使用@Type转换过的子类上的验证规则就好了

ghostoy avatar Jun 30 '22 02:06 ghostoy

如果是子类,我理解就是面向不同 DTO 的单独校验了,直接在装饰器上做并不是适合了,还是要靠 validateService 的 API 来单独校验了。

我觉得Joi已经提供了类似的验证机制,能不能跟class-transformer再整合一下,让DTO的验证更方便一些呢

ghostoy avatar Jul 06 '22 02:07 ghostoy