Skip to content

Commit eae5684

Browse files
authored
Merge pull request mohsenbostan#11 from youngkiu/feature/refactor-HttpToGrpcInterceptor
refactor: Generalize HttpToGrpcInterceptor with status code map.
2 parents ddb0a14 + da2df3a commit eae5684

File tree

5 files changed

+110
-30
lines changed

5 files changed

+110
-30
lines changed

lib/interceptors/http-to-grpc.interceptor.ts

Lines changed: 12 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,14 @@
11
import {
22
CallHandler,
33
ExecutionContext,
4-
HttpStatus,
54
Injectable,
65
NestInterceptor,
76
} from "@nestjs/common";
87
import { Observable, throwError } from "rxjs";
98
import { catchError } from "rxjs/operators";
10-
import {
11-
GrpcAbortedException,
12-
GrpcAlreadyExistsException,
13-
GrpcInternalException,
14-
GrpcInvalidArgumentException,
15-
GrpcNotFoundException,
16-
GrpcPermissionDeniedException,
17-
GrpcResourceExhaustedException,
18-
GrpcUnauthenticatedException,
19-
GrpcUnknownException,
20-
} from "../exceptions";
21-
22-
const GRPC_EXCEPTION_FROM_HTTP: Record<number, any> = {
23-
[HttpStatus.NOT_FOUND]: GrpcNotFoundException,
24-
[HttpStatus.FORBIDDEN]: GrpcPermissionDeniedException,
25-
[HttpStatus.METHOD_NOT_ALLOWED]: GrpcAbortedException,
26-
[HttpStatus.INTERNAL_SERVER_ERROR]: GrpcInternalException,
27-
[HttpStatus.TOO_MANY_REQUESTS]: GrpcResourceExhaustedException,
28-
[HttpStatus.BAD_GATEWAY]: GrpcUnknownException,
29-
[HttpStatus.CONFLICT]: GrpcAlreadyExistsException,
30-
[HttpStatus.UNPROCESSABLE_ENTITY]: GrpcInvalidArgumentException,
31-
[HttpStatus.UNAUTHORIZED]: GrpcUnauthenticatedException,
32-
};
9+
import { GRPC_CODE_FROM_HTTP } from "../utils";
10+
import { status as Status } from "@grpc/grpc-js";
11+
import { RpcException } from "@nestjs/microservices";
3312

3413
@Injectable()
3514
export class HttpToGrpcInterceptor implements NestInterceptor {
@@ -55,11 +34,16 @@ export class HttpToGrpcInterceptor implements NestInterceptor {
5534
message: string;
5635
};
5736

58-
if (!(exception.statusCode in GRPC_EXCEPTION_FROM_HTTP))
59-
return throwError(() => err);
37+
const statusCode =
38+
GRPC_CODE_FROM_HTTP[exception.statusCode] || Status.INTERNAL;
6039

61-
const Exception = GRPC_EXCEPTION_FROM_HTTP[exception.statusCode];
62-
return throwError(() => new Exception(exception.message));
40+
return throwError(
41+
() =>
42+
new RpcException({
43+
message: exception.message,
44+
code: statusCode,
45+
}),
46+
);
6347
}),
6448
);
6549
}

lib/utils/grpc-codes-map.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { status as Status } from "@grpc/grpc-js";
2+
import { HttpStatus } from "@nestjs/common";
3+
4+
// https://github.com/nestjs/nest/blob/master/packages/common/enums/http-status.enum.ts
5+
export const GRPC_CODE_FROM_HTTP: Record<number, number> = {
6+
[HttpStatus.OK]: Status.OK,
7+
[HttpStatus.BAD_GATEWAY]: Status.UNKNOWN,
8+
[HttpStatus.UNPROCESSABLE_ENTITY]: Status.INVALID_ARGUMENT,
9+
[HttpStatus.REQUEST_TIMEOUT]: Status.DEADLINE_EXCEEDED,
10+
[HttpStatus.NOT_FOUND]: Status.NOT_FOUND,
11+
[HttpStatus.CONFLICT]: Status.ALREADY_EXISTS,
12+
[HttpStatus.FORBIDDEN]: Status.PERMISSION_DENIED,
13+
[HttpStatus.TOO_MANY_REQUESTS]: Status.RESOURCE_EXHAUSTED,
14+
[HttpStatus.PRECONDITION_REQUIRED]: Status.FAILED_PRECONDITION,
15+
[HttpStatus.METHOD_NOT_ALLOWED]: Status.ABORTED,
16+
[HttpStatus.PAYLOAD_TOO_LARGE]: Status.OUT_OF_RANGE,
17+
[HttpStatus.NOT_IMPLEMENTED]: Status.UNIMPLEMENTED,
18+
[HttpStatus.INTERNAL_SERVER_ERROR]: Status.INTERNAL,
19+
[HttpStatus.UNAUTHORIZED]: Status.UNAUTHENTICATED,
20+
};

lib/utils/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from "./error-object";
22
export * from "./http-codes-map";
3+
export * from "./grpc-codes-map";

test/interceptors/grpc-to-http-interceptor.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { HttpException } from "@nestjs/common";
1+
import { HttpException, HttpStatus } from "@nestjs/common";
22
import { Observable, throwError } from "rxjs";
33
import { GrpcNotFoundException, GrpcToHttpInterceptor } from "../../lib";
44

@@ -48,7 +48,7 @@ describe("GrpcToHttpInterceptor", () => {
4848
intercept$.subscribe({
4949
error: (err) => {
5050
expect(err).toBeInstanceOf(HttpException);
51-
expect(err.status).toEqual(404);
51+
expect(err.status).toEqual(HttpStatus.NOT_FOUND);
5252
done();
5353
},
5454
complete: () => done(),
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { NotFoundException } from "@nestjs/common";
2+
import { Observable, throwError } from "rxjs";
3+
import { HttpToGrpcInterceptor } from "../../lib";
4+
import { RpcException } from "@nestjs/microservices";
5+
import { status as GrpcStatusCode } from "@grpc/grpc-js";
6+
import { GRPC_CODE_FROM_HTTP } from "../../lib/utils";
7+
8+
const throwMockException = (
9+
Exception: new (...args: any[]) => any,
10+
): Observable<any> => {
11+
const exception = new Exception(Exception.name);
12+
return throwError(
13+
() =>
14+
new RpcException({
15+
message: exception.message,
16+
code: GRPC_CODE_FROM_HTTP[exception.status],
17+
}),
18+
);
19+
};
20+
21+
describe("HttpToGrpcInterceptor", () => {
22+
let interceptor: HttpToGrpcInterceptor;
23+
24+
beforeAll(() => {
25+
interceptor = new HttpToGrpcInterceptor();
26+
});
27+
28+
it("Should be defined", () => {
29+
expect(interceptor).toBeDefined();
30+
});
31+
32+
it("Should convert HTTP exceptions to gRPC exceptions", (done) => {
33+
const intercept$ = interceptor.intercept({} as any, {
34+
handle: () => throwMockException(NotFoundException),
35+
}) as Observable<any>;
36+
37+
intercept$.subscribe({
38+
error: (err) => {
39+
expect(err).toBeInstanceOf(RpcException);
40+
done();
41+
},
42+
complete: () => done(),
43+
});
44+
});
45+
46+
it("Should convert HTTP exceptions to gRPC exceptions", (done) => {
47+
const intercept$ = interceptor.intercept({} as any, {
48+
handle: () => throwMockException(NotFoundException),
49+
}) as Observable<any>;
50+
51+
intercept$.subscribe({
52+
error: (err) => {
53+
expect(err).toBeInstanceOf(RpcException);
54+
expect(err.error.code).toEqual(GrpcStatusCode.NOT_FOUND);
55+
done();
56+
},
57+
complete: () => done(),
58+
});
59+
});
60+
61+
it("Should contain the HTTP exception error message", (done) => {
62+
const intercept$ = interceptor.intercept({} as any, {
63+
handle: () => throwMockException(NotFoundException),
64+
}) as Observable<any>;
65+
66+
intercept$.subscribe({
67+
error: (err) => {
68+
expect(err).toBeInstanceOf(RpcException);
69+
expect(err.message).toEqual(NotFoundException.name);
70+
done();
71+
},
72+
complete: () => done(),
73+
});
74+
});
75+
});

0 commit comments

Comments
 (0)