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

What is the process to enable mandatory validation after a change in input in Angular 4?

Currently, I am working on a project using Angular 4. One of the tasks I need to achieve is validation. <input [(ngModel)]="someModel" required placeholder="some placeholder"/> The validation triggers immediately, but I want it to only trigger aft ...

Oops! A mistake was made by passing an incorrect argument to a color function. Make sure to provide a string representation of a color as the argument next time

Encountering an issue with a button react component utilizing the opacify function from the Polished Library The styling is done using styled-components along with a theme passed through ThemeProvider. Upon testing the code, an error is thrown. Also, the ...

Transforming an array of JSON items into a unified object using Angular

I need to convert an array list into a single object with specific values using TypeScript in Angular 8. Here is the array: "arrayList": [{ "name": "Testname1", "value": "abc" }, { "name": "Testname2", "value": "xyz" } ] The desired ...

When working with TypeScript in Node, the module ""http"" does not have a default export available

const httpModule = require('http'); httpModule.createServer((req, res) => { res.end('Hello World'); }).listen(3000, () => console.log('Server is running on port 3000')); I've installed @types/node but ...

Error: The function 'some' is not recognized in the rawData variable in REACT/ANTDESIGN

I've been grappling with this issue for nearly a full day now. Despite exhausting all possible solutions and conducting extensive searches, I'm still stumped. My task is to create a table using ant design where all the users are displayed upon i ...

What is the best way to upload a file using a relative path in Playwright with TypeScript?

Trying to figure out how to upload a file in a TypeScript test using Playwright. const fileWithPath = './abc.jpg'; const [fileChooser] = await Promise.all([ page.waitForEvent('filechooser'), page.getByRole('button' ...

Encountering a compilation error due to a Typescript assignment

While working with Typescript, I encountered a compilation error in the code shown below: console.log('YHISTORY:login: data = '+data); let theData = JSON.parse(data); console.log('YHISTORY:login: theData = '+JSON.stringify(theData)); ...

Encountering an issue when using npm to add a forked repository as a required package

While attempting to install my fork of a repository, I encountered the error message "Can't install github:<repo>: Missing package name." The original repository can be accessed here, but the specific section I am modifying in my fork is located ...

Posting JSON data with null values in a Spring REST API

In my Spring REST endpoint, I am trying to create a simple hello application. This app should accept a JSON input {"name":"something"} and return "Hello, something". Here is my controller: @RestController public class GreetingController { private s ...

Developing a dynamic web application using Asp.Net Core integrated with React and material

After setting up an Asp.Net Core project using the react template, I decided to incorporate material-ui by following the steps outlined on this page. However, encountered some dependency issues along the way. To resolve them, I had to update the react and ...

Troubleshooting an Angular application in Intellij using Chrome on a Windows operating system

I've been searching for a long time for a way to debug an Angular app in IntelliJ using Chrome on Windows. So far, I have not been successful in attaching a debugger to Chrome. I have tried launching Chrome with --remote-debugging-port=9222 and numer ...

The presence of a constructor in a component disrupts the connection between React and Redux in

I am facing an issue with the connect function from 'react-redux' in my Typescript class example. The error occurs at the last line and I'm struggling to understand why it's happening. The constructor is necessary for other parts of the ...

Is TypeScript failing to enforce generic constraints?

There is an interface defined as: export default interface Cacheable { } and then another one that extends it: import Cacheable from "./cacheable.js"; export default interface Coin extends Cacheable{ id: string; // bitcoin symbol: stri ...

"Send the selected radio button options chosen by the user, with the values specified in a JSON format

My current task involves inserting radio button values into a MySql database using Angular. The form consists of radio buttons with predefined values stored in a json file. Below is an example of how the json file is structured: //data.json [{ "surve ...

Changing the title of your document and app bar in React Router when navigating pages

I'm looking into implementing the react router in a TypeScript project. How can I dynamically update the document title (document.title) and the app bar title (<span className="appbartitle">Home</span>) in a single page application based o ...

React Bootstrap Forms: The <Form.Control.Feedback> element is failing to display when the validation is set to false

Problem: I am facing difficulties with displaying the React Bootstrap <Form.Control.Feedback></Form.Control.Feedback> when the validation is false in my form implementation. Steps to Recreate: Upon clicking the Send Verification Code button, ...

What is the best way to assign the result of a promise to a variable?

My code includes an async function that retrieves a value async fetchUserName(environment: string, itemName: string, authToken: string): Promise<any> { let result = await this.obtainDeviceName(environment, itemName, authToken); return ...

What is the best way to incorporate node_module during the iOS build process in Ionic 2?

Looking to implement an autosize module for automatic resizing of an ion-textarea. Module: Following the installation instructions, I tested it in the browser (ionic serve) and on my iPhone (ionic build ios => run with xcode). Browser: The module wor ...

Sharing information with a service in Ionic and Angular

I need to send data to my service and incorporate it into a URL string. The code snippet below shows how I am obtaining the data in my constructor when the user navigates to the page: constructor(public alertController: AlertController, pri ...

Unable to employ the inequality operator while querying a collection in AngularFire

I'm facing a challenge with pulling a collection from Firebase that is not linked to the user. While I've managed to query the user's collection successfully, I am struggling to retrieve the collection that does not belong to the user using ...