typescriptrestnestjs

Validate Enum directly in controller function


I have a query parameter in my REST API, which values should be restricted by an enum type. I'm looking for a way to throw a "Bad Request" error, when a client gives something different.

My enum looks like this:

export enum Precision {
    S = 's',
    MS = 'ms',
    U = 'u',
    NS = 'ns',
}

My controller function looks like this:

  @Get(':deviceId/:datapoint/last')
  @ApiOkResponse()
  @ApiQuery({name: 'precision', enum: Precision})
  getLastMeasurement(
    @Param('deviceId') deviceId: string,
    @Param('datapoint') datapoint: string,
    @Query('precision') precision: Precision = Precision.S,
    @Res() response: Response,
  ) {
    console.log(precision);
    ....
    response.status(HttpStatus.OK).send(body);
  }

My problem here is that the function accepts other values, too (for example I can send an f as the query parameter's value). The Function won't return an error to the client, but I want to without writing an if else block at the beginning of each controller function.
I guess there is a rather simple solution to this, but when I try to look it up on the internet I always get results for class validation in DTOs, but not for a simple enum validation directly in the query param/REST controller.

Thanks for your time,
J


Solution

  • There is 2 issues here.

    The first one is that you are passing the default value of precision params in the wrong way. You must use the DefaultValuePipe like this:

    getLastMeasurement(
      ... // some other params
        @Query('precision', new DefaultValuePipe(Precision.S)) precision: Precision
      ) { 
      ... // do something
    }
    

    The second one is the enum validation. NestJS comes with only 6 types of validationPipes and none of them validates an enum, so you must create your own custom validation pipe to validate enums.

    There is 2 possible ways that you can do that:

    1. Create a custom pipe to validate only your specific enum;
    2. Create a custom pipe generic to validate any enum;

    Based on this https://docs.nestjs.com/pipes#custom-pipes, it would be something like:

    1. Validate only the specific enum
    import { BadRequestException, PipeTransform } from '@nestjs/common';
    import { isDefined, isEnum } from 'class-validator';
    
    export class PrecisionValidationPipe implements PipeTransform<string, Promise<Precision>> {
    
      transform(value: string): Promise<Precision> {
        if (isDefined(value) && isEnum(value, Precision)) {
          return Precision[value];
        } else {
          const errorMessage = `the value ${value} is not valid. See the acceptable values: ${Object.keys(
            Precision
          ).map(key => Precision[key])}`;
          throw new BadRequestException(errorMessage);
        }
      }
    }
    

    and then in your request, it would be like

      getLastMeasurement(
        @Query('precision', PrecisionValidationPipe, new DefaultValuePipe(Precision.S)) precision: Precision,
      ) {
        console.log(precision);
        ....
        response.status(HttpStatus.OK).send(body);
      }
    
    1. Validate any enum (my favorite)
    import { BadRequestException, Injectable, PipeTransform } from '@nestjs/common';
    import { isDefined, isEnum } from 'class-validator';
    
    @Injectable()
    export class EnumValidationPipe implements PipeTransform<string, Promise<any>> {
      constructor(private enumEntity: any) {}
      transform(value: string): Promise<any> {
          if (isDefined(value) && isEnum(value, this.enumEntity)) {
            return this.enumEntity[value];
          } else {
            const errorMessage = `the value ${value} is not valid. See the acceptable values: ${Object.keys(this.enumEntity).map(key => this.enumEntity[key])}`;
            throw new BadRequestException(errorMessage);
          }
      }
    }
    

    and then in your request, it would be like

      getLastMeasurement(
        @Query('precision', new EnumValidationPipe(Precision), new DefaultValuePipe(Precision.S)) precision: Precision,
      ) {
        console.log(precision);
        ....
        response.status(HttpStatus.OK).send(body);
      }