Verify enum values within controller function

I am dealing with a query parameter in my REST API that should be restricted to specific values according to an enum type. I need to find a way to handle a "Bad Request" error if the client provides any value outside of this enum.

Here is what my enum looks like:

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

This is how my controller function is structured:

  @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);
  }

The issue I am facing is that the function currently allows for other values besides those defined in the enum (e.g., accepting 'f' as a query parameter value). While no error is returned to the client, I want to address this without having to include an if-else block at the beginning of each controller function.
I believe there must be a straightforward solution to enforcing enum validation directly in the query parameter and REST controller, but my searches online have mainly yielded results related to class validation in DTOs rather than this specific scenario.

Thank you for your time,
J

Answer №1

There are two issues to address in this code.

The first problem lies in the incorrect passing of the default value for the precision parameter. To rectify this, you should utilize the DefaultValuePipe as shown below:

getLastMeasurement(
  ... // other parameters
    @Query('precision', new DefaultValuePipe(Precision.S)) precision: Precision
  ) { 
  ... // perform actions
}

The second issue pertains to enum validation. NestJS offers only six types of validation pipes, none of which directly validate enums. Therefore, you will need to create a custom validation pipe specifically for handling enums.

There are two approaches you can take to tackle this problem:

  1. Create a custom pipe tailored to validating your specific enum;
  2. Develop a generic custom pipe capable of validating any enum.

Referring to the documentation at https://docs.nestjs.com/pipes#custom-pipes, the implementation would resemble:

  1. Validation for a 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);
    }
  }
}

Subsequently, in your request, it would be implemented as follows:

  getLastMeasurement(
    @Query('precision', PrecisionValidationPipe, new DefaultValuePipe(Precision.S)) precision: Precision,
  ) {
    console.log(precision);
    ....
    response.status(HttpStatus.OK).send(body);
  }
  1. Validation for any enum (preferred approach)
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);
      }
  }
}

Following that, in your request, it would be set up like so:

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

Answer №2

If you want to ensure that the expected values are being sent in for a class, consider creating a class similar to LastMeasurementQueryParams that utilizes the decorators provided by class-validator. You can then use NestJS's built-in ValidationPipe to validate and verify these values.

An implementation of such a class might resemble the following:

export class LastMeasurementQueryParams {

  @IsEnum(Precision)
  precision: Precision;
}

Your controller could be structured as follows:

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

Answer №3

Fixed a bug in Raphael's response where the enum key was returned instead of the value.

Additionally, utilized the metadata parameter to display the field name in the error message and made the following change:

${Object.values(this.enumEntity)}

Here is the updated code for the validator:

@Injectable()
export class EnumValidationPipe implements PipeTransform<string, Promise<any>> {
  constructor(private enumEntity: any) {}
  transform(value: string, metadata: ArgumentMetadata): Promise<any> {
    if (isDefined(value) && isEnum(value, this.enumEntity)) {
      return Promise.resolve(value);
    } else {
      const errorMessage = `the value ${value} from field ${
        metadata.data
      } is not valid. Acceptable values: ${Object.values(this.enumEntity)}`;
      throw new BadRequestException(errorMessage);
    }
  }
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Guide on linking action observables to emit values in sync before emitting a final value

If you're familiar with Redux-Observable, I have a method that accepts an observable as input and returns another observable with the following type signature: function (action$: Observable<Action>): Observable<Action>; When this method r ...

What is the proper way to implement a class decorator in TypeScript?

How can a class decorator be implemented to accept only specific classes? A attempted solution is as follows: class Component { age: number; } function registerComponent(name: string) { return <T extends Component>(constructor: T): T => { ...

What is the equivalent of getElementById in .ts when working with tags in .js?

Looking to incorporate Electron, Preload, Renderer with ReactJS and TypeScript into my project. <index.html> <body> <div id="root" /> <script src='./renderer.js'/> </body> <index.ts> const root = Re ...

Tips on organizing all property options into an array of objects

I need to identify the array type of a specific property and use TypeScript typing for autocompletion. const arr = [{test:'option 1'},{test: 'option 2'}] type test = arr[number]['test'] let t:test = '' // will equal ...

What is a way to execute a series of requests using rxjs similar to forkJoin and combineLatest, without needing to wait for all requests to finish before viewing the results?

Consider you have a list of web addresses: urls: string[] You create a set of requests (in this instance, utilizing Angular's HTTPClient.get which gives back an Observable) const requests = urls.map((url, index) => this.http.get<Film>(url) ...

Encountering a getStaticProps error while using Typescript with Next.js

I encountered an issue with the following code snippet: export const getStaticProps: GetStaticProps<HomeProps> = async () => { const firstCategory = 0; const { data }: AxiosResponse<MenuItem[]> = await axios.post( ...

Tips on customizing the appearance of the dropdown calendar for the ngx-daterangepicker-material package

Is there a way to customize the top, left, and width styling of the calendar? I'm struggling to find the right approach. I've been working with this date range picker. Despite trying to add classes and styles, I can't seem to update the app ...

What sets `isomorphic-fetch` apart from `isomorphic-unfetch` in the world of npm packages?

Both Isomorphic Unfetch and Isomorphic Fetch are used for server-side rendering. But what sets them apart? Aside from the fact that Isomorphic Fetch is older and slightly larger when gzipped according to this comparison. Learn more about each library here ...

How to access a component attribute in a JavaScript library method in Angular 8

Within my Angular project, I am utilizing Semantic UI with the code snippet below: componentProperty: boolean = false; ngOnInit() { (<any>$('.ui.dropdown')).dropdown(); (<any>$('.ui.input')).popup({ ...

Angular 5 is throwing an error that says: "There is a TypeError and it cannot read the property 'nativeElement' because it

Being aware that I may not be the first to inquire about this issue, I find myself working on an Angular 5 application where I need to programmatically open an accordion. Everything seems to function as expected in stackblitz, but unfortunately, I am enco ...

Encountering issues with Socket.io: consistently experiencing websocket connection failures along with persistent 404 errors on the

I am facing issues with setting up a websocket using socket.io. The server-side seems to be making a GET call successfully, but on the client-side, I am getting a 404 error: GET http://localhost:6543/socket.io/?uuid=258c4ab9-b263-47ca-ab64-83fe99ea03d4& ...

The combination of React Vite and SockJS Client has encountered a failure in all transport

My current project is utilizing react + vite without any proxy configuration. I am attempting to use webstomp-client and sockjs to establish a connection with a websocket server that is supported by Springboot using SockJS. The backend Springboot server w ...

How can we limit the CSS properties that can be used in an interpolated manner by defining a restricted TS type for CSS props based on emotions?

When dealing with emotions, how can we specify a restricted TS type for the css prop to only allow certain css properties to be interpolated? For instance, consider the following scenario: // This is considered valid css = {{ color: 'white', ...

Alert: The route "/D:/original/22-02-2017/job2.html" specified in [react-router] does not correspond to any existing routes

I am currently working on a project using TypeScript with React. I'm utilizing webpack as a compiler, and my directory structure looks like this: d:/original/22-02-2017/ - In my web.config.js file, the entry point is set to ./src/index.tsx. All ...

Error message TS2339 in Typescript: The property '__super__' is not found on the type '($element: any, options: any) => any'

Having trouble with Javascript code inside typescript. $.fn.select2.amd.require([ 'select2/data/array', 'select2/utils' ], function (ArrayData, Utils) { /* tslint:disable */ function CustomData ($element, opti ...

Using arrow functions in Typescript e6 allows for the utilization of Array.groupBy

I'm attempting to transform a method into a generic method for use with arrow functions in JavaScript, but I'm struggling to determine the correct way to do so. groupBy: <Map>(predicate: (item: T) => Map[]) => Map[]; Array.prototype ...

Combining type inference validation and authentication middleware in Express routes can be a powerful way to enhance security and ensure

I am struggling to grasp how types are automatically determined in Express routes when utilizing multiple middlewares. To conduct validation using zod, I have employed the middleware package express-zod-safe, although a similar issue arose with alternativ ...

Trigger the Material UI DatePicker to open upon clicking the input field

I have a component that is not receiving the onClick event. I understand that I need to pass a prop with open as a boolean value, but I'm struggling to find a way to trigger it when clicking on MuiDatePicker. Here is an image to show where I want to ...

Module not found

Hey everyone, I recently updated my project to node version v14.18.0, but now I'm encountering a "module not found" issue (see screenshot below). Any suggestions on how to resolve this? https://i.stack.imgur.com/k0u82.png ...

What are the steps to enable full functionality of the strict option in TypeScript?

Despite enforcing strict options, TypeScript is not flagging the absence of defined types for port, req, and res in this code snippet. I am using Vscode and wondering how to fully enforce type checking. import express from 'express'; const app ...