禁止 Nestjs 中 DTO 的特定枚举值

Forbid specific enum value for DTO in Nestjs

我的“AppState”枚举有以下可能的枚举值:

export enum AppState {
  SUCCESS,
  ERROR,
  RUNNING
}

我有一个 UpdateAppStateDTO 和一个 appState 应该接受除 运行 之外的每个枚举值。

export class UpdateAppStateDTO {
  @IsEnum(AppState)
  @NotEquals(AppState.RUNNING) // Doesn't work properly
  public appState: AppState;
}

对于路线我有这个例子

  @Patch()
  public setState(@Body() { appState }: UpdateAppStateDTO): void {
    console.log(appState);
  }

如果请求有空主体或无效枚举值,如 appState 的“foobar”,我得到 400,这很好。

问题是,当我发送“运行”时,我仍然收到 200 而不是 400。

如何防止这种行为?

我假设您发送的是字符串 'RUNNING',并且您试图确保未使用该字符串,对吗?根据您目前所拥有的,您的枚举映射到这些值:

export enum AppState {
  SUCCESS = 0,
  ERROR = 1,
  RUNNING = 2
}

因此,如果您发送字符串 'RUNNING',验证程序会检查 RUNNING !== 2 实际上是 true,从而导致验证成功。 @IsEnum() 装饰器检查在枚举的有效键中发送的值,因此发送 'RUNNING' 通过该检查,因此你在那里没有得到某种错误。

解决此问题的最详细方法是使您的枚举成为 string enum,如下所示:

export enum AppState {
  SUCCESS = 'SUCCESS',
  ERROR = 'ERROR',
  RUNNING = 'RUNNING'
}

这将使每个 AppState 值映射到其相应的字符串,尽管这确实会导致必须输入大量声明并可能导致重复代码。

另一种管理方法是将您的 @NotEquals() 枚举设置为枚举值提供的键,如下所示:

export class UpdateAppStateDTO {
  @IsEnum(AppState)
  @NotEquals(AppState[AppState.RUNNING])
  public appState: AppState;
}

但请记住,当您稍后查看 appState 时,使用这种方法它仍然是一个数值而不是字符串。

你可以玩玩 this stackblitz 我为此做了一些 运行 代码。

The problem is that when I send "RUNNING" I'm still getting a 200 instead of a 400.

您似乎在请求负载中使用字符串 (!)“运行”作为值:

{ appState: "RUNNING" }

在这种情况下,IsEnumNotEquals都认为有效载荷有效。

这是为什么?

首先 numeric enums are reverse mapped by typescript 所以你的枚举在内部(作为 javascript 对象)表示如下:

{
  '0': 'SUCCESS',
  '1': 'ERROR',
  '2': 'RUNNING',
  'SUCCESS': 0,
  'ERROR': 1,
  'RUNNING': 2
}

现在 class-验证者的 isEnum() is coded as follows:

isEnum(value: unknown, entity: any): boolean {
    const enumValues = Object.keys(entity)
        .map(k => entity[k]);
    return enumValues.indexOf(value) >= 0;
}

并且由于枚举是反向映射的 isEnum('RUNNNING', AppState) 将 return 为真。

同时NotEquals,其中is coded as such...

notEquals(value: unknown, comparison: unknown): boolean {
    return value !== comparison;
}

将字符串 'RUNNING' 与 AppState.RUNNING(等于 2)进行比较,并得出结论认为这是有效的,因为 'RUNNING' != 2.

所以你明白了为什么负载 { appState: "RUNNING" } 会导致 200 而不是 400 状态代码。

How can I prevent this behaviour?

枚举值 AppState.RUNNING 等于 2 所以当你发出请求时,你应该在你的有效负载中使用 2 的数值:

{ appState: 2 }

在上述情况下,class-验证器的 NotEquals 验证器将正确拒绝请求,响应包含:

"constraints": {
    "notEquals": "appState should not be equal to 2"
}