Merge pull request #14753 from glebbash/fix/stricter-is-http-error-check

fix(core): make `isHttpError` check stricter
This commit is contained in:
Kamil Mysliwiec
2026-02-17 17:19:23 +01:00
committed by GitHub
2 changed files with 77 additions and 2 deletions

View File

@@ -79,10 +79,28 @@ export class BaseExceptionFilter<T = any> implements ExceptionFilter<T> {
}
/**
* 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
);
}
}

View File

@@ -6,6 +6,7 @@ import { InvalidExceptionFilterException } from '../../errors/exceptions/invalid
import { ExceptionsHandler } from '../../exceptions/exceptions-handler.js';
import { ExecutionContextHost } from '../../helpers/execution-context-host.js';
import { NoopHttpAdapter } from '../utils/noop-adapter.js';
import fastifyErrors from '@fastify/error';
describe('ExceptionsHandler', () => {
let adapter: AbstractHttpAdapter;
@@ -53,6 +54,62 @@ describe('ExceptionsHandler', () => {
message: 'Internal server error',
});
});
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).toHaveBeenCalledWith(400);
expect(jsonStub).toHaveBeenCalledWith({
statusCode: 400,
message:
"Body cannot be empty when content-type is set to 'application/json'",
});
});
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).toHaveBeenCalledWith(500);
expect(jsonStub).toHaveBeenCalledWith({
statusCode: 500,
message: 'Internal server error',
});
});
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).toHaveBeenCalledWith(400);
expect(jsonStub).toHaveBeenCalledWith({
statusCode: 400,
message:
"Body cannot be empty when content-type is set to 'application/json'",
});
});
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).toHaveBeenCalledWith(500);
expect(jsonStub).toHaveBeenCalledWith({
statusCode: 500,
message: 'Internal server error',
});
});
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');