diff --git a/src/main.ts b/src/main.ts index 54e6b28..41fb7ab 100644 --- a/src/main.ts +++ b/src/main.ts @@ -5,6 +5,7 @@ import { SwaggerModule } from '@nestjs/swagger' import { AppModule } from './core/app.module' import { getCorsConfig, swaggerConfig, validationPipe } from './core/config' +import { GrpcExceptionFilter } from './shared/filters' async function bootstrap() { const app = await NestFactory.create(AppModule) @@ -13,6 +14,7 @@ async function bootstrap() { const config = app.get(ConfigService) app.useGlobalPipes(validationPipe) + app.useGlobalFilters(new GrpcExceptionFilter()) app.enableCors(getCorsConfig(config)) diff --git a/src/shared/enums/index.ts b/src/shared/enums/index.ts new file mode 100644 index 0000000..e8161ed --- /dev/null +++ b/src/shared/enums/index.ts @@ -0,0 +1 @@ +export * from './rpc-status.enum' diff --git a/src/shared/enums/rpc-status.enum.ts b/src/shared/enums/rpc-status.enum.ts new file mode 100644 index 0000000..54d2363 --- /dev/null +++ b/src/shared/enums/rpc-status.enum.ts @@ -0,0 +1,19 @@ +export enum RpcStatus { + OK = 0, + CANCELED = 1, + UNKNOWN = 2, + INVALID_ARGUMENTS = 3, + DEADLINE_EXCEEDED = 4, + NOT_FOUND = 5, + ALREADY_EXISTS = 6, + PERMISION_DENIED = 7, + RESOURSE_EXHAUSED = 8, + FAILED_PRECONDITION = 9, + ABORTED = 10, + OUT_OF_RANGE = 11, + UNIMPLEMENTED = 12, + INTERNAL = 13, + UNAVAILABLE = 14, + DATA_LOSS = 15, + UNAUTHENTICATED = 16 +} diff --git a/src/shared/filters/grpc-exception.filter.ts b/src/shared/filters/grpc-exception.filter.ts new file mode 100644 index 0000000..d46c813 --- /dev/null +++ b/src/shared/filters/grpc-exception.filter.ts @@ -0,0 +1,57 @@ +import { + ArgumentsHost, + Catch, + ExceptionFilter, + HttpException, + HttpStatus +} from '@nestjs/common' +import type { Response } from 'express' + +import { grpcToHttpStatus } from '../utils' + +type GrpcException = { + code: number + details: string +} + +@Catch() +export class GrpcExceptionFilter implements ExceptionFilter { + public catch(exception: unknown, host: ArgumentsHost) { + console.log('filter host', host) + console.log('filter exception', exception) + const ctx = host.switchToHttp() + const response = ctx.getResponse() + + if (this.isGrpcException(exception)) { + const status = + grpcToHttpStatus[exception.code] || HttpStatus.INTERNAL_SERVER_ERROR + + return response.status(status).json({ + statusCode: status, + message: exception.details || 'gRPC Error' + }) + } + + if (exception instanceof HttpException) { + const status = exception.getStatus() + return response.status(status).json({ + statusCode: status, + message: exception.message + }) + } + + return response.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ + statusCode: HttpStatus.INTERNAL_SERVER_ERROR, + message: 'Internal Server Error' + }) + } + + private isGrpcException(exception: unknown): exception is GrpcException { + return Boolean( + exception && + typeof exception === 'object' && + 'code' in exception && + 'details' in exception + ) + } +} diff --git a/src/shared/filters/index.ts b/src/shared/filters/index.ts new file mode 100644 index 0000000..991c6f7 --- /dev/null +++ b/src/shared/filters/index.ts @@ -0,0 +1 @@ +export * from './grpc-exception.filter' diff --git a/src/shared/utils/grpc-to-http-status.ts b/src/shared/utils/grpc-to-http-status.ts new file mode 100644 index 0000000..c7fd611 --- /dev/null +++ b/src/shared/utils/grpc-to-http-status.ts @@ -0,0 +1,23 @@ +import { HttpStatus } from '@nestjs/common' + +import { RpcStatus } from '../enums' + +export const grpcToHttpStatus: Record = { + [RpcStatus.OK]: HttpStatus.OK, + [RpcStatus.CANCELED]: 499, + [RpcStatus.UNKNOWN]: HttpStatus.INTERNAL_SERVER_ERROR, + [RpcStatus.INVALID_ARGUMENTS]: HttpStatus.BAD_REQUEST, + [RpcStatus.DEADLINE_EXCEEDED]: HttpStatus.GATEWAY_TIMEOUT, + [RpcStatus.NOT_FOUND]: HttpStatus.NOT_FOUND, + [RpcStatus.ALREADY_EXISTS]: HttpStatus.CONFLICT, + [RpcStatus.PERMISION_DENIED]: HttpStatus.FORBIDDEN, + [RpcStatus.RESOURSE_EXHAUSED]: HttpStatus.TOO_MANY_REQUESTS, + [RpcStatus.FAILED_PRECONDITION]: HttpStatus.BAD_REQUEST, + [RpcStatus.ABORTED]: HttpStatus.CONFLICT, + [RpcStatus.OUT_OF_RANGE]: HttpStatus.BAD_REQUEST, + [RpcStatus.UNIMPLEMENTED]: HttpStatus.NOT_IMPLEMENTED, + [RpcStatus.INTERNAL]: HttpStatus.INTERNAL_SERVER_ERROR, + [RpcStatus.UNAVAILABLE]: HttpStatus.SERVICE_UNAVAILABLE, + [RpcStatus.DATA_LOSS]: HttpStatus.INTERNAL_SERVER_ERROR, + [RpcStatus.UNAUTHENTICATED]: HttpStatus.UNAUTHORIZED +} diff --git a/src/shared/utils/index.ts b/src/shared/utils/index.ts new file mode 100644 index 0000000..74a1a53 --- /dev/null +++ b/src/shared/utils/index.ts @@ -0,0 +1 @@ +export * from './grpc-to-http-status'