Skip to content

Commit 525f89b

Browse files
committed
feature: add general error response format
1 parent 8b82738 commit 525f89b

File tree

65 files changed

+1325
-176
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+1325
-176
lines changed

generators/auth/templates/mongodb/jwt/src/_main.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,35 @@ import { NestFactory } from '@nestjs/core';
55
import { ValidationPipe, ValidationError } from '@nestjs/common';
66
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
77
import { ConfigService } from '@nestjs/config';
8-
import ValidationExceptions from './exceptions/validation.exceptions';
98

109
import AppModule from './modules/app/app.module';
1110

12-
import AllExceptionsFilter from './filters/all-exceptions.filter';
11+
import ValidationExceptions from './exceptions/validation.exceptions';
12+
13+
import {
14+
BadRequestExceptionFilter,
15+
UnauthorizedExceptionFilter,
16+
ForbiddenExceptionFilter,
17+
ValidationExceptionsFilter,
18+
NotFoundExceptionFilter,
19+
AllExceptionsFilter,
20+
} from './filters';
1321

1422
async function bootstrap() {
1523
const app = await NestFactory.create(AppModule);
1624

1725
app.useGlobalPipes(new ValidationPipe({
1826
exceptionFactory: (errors: ValidationError[]) => new ValidationExceptions(errors),
1927
}));
20-
app.useGlobalFilters(new AllExceptionsFilter());
28+
29+
app.useGlobalFilters(
30+
new AllExceptionsFilter(),
31+
new UnauthorizedExceptionFilter(),
32+
new ForbiddenExceptionFilter(),
33+
new BadRequestExceptionFilter(),
34+
new NotFoundExceptionFilter(),
35+
new ValidationExceptionsFilter(),
36+
);
2137

2238
const configService = app.get(ConfigService);
2339
const port = configService.get<number>('SERVER_POR') || 3000;

generators/auth/templates/mongodb/jwt/src/exceptions/validation.exceptions.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,12 @@ import { BadRequestException, ValidationError } from '@nestjs/common';
22

33
function transform(errors: ValidationError[]) {
44
return errors.map((error) => {
5-
return {
6-
detail: `${error.property} validation error`,
7-
source: { pointer: `data/attributes/${error.property}` },
8-
meta: error.constraints ? Object.values(error.constraints) : null,
9-
};
5+
return Object.values(error.constraints) ?? [];
106
});
117
}
128

139
export default class ValidationExceptions extends BadRequestException {
1410
constructor(public validationErrors: ValidationError[]) {
15-
super({ errorType: 'ValidationError', errors: transform(validationErrors) });
11+
super({ error: 'ValidationError', messages: transform(validationErrors).flat() });
1612
}
1713
}
Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
1+
import { Response as ExpressResponse } from 'express';
12
import {
2-
ArgumentsHost, Catch, ExceptionFilter, HttpStatus,
3+
ArgumentsHost,
4+
Catch,
5+
ExceptionFilter,
6+
HttpStatus,
37
} from '@nestjs/common';
4-
import { Response as ExpressResponse } from 'express';
5-
import { ExceptionResponse } from '@interfaces/exception-response.interface';
68
import { HttpArgumentsHost } from '@nestjs/common/interfaces';
7-
import { Error } from 'jsonapi-serializer';
9+
10+
import { ExceptionResponse } from '@interfaces/exception-response.interface';
811

912
@Catch()
10-
export default class AllExceptionsFilter implements ExceptionFilter {
13+
export class AllExceptionsFilter implements ExceptionFilter {
1114
catch(exception: any, host: ArgumentsHost) {
1215
const ctx: HttpArgumentsHost = host.switchToHttp();
1316
const res = ctx.getResponse<ExpressResponse>();
@@ -21,24 +24,25 @@ export default class AllExceptionsFilter implements ExceptionFilter {
2124
};
2225

2326
if (exception.code === mongodbCodes.bulkWriteError) {
24-
return res.status(HttpStatus.CONFLICT).json(
25-
new Error({
26-
source: { pointer: '/data/attributes/email' },
27-
title: 'Duplicate key',
28-
detail: exception.message,
29-
}),
30-
);
27+
return res.status(HttpStatus.CONFLICT).json({
28+
error: 'Duplicate key',
29+
message: exception.message,
30+
});
3131
}
3232

33-
if (exception.response?.errorType === 'ValidationError') {
34-
return res.status(HttpStatus.BAD_REQUEST).json(
35-
new Error(exceptionResponse ? exceptionResponse.errors : {}),
36-
);
33+
const errorBody = {
34+
error: exception.name,
35+
message: exception.message,
36+
};
37+
38+
if (exceptionResponse) {
39+
if (Array.isArray(exceptionResponse.message)) {
40+
Reflect.set(errorBody, 'messages', exceptionResponse.message);
41+
} else {
42+
Reflect.set(errorBody, 'message', exceptionResponse.message);
43+
}
3744
}
3845

39-
return res.status(status).json(new Error({
40-
title: exceptionResponse?.error,
41-
detail: exception?.message,
42-
}));
46+
return res.status(status).json(errorBody);
4347
}
4448
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { Response as ExpressResponse } from 'express';
2+
import {
3+
ArgumentsHost,
4+
Catch,
5+
ExceptionFilter,
6+
HttpStatus,
7+
BadRequestException,
8+
} from '@nestjs/common';
9+
import { HttpArgumentsHost } from '@nestjs/common/interfaces';
10+
11+
import { ExceptionResponse } from '@interfaces/exception-response.interface';
12+
13+
@Catch(BadRequestException)
14+
export class BadRequestExceptionFilter implements ExceptionFilter {
15+
catch(exception: any, host: ArgumentsHost) {
16+
const ctx: HttpArgumentsHost = host.switchToHttp();
17+
const res = ctx.getResponse<ExpressResponse>();
18+
19+
const exceptionResponse: ExceptionResponse = exception.getResponse() as ExceptionResponse;
20+
21+
const errorBody = {
22+
error: exception.name,
23+
};
24+
25+
if (Array.isArray(exceptionResponse.message)) {
26+
Reflect.set(errorBody, 'messages', exceptionResponse.message);
27+
} else {
28+
Reflect.set(errorBody, 'message', exceptionResponse.message);
29+
}
30+
31+
return res.status(HttpStatus.BAD_REQUEST).json(errorBody);
32+
}
33+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { Response as ExpressResponse } from 'express';
2+
import {
3+
ArgumentsHost,
4+
Catch,
5+
ExceptionFilter,
6+
HttpStatus,
7+
ForbiddenException,
8+
} from '@nestjs/common';
9+
import { HttpArgumentsHost } from '@nestjs/common/interfaces';
10+
11+
import { ExceptionResponse } from '@interfaces/exception-response.interface';
12+
13+
@Catch(ForbiddenException)
14+
export class ForbiddenExceptionFilter implements ExceptionFilter {
15+
catch(exception: any, host: ArgumentsHost) {
16+
const ctx: HttpArgumentsHost = host.switchToHttp();
17+
const res = ctx.getResponse<ExpressResponse>();
18+
19+
const exceptionResponse: ExceptionResponse = exception.getResponse() as ExceptionResponse;
20+
21+
const errorBody = {
22+
error: exception.name,
23+
};
24+
25+
if (Array.isArray(exceptionResponse.message)) {
26+
Reflect.set(errorBody, 'messages', exceptionResponse.message);
27+
} else {
28+
Reflect.set(errorBody, 'message', exceptionResponse.message);
29+
}
30+
31+
return res.status(HttpStatus.FORBIDDEN).json(errorBody);
32+
}
33+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export * from './all-exceptions.filter';
2+
export * from './bad-request-exception.filter';
3+
export * from './forbidden-exception.filter';
4+
export * from './not-found-exception.filter';
5+
export * from './unauthorized-exception.filter';
6+
export * from './validation-exceptions.filter';
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { Response as ExpressResponse } from 'express';
2+
import {
3+
ArgumentsHost,
4+
Catch,
5+
ExceptionFilter,
6+
HttpStatus,
7+
NotFoundException,
8+
} from '@nestjs/common';
9+
import { HttpArgumentsHost } from '@nestjs/common/interfaces';
10+
11+
import { ExceptionResponse } from '@interfaces/exception-response.interface';
12+
13+
@Catch(NotFoundException)
14+
export class NotFoundExceptionFilter implements ExceptionFilter {
15+
catch(exception: any, host: ArgumentsHost) {
16+
const ctx: HttpArgumentsHost = host.switchToHttp();
17+
const res = ctx.getResponse<ExpressResponse>();
18+
19+
const exceptionResponse: ExceptionResponse = exception.getResponse() as ExceptionResponse;
20+
21+
const errorBody = {
22+
error: exception.name,
23+
};
24+
25+
if (Array.isArray(exceptionResponse.message)) {
26+
Reflect.set(errorBody, 'messages', exceptionResponse.message);
27+
} else {
28+
Reflect.set(errorBody, 'message', exceptionResponse.message);
29+
}
30+
31+
return res.status(HttpStatus.NOT_FOUND).json(errorBody);
32+
}
33+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { Response as ExpressResponse } from 'express';
2+
import {
3+
ArgumentsHost,
4+
Catch,
5+
ExceptionFilter,
6+
HttpStatus,
7+
UnauthorizedException,
8+
} from '@nestjs/common';
9+
import { HttpArgumentsHost } from '@nestjs/common/interfaces';
10+
11+
import { ExceptionResponse } from '@interfaces/exception-response.interface';
12+
13+
@Catch(UnauthorizedException)
14+
export class UnauthorizedExceptionFilter implements ExceptionFilter {
15+
catch(exception: any, host: ArgumentsHost) {
16+
const ctx: HttpArgumentsHost = host.switchToHttp();
17+
const res = ctx.getResponse<ExpressResponse>();
18+
19+
const exceptionResponse: ExceptionResponse = exception.getResponse() as ExceptionResponse;
20+
21+
const errorBody = {
22+
error: exception.name,
23+
};
24+
25+
if (Array.isArray(exceptionResponse.message)) {
26+
Reflect.set(errorBody, 'messages', exceptionResponse.message);
27+
} else {
28+
Reflect.set(errorBody, 'message', exceptionResponse.message);
29+
}
30+
31+
return res.status(HttpStatus.UNAUTHORIZED).json(errorBody);
32+
}
33+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { Response as ExpressResponse } from 'express';
2+
import {
3+
ArgumentsHost,
4+
Catch,
5+
ExceptionFilter,
6+
HttpStatus,
7+
} from '@nestjs/common';
8+
import { HttpArgumentsHost } from '@nestjs/common/interfaces';
9+
10+
import { ExceptionResponse } from '@interfaces/exception-response.interface';
11+
import ValidationExceptions from '@exceptions/validation.exceptions';
12+
13+
@Catch(ValidationExceptions)
14+
export class ValidationExceptionsFilter implements ExceptionFilter {
15+
catch(exception: any, host: ArgumentsHost) {
16+
const ctx: HttpArgumentsHost = host.switchToHttp();
17+
const res = ctx.getResponse<ExpressResponse>();
18+
19+
const exceptionResponse: ExceptionResponse = exception.getResponse() as ExceptionResponse;
20+
21+
return res.status(HttpStatus.BAD_REQUEST).json({
22+
error: exception.name,
23+
messages: exceptionResponse.messages,
24+
});
25+
}
26+
}
Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
1-
import { JSONAPIErrorOptions } from 'jsonapi-serializer';
2-
31
export interface ExceptionResponse {
4-
readonly message: unknown;
5-
62
readonly statusCode: number;
73

84
readonly error: string;
95

10-
readonly errorType: string;
6+
readonly message: unknown;
117

12-
readonly errors: JSONAPIErrorOptions[];
8+
readonly messages: unknown[];
139
}

0 commit comments

Comments
 (0)