Nestjs class-validator exception - how to throw metadata info for @IsNotIn validator
Asked Answered
C

1

6

I have a NestJs dto that looks like this

import { IsEmail, IsNotEmpty, IsNotIn } from 'class-validator';
import { AppService } from './app.service';
const restrictedNames = ['Name Inc', 'Acme Inc'];
class DTO {
  @IsNotEmpty()
  name: string;
  @IsEmail()
  email: string;

  @IsNotEmpty()
  @IsNotIn(restrictedNames)
  orgName: string;
}

I am using an exception filter that returns errors with clear details on what validation failed and for which field.

app.useGlobalPipes(
new ValidationPipe({
  exceptionFactory: (validationErrors: ValidationError[] = []) => {
    console.log(validationErrors);
    return new BadRequestException({
      statusCode: HttpStatus.BAD_REQUEST,
      message: validationErrors.reduce((acc, error) => {
        acc[error.property] = Object.keys(error.constraints).map(
          (failed) => ({
            failedValidation: failed,
            message: error.constraints[failed],
          }),
        );
        return acc;
      }, {}),
      error: 'validation',
    });
  },
}),

);

which returns an error something like this

{"statusCode":400,"message":{"email":[{"failedValidation":"isEmail","message":"email must be an email"}],"orgName":[{"failedValidation":"isNotIn","message":"orgName should not be one of the following values: Name Inc, Acme Inc"}]},"error":"validation"}

But for the failed validation such as @NotIn, i would want the error to be more specific in terms of what are the reserved keywords, and want them returned in the error as a separate key like:

{"statusCode":400,"message":{"email":[{"failedValidation":"isEmail","message":"email must be an email"}],"orgName":[{"failedValidation":"isNotIn","message":"orgName should not be one of the following values: Name Inc, Acme Inc", "data":{"reservedKeywords":["Name Inc","Acme Inc"]}}]},"error":"validation"}

But this block from the exception Filter doesnt return the constraints values with the decorator metadata.

message: validationErrors.reduce((acc, error) => {
        acc[error.property] = Object.keys(error.constraints).map(
          (failed) => ({
            failedValidation: failed,
            message: error.constraints[failed],
          }),
        );
        return acc;
      }, {}),
      error: 'validation',
    });
Conker answered 22/5, 2023 at 5:47 Comment(0)
R
0

As per my knowledge, there is no concept of returning metadata or whatsoever automatically. Since you are storing the blacklisted values in the variables. You can provide it as context, which is mapped to particular error types.

const restrictedNames = ['Name Inc', 'Acme Inc'] as const;
class DTO {
  @IsNotEmpty()
  name: string;
  @IsEmail()
  email: string;

  @IsNotEmpty()
  @IsNotIn(restrictedNames, { context: { reservedKeywords: restrictedNames } }) // here
  orgName: string;

  // You may ignore the contructor
  constructor(name: string, email: string, orgName: string) {
    this.name = name;
    this.email = email;
    this.orgName = orgName;
  }
}

Now all you need is to include the value conditionally in your transform function using error.contexts

const transformedMessage = errors.reduce((acc, error) => {
  acc[error.property] = Object.keys(error.constraints).map((failed) => ({
    failedValidation: failed,
    message: error.constraints[failed],
    data: error.contexts && error.contexts[failed],
  }));
  return acc;
}, {});

Like you want, here is the output.

{
  "email": [
    {
      "failedValidation": "isEmail",
      "message": "email must be an email"
    }
  ],
  "orgName": [
    {
      "failedValidation": "isNotIn",
      "message": "orgName should not be one of the following values: Name Inc, Acme Inc",
      "data": {
        "reservedKeywords": [
          "Name Inc",
          "Acme Inc"
        ]
      }
    }
  ]
}

PS: Thanks for providing the transform function. I am stealing it shamefully.

Radtke answered 22/3 at 18:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.