Skip to content

Commit 0e661c8

Browse files
Merge pull request nestjs#3053 from nestjs/refactor/http-message-property
refactor(core) make message property (http errors) serializable
2 parents 08960e8 + d3c2251 commit 0e661c8

5 files changed

Lines changed: 50 additions & 67 deletions

File tree

integration/graphql-code-first/e2e/pipes.spec.ts

Lines changed: 2 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -32,24 +32,7 @@ describe('GraphQL Pipes', () => {
3232
extensions: {
3333
code: 'INTERNAL_SERVER_ERROR',
3434
exception: {
35-
message: {
36-
error: 'Bad Request',
37-
message: [
38-
{
39-
children: [],
40-
constraints: {
41-
length:
42-
'description must be longer than or equal to 30 characters',
43-
},
44-
property: 'description',
45-
target: {
46-
ingredients: [],
47-
title: 'test',
48-
},
49-
},
50-
],
51-
statusCode: 400,
52-
},
35+
message: 'Bad Request Exception',
5336
response: {
5437
error: 'Bad Request',
5538
message: [
@@ -77,24 +60,7 @@ describe('GraphQL Pipes', () => {
7760
line: 2,
7861
},
7962
],
80-
message: {
81-
error: 'Bad Request',
82-
message: [
83-
{
84-
children: [],
85-
constraints: {
86-
length:
87-
'description must be longer than or equal to 30 characters',
88-
},
89-
property: 'description',
90-
target: {
91-
ingredients: [],
92-
title: 'test',
93-
},
94-
},
95-
],
96-
statusCode: 400,
97-
},
63+
message: 'Bad Request Exception',
9864
path: ['addRecipe'],
9965
},
10066
],

integration/hello-world/e2e/fastify-adapter.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ describe('Hello world (fastify adapter)', () => {
5959
fail(`Unexpected success: ${payload}`);
6060
},
6161
err => {
62-
expect(err.message).to.be.eql({
62+
expect(err.getResponse()).to.be.eql({
6363
error: 'Internal Server Error',
6464
message:
6565
'HTTP adapter does not support filtering on host: ":tenant.example.com"',

packages/common/exceptions/http.exception.ts

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import { isObject, isString } from '../utils/shared.utils';
99
* @publicApi
1010
*/
1111
export class HttpException extends Error {
12-
public readonly message: any;
1312
/**
1413
* Instantiate a plain HTTP Exception.
1514
*
@@ -36,11 +35,26 @@ export class HttpException extends Error {
3635
* @param status HTTP response status code
3736
*/
3837
constructor(
39-
private readonly response: string | object,
38+
private readonly response: string | Record<string, any>,
4039
private readonly status: number,
4140
) {
4241
super();
43-
this.message = response;
42+
this.initMessage();
43+
}
44+
45+
public initMessage() {
46+
if (isString(this.response)) {
47+
this.message = this.response;
48+
} else if (
49+
isObject(this.response) &&
50+
isString((this.response as Record<string, any>).message)
51+
) {
52+
this.message = (this.response as Record<string, any>).message;
53+
} else if (this.constructor) {
54+
this.message = this.constructor.name
55+
.match(/[A-Z][a-z]+|[0-9]+/g)
56+
.join(' ');
57+
}
4458
}
4559

4660
public getResponse(): string | object {
@@ -51,15 +65,6 @@ export class HttpException extends Error {
5165
return this.status;
5266
}
5367

54-
public toString(): string {
55-
const message = this.getErrorString(this.message);
56-
return `Error: ${message}`;
57-
}
58-
59-
private getErrorString(target: string | object): string {
60-
return isString(target) ? target : JSON.stringify(target);
61-
}
62-
6368
public static createBody(
6469
message: object | string,
6570
error?: string,

packages/common/test/exceptions/http.exception.spec.ts

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,33 +6,33 @@ import {
66
} from '../../exceptions';
77

88
describe('HttpException', () => {
9-
it('should return a message as a string when input is a string', () => {
9+
it('should return a response as a string when input is a string', () => {
1010
const message = 'My error message';
11-
expect(new HttpException(message, 404).message).to.be.eql(
11+
expect(new HttpException(message, 404).getResponse()).to.be.eql(
1212
'My error message',
1313
);
1414
});
1515

16-
it('should return a message as an object when input is an object', () => {
17-
const message: object = {
16+
it('should return a response as an object when input is an object', () => {
17+
const message = {
1818
msg: 'My error message',
1919
reason: 'this can be a human readable reason',
2020
anything: 'else',
2121
};
22-
expect(new HttpException(message, 404).message).to.be.eql(message);
22+
expect(new HttpException(message, 404).getResponse()).to.be.eql(message);
2323
});
2424

2525
it('should return a message from a built-in exception as an object', () => {
2626
const message = 'My error message';
27-
expect(new BadRequestException(message).message).to.be.eql({
27+
expect(new BadRequestException(message).getResponse()).to.be.eql({
2828
statusCode: 400,
2929
error: 'Bad Request',
3030
message: 'My error message',
3131
});
3232
});
3333

3434
it('should return an object even when the message is undefined', () => {
35-
expect(new BadRequestException().message).to.be.eql({
35+
expect(new BadRequestException().getResponse()).to.be.eql({
3636
statusCode: 400,
3737
error: 'Bad Request',
3838
});
@@ -65,17 +65,24 @@ describe('HttpException', () => {
6565
expect(`${error}`).to.be.eql(`Error: ${message}`);
6666
});
6767

68-
describe('when "message" is an object', () => {
69-
it('should serialize an object', () => {
68+
describe('when "response" is an object', () => {
69+
it('should use default message', () => {
7070
const obj = { foo: 'bar' };
7171
const error = new HttpException(obj, 400);
72-
expect(`${error}`).to.be.eql(`Error: ${JSON.stringify(obj)}`);
72+
const badRequestError = new BadRequestException(obj);
73+
74+
expect(`${error}`).to.be.eql(`Error: Http Exception`);
75+
expect(`${badRequestError}`).to.be.eql(`Error: Bad Request Exception`);
7376
expect(`${error}`.includes('[object Object]')).to.not.be.true;
77+
expect(`${badRequestError}`.includes('[object Object]')).to.not.be.true;
7478
});
75-
76-
it('should serialize sub errors', () => {
77-
const error = new NotFoundException();
78-
expect(`${error}`.includes('Not Found')).to.be.true;
79+
describe('otherwise', () => {
80+
it('should concat strings', () => {
81+
const test = 'test message';
82+
const error = new HttpException(test, 400);
83+
expect(`${error}`).to.be.eql(`Error: ${test}`);
84+
expect(`${error}`.includes('[object Object]')).to.not.be.true;
85+
});
7986
});
8087
});
8188

packages/core/test/router/router-execution-context.spec.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { ForbiddenException } from '@nestjs/common/exceptions/forbidden.exception';
22
import { expect } from 'chai';
33
import * as sinon from 'sinon';
4-
import { HttpStatus, RouteParamMetadata } from '../../../common';
4+
import { HttpException, HttpStatus, RouteParamMetadata } from '../../../common';
55
import { CUSTOM_ROUTE_AGRS_METADATA } from '../../../common/constants';
66
import { RouteParamtypes } from '../../../common/enums/route-paramtypes.enum';
77
import { AbstractHttpAdapter } from '../../adapters';
@@ -137,14 +137,16 @@ describe('RouterExecutionContext', () => {
137137
});
138138
it('should throw exception when "tryActivate" returns false', async () => {
139139
tryActivateStub.callsFake(async () => false);
140-
let error: Error;
140+
141+
let error: HttpException;
141142
try {
142143
await proxyContext(request, response, next);
143144
} catch (e) {
144145
error = e;
145146
}
146147
expect(error).to.be.instanceOf(ForbiddenException);
147-
expect(error.message).to.be.eql({
148+
expect(error.message).to.be.eql('Forbidden resource');
149+
expect(error.getResponse()).to.be.eql({
148150
statusCode: HttpStatus.FORBIDDEN,
149151
error: 'Forbidden',
150152
message: FORBIDDEN_MESSAGE,
@@ -297,14 +299,17 @@ describe('RouterExecutionContext', () => {
297299
it('should throw ForbiddenException when "tryActivate" returns false', async () => {
298300
const guardsFn = contextCreator.createGuardsFn([null], null, null);
299301
sinon.stub(guardsConsumer, 'tryActivate').callsFake(async () => false);
302+
300303
let error: ForbiddenException;
301304
try {
302305
await guardsFn([]);
303306
} catch (e) {
304307
error = e;
305308
}
309+
306310
expect(error).to.be.instanceOf(ForbiddenException);
307-
expect(error.message).to.be.eql({
311+
expect(error.message).to.be.eql('Forbidden resource');
312+
expect(error.getResponse()).to.be.eql({
308313
statusCode: HttpStatus.FORBIDDEN,
309314
error: 'Forbidden',
310315
message: FORBIDDEN_MESSAGE,

0 commit comments

Comments
 (0)