mirror of
https://github.com/nestjs/nest.git
synced 2026-10-28 09:45:43 +08:00
fix(express): map http-errors errors to http exceptions (#18214)
http-errors errors raised before the handler, such as a body that is too large, now reach exception filters as an HttpException with their status, as on Fastify. The message of an error with expose: false is replaced by a status text. Fixes #18213
This commit is contained in:
1 parent
68d3a8f835
commit
7ea87653af
2 files changed
+55
No files matched your search
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
BadRequestException,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
InternalServerErrorException,
|
||||
Logger,
|
||||
@@ -675,6 +676,20 @@ export class ExpressAdapter extends AbstractHttpAdapter<
|
||||
// encoding, e.g. '%FF' (#8915)
|
||||
case error instanceof SyntaxError || error instanceof URIError:
|
||||
return new BadRequestException(error.message);
|
||||
// http-errors (used by body-parser, Express itself, etc.) carry a numeric statusCode
|
||||
// and an expose flag. Without this case, errors like PayloadTooLargeError reach custom
|
||||
// exception filters as a raw http-errors instance rather than an HttpException, so a
|
||||
// @Catch(HttpException) filter never runs and a catch-all filter reports 500 (#18213).
|
||||
case isHttpError(error):
|
||||
return new HttpException(
|
||||
// An error that must not reach the client only gets a status text,
|
||||
// like the built-in exception filter answers it
|
||||
error.expose === false
|
||||
? (http.STATUS_CODES[error.statusCode] ?? 'Internal server error')
|
||||
: error.message,
|
||||
error.statusCode,
|
||||
{ cause: error },
|
||||
);
|
||||
default:
|
||||
return error;
|
||||
}
|
||||
@@ -734,3 +749,19 @@ export class ExpressAdapter extends AbstractHttpAdapter<
|
||||
this.setHeaderIfNotExists(response, 'Content-Length', headers.length);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true when `err` is an "http-errors"-shaped error (body-parser, Express, etc.).
|
||||
* Mirrors the same check in BaseExceptionFilter.isHttpError().
|
||||
*/
|
||||
function isHttpError(
|
||||
err: unknown,
|
||||
): err is { statusCode: number; message: string; expose: boolean } {
|
||||
return (
|
||||
isObject(err) &&
|
||||
err instanceof Error &&
|
||||
typeof (err as any).expose === 'boolean' &&
|
||||
typeof (err as any).statusCode === 'number' &&
|
||||
(err as any).status === (err as any).statusCode
|
||||
);
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
import {
|
||||
BadRequestException,
|
||||
HttpException,
|
||||
RequestMethod,
|
||||
StreamableFile,
|
||||
UnauthorizedException,
|
||||
} from '@nestjs/common';
|
||||
import { ExpressAdapter } from '@nestjs/platform-express';
|
||||
import express from 'express';
|
||||
import createHttpError from 'http-errors';
|
||||
import { PassThrough, Readable, Writable } from 'stream';
|
||||
|
||||
describe('ExpressAdapter', () => {
|
||||
@@ -528,5 +530,27 @@ describe('ExpressAdapter', () => {
|
||||
const result = expressAdapter.mapException(error);
|
||||
expect(result).toBe(error);
|
||||
});
|
||||
|
||||
it('should map an http-errors error to an HttpException with its status', () => {
|
||||
const error = createHttpError(413, 'request entity too large');
|
||||
const result = expressAdapter.mapException(error) as HttpException;
|
||||
expect(result).toBeInstanceOf(HttpException);
|
||||
expect(result.getStatus()).toBe(413);
|
||||
expect(result.getResponse()).toBe('request entity too large');
|
||||
expect(result.cause).toBe(error);
|
||||
});
|
||||
|
||||
it.each([
|
||||
[500, 'Internal Server Error'],
|
||||
[599, 'Internal server error'],
|
||||
])(
|
||||
'should not expose the message of an http-errors error with status %i',
|
||||
(status, message) => {
|
||||
const error = createHttpError(status, 'internal detail');
|
||||
const result = expressAdapter.mapException(error) as HttpException;
|
||||
expect(result.getStatus()).toBe(status);
|
||||
expect(result.getResponse()).toBe(message);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user