node.js - El filtro de excepciones de NestJS estropea el arreglo de errores si proviene de ValidationPipe
Así que uso el ValidationPipe para validar mis DTOs en NestJS, así:
// auth.dto.ts
export class AuthDto {
@IsEmail()
@IsNotEmpty()
email: string;
}
Sin el filtro de Excepción, el mensaje de error funciona como se espera. Dejo el campo de correo electrónico vacío y recibo un arreglo de mensajes de error:
// Response - Message array, but no wrapper
{
"statusCode": 400,
"message": [
"email should not be empty",
"email must be an email"
],
"error": "Bad Request"
}
Perfecto. Ahora quiero implementar un envoltorio para los mensajes de error, así que creo un nuevo filtro y lo agrego al bootstrap:
// main.ts
async function bootstrap() {
// ...
app.useGlobalFilters(new GlobalExceptionFilter());
}
bootstrap();
// global-exception.filter.ts
import {
ArgumentsHost,
Catch,
ExceptionFilter,
HttpException,
HttpStatus,
} from '@nestjs/common';
import { Response } from 'express';
import { IncomingMessage } from 'http';
export const getStatusCode = <T>(exception: T): number => {
return exception instanceof HttpException
? exception.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR;
};
export const getErrorMessage = <T>(exception: T): string => {
return exception instanceof HttpException
? exception.message
: String(exception);
};
@Catch()
export class GlobalExceptionFilter<T> implements ExceptionFilter {
catch(exception: T, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<IncomingMessage>();
const statusCode = getStatusCode<T>(exception);
const message = getErrorMessage<T>(exception);
response.status(statusCode).json({
error: {
timestamp: new Date().toISOString(),
path: request.url,
statusCode,
message,
},
});
}
}
Funciona muy bien para la mayoría de mis errores:
// Response - Good format (wrapped), single message expected
{
"error": {
"timestamp": "2022-05-11T19:54:59.093Z",
"path": "/auth/signup",
"statusCode": 400,
"message": "Email already in use"
}
}
Pero cuando obtengo un ValidationError del ValidationPipe, debería darme un arreglo de mensajes como antes, pero en su lugar da este mensaje:
// Response - Wrapper: check, single message instead of array
{
"error": {
"timestamp": "2022-05-11T19:59:17.282Z",
"path": "/auth/signup",
"statusCode": 400,
"message": "Bad Request Exception" // it should be "message": ["not empty", "must be email"]
}
}
El objeto de excepción en mi filtro de excepciones tiene un campo de respuesta que contiene el arreglo de mensajes:
// HttpException object inside the filter class
{
response: {
statusCode: 400,
message: [ 'email should not be empty', 'email must be an email' ],
error: 'Bad Request'
},
status: 400
}
Pero exception.response.message no funciona, porque el campo es privado y TypeScript lanza un error:Property 'response' is private y solo accesible dentro de la clase 'HttpException'.
¿Alguno de ustedes sabe cómo podría acceder al arreglo de mensajes, para poder formatear correctamente mi respuesta de error?
EDITAR: ¡Perdón por el largo mensaje!
Respuesta #1
Como comentó @tobias-s, hay una solución alternativa, la cual resolvió el problema:
Intenta exception["response"]["message"]. Esto evita la restricción privada
Respuesta #2
Dado que estás usando el decorador @Catch, podrías obtener un HttpException o no, así que necesitamos evaluarlo.
Vamos a crear una interfaz para "analizar" la respuesta de la clase incorporada HttpException de Nest.js:
export interface HttpExceptionResponse {
statusCode: number;
message: any;
error: string;
}
Ahora podemos procesarlo:
export const getErrorMessage = <T>(exception: T): any => {
if(exception instanceof HttpException) {
const errorResponse = exception.getResponse();
const errorMessage = (errorResponse as HttpExceptionResponse).message || exception.message;
return errorMessage;
} else {
return String(exception);
}
};
exception.getResponse() puede ser una cadena u un objeto, eso es porque lo manejamos como message: any por supuesto.
