Reputation: 1
I have a nested form validation on my nestjs application, that returns the following result when a field has some error:
Current error message: "products.0.amount A quantidade do produto é obrigatória"
Expected error message: "A quantidade do produto é obrigatória"
For those examples above, I have a form with an attribute called product
that is an array of objects
The issue is that I don't want to return the property name appended to the message, I want to return a clean message, readable for end users
export class CheckoutForm {
@ValidateNested({ always: true })
@Type(() => ProductForm)
products: ProductForm[];
}
export class ProductForm {
@IsString({ always: true, message: 'O nome do produto é obrigatório' })
name: string;
@Min(1, { always: true, message: 'O preço do produto é obrigatório' })
value: number;
@IsNotEmpty({
always: true,
message: 'A quantidade do produto é obrigatória',
})
amount: number;
}
Upvotes: 0
Views: 10843
Reputation: 1931
If you want to keep the hierarchy of your form mirrored in your error messages (so that maybe your frontend has a better time assigning error messages to their appropriate fields), you can set up your exception factory as follow:
app.useGlobalPipes(
new ValidationPipe({
transform: true,
exceptionFactory: (validationErrors: ValidationError[] = []) => {
const formatError = (error: ValidationError) => {
if (error.children?.length) {
return {
field: error.property,
errors: error.children.map(formatError),
}
}
return {
field: error.property,
errors: Object.values(error.constraints ?? {}),
}
}
return new BadRequestException(
validationErrors.map((error) => formatError(error)),
)
},
}),
)
This way you'd get error messages that look something like:
{
"message": [
{
"field": "products",
"errors": [
{
"field": "0",
"errors": [
{
"field": "amount",
"errors": [
"A quantidade do produto é obrigatória"
]
}
]
}
]
}
],
"error": "Bad Request",
"statusCode": 400
}
Upvotes: 0
Reputation: 41
In addition to Luis Monsalve answer, you can make your code a bit cleaner and more versatile. I just added a helper function that returns an array of all errors.
Defualt validation error from the NestJS ValidationPipe looks like this:
{
"statusCode": 422,
"message": [
"name must be longer than or equal to 3 characters",
"name must contain only letters (a-zA-Z)"
],
"error": "Unprocessable Entity"
}
So we can add the helper function getCustomValidationError to save this format. And we can use getAllConstraints helper function to get all error messages from validators without prefixes like this "products.0.amount".
function getAllConstraints(errors: ValidationError[]): string[] {
const constraints: string[] = [];
for (const error of errors) {
if (error.constraints) {
const constraintValues = Object.values(error.constraints);
constraints.push(...constraintValues);
}
if (error.children) {
const childConstraints = getAllConstraints(error.children);
constraints.push(...childConstraints);
}
}
return constraints;
}
function getCustomValidationError (message: string | string[]) {
return {
statusCode: 422,
message,
error: 'Unprocessable Entity',
}
};
And add useGlobalPipes in main.ts file:
app.useGlobalPipes(new ValidationPipe({
exceptionFactory: (errors: ValidationError[]) => new HttpException(getCustomValidationError(getAllConstraints(errors)), HttpStatus.BAD_REQUEST),
}));
If you are not using a global validation pipe you can do it locally in a controller method, use @UsePipe decorator. Note that this will not work if there is a global pipe, as it will be executed after the controller method pipe.
@Post()
@UsePipes(new ValidationPipe(exceptionFactory...))
yourControllerMethod() {}
Upvotes: 4
Reputation: 160
Since 5.5.0 nestjs have a method to modify the errors on class-validator and class-transformer.
in your main.ts you should have
await app.useGlobalPipes(new ValidationPipe());
this method receives a parameter named "exceptionFactory", which receives a function that can return an exception. if you want to modify and specific error you can do something like this
await app.useGlobalPipes(new ValidationPipe({
exceptionFactory: (errors: ValidationError[]) =>
{
const customError = errors.map((el) =>
{
if(el.constraints.isString)
{
return "O nome do produto é obrigatório"
}
if(el.constraints.isNotEmpty)
{
return "A quantidade do produto é obrigatória"
}
});
return new HttpException(customError, HttpStatus.BAD_REQUEST);
}
}));
notices I have an array of errors this is because I use graphql maybe in your case in REST is different however if you console.log this errors you get an array whit this values
[
ValidationError {
target: SignUpInput {
confirmPassword: '123',
email: '[email protected]',
name: '',
password: '123',
surname: ' '
},
value: '',
property: 'name',
children: [],
constraints: {
minLength: 'name must be longer than or equal to 3 characters',
isAlpha: 'name must contain only letters (a-zA-Z)'
}
}
]
in the constraints elements are the errors of the validations do you have whit this you can modify the errors.
IMPORTANT: "exceptionFactory" should return a valid exeption otherwise it wont work
Upvotes: 1
Reputation: 91
Looks like this is an open issue with class-validator. https://github.com/typestack/class-validator/issues/614#issuecomment-807255434
Seems to be nested validation issues if more than one level, which would explain the issues that you are having.
Upvotes: 0