Search code examples
nestjsclass-validator

Forbid specific enum value for DTO in Nestjs


My "AppState" enum has following possible enum values:

export enum AppState {
  SUCCESS,
  ERROR,
  RUNNING
}

I have a UpdateAppStateDTO with an appState which should accept every enum value except RUNNING.

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

For the route I have this example

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

If the request has an empty body or a non valid enum value like "foobar" for appState I'm getting a 400, which is fine.

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

How can I prevent this behaviour?


Solution

  • I assume you are sending in the string 'RUNNING', and you're trying to make sure that that is what is not used, correct? With what you've currently got, your enum maps to these values:

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

    So if you send in the string 'RUNNING', the validator checks that RUNNING !== 2 which is in fact true leading to successful validation. The @IsEnum() decorator checks that the value sent in in a valid key of the enum, so sending in 'RUNNING' passes that check, hence why you don't get some sort of error there.

    The most verbose way to fix this is to make your enum a string enum like so:

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

    This will make each AppState value map to its corresponding string, though that does lead to having to type out a lot of declarations and can lead to duplicate code.

    Another way to manage this is to set your @NotEquals() enum to the key provided by the enum value like so:

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

    But keep in mind that with this approach when you look at appState later it will still be a numeric value instead of a string.

    You can play around with this stackblitz I made for this to see some running code.