diff --git a/packages/core/exceptions/base-exception-filter.ts b/packages/core/exceptions/base-exception-filter.ts index 1966614101b..eeca36c1d29 100644 --- a/packages/core/exceptions/base-exception-filter.ts +++ b/packages/core/exceptions/base-exception-filter.ts @@ -79,10 +79,28 @@ export class BaseExceptionFilter implements ExceptionFilter { } /** - * Checks if the thrown error comes from the "http-errors" library. + * Checks if the thrown error is a FastifyError or comes from the "http-errors" library. * @param err error object */ public isHttpError(err: any): err is { statusCode: number; message: string } { - return err?.statusCode && err?.message; + if (!err || typeof err !== 'object') { + return false; + } + + if ( + err.constructor.name === 'FastifyError' && + typeof err.code === 'string' && + typeof err.statusCode === 'number' + ) { + return true; + } + + // "http-errors" error signature + return ( + typeof err.expose === 'boolean' && + typeof err.statusCode === 'number' && + err.status === err.statusCode && + err instanceof Error + ); } } diff --git a/packages/core/test/exceptions/exceptions-handler.spec.ts b/packages/core/test/exceptions/exceptions-handler.spec.ts index 4472a0b36f1..e8d647e6aff 100644 --- a/packages/core/test/exceptions/exceptions-handler.spec.ts +++ b/packages/core/test/exceptions/exceptions-handler.spec.ts @@ -3,6 +3,7 @@ import { isNil, isObject } from '@nestjs/common/utils/shared.utils'; import { expect } from 'chai'; import * as createHttpError from 'http-errors'; import * as sinon from 'sinon'; +import fastifyErrors from '@fastify/error'; import { AbstractHttpAdapter } from '../../adapters'; import { InvalidExceptionFilterException } from '../../errors/exceptions/invalid-exception-filter.exception'; import { ExceptionsHandler } from '../../exceptions/exceptions-handler'; @@ -57,6 +58,38 @@ describe('ExceptionsHandler', () => { }), ).to.be.true; }); + it('should treat fastify errors as http errors', () => { + const fastifyError = fastifyErrors.createError( + 'FST_ERR_CTP_EMPTY_JSON_BODY', + "Body cannot be empty when content-type is set to 'application/json'", + 400, + )(); + handler.next(fastifyError, new ExecutionContextHost([0, response])); + + expect(statusStub.calledWith(400)).to.be.true; + expect( + jsonStub.calledWith({ + statusCode: 400, + message: + "Body cannot be empty when content-type is set to 'application/json'", + }), + ).to.be.true; + }); + it('should not treat errors from external API calls as errors from "http-errors" library', () => { + const apiCallError = Object.assign( + new Error('Some external API call failed'), + { status: 400 }, + ); + handler.next(apiCallError, new ExecutionContextHost([0, response])); + + expect(statusStub.calledWith(500)).to.be.true; + expect( + jsonStub.calledWith({ + statusCode: 500, + message: 'Internal server error', + }), + ).to.be.true; + }); describe('when exception is instantiated by "http-errors" library', () => { it('should send expected response status code and message', () => { const error = new createHttpError.NotFound('User does not exist');