Skip to content

Commit 75acc7e

Browse files
authored
Merge pull request #9 from hfour/goce/H4C-247_јson-rpc-client
H4C-247: JSON RPC Client implementation
2 parents e79b547 + d222f73 commit 75acc7e

File tree

6 files changed

+109
-29
lines changed

6 files changed

+109
-29
lines changed

package.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@
1717
"@nestjs/common": "^6.11.11",
1818
"@nestjs/core": "^6.11.11",
1919
"@nestjs/microservices": "^6.11.11",
20-
"reflect-metadata": "^0.1.13",
21-
"express": "^4.17.1"
20+
"axios": "^0.19.2",
21+
"express": "^4.17.1",
22+
"reflect-metadata": "^0.1.13"
2223
},
2324
"devDependencies": {
2425
"@nestjs/testing": "^6.11.11",
@@ -28,11 +29,11 @@
2829
"@types/supertest": "^2.0.7",
2930
"jest": "^25.1.0",
3031
"prettier": "^1.15.3",
32+
"rxjs": "^6.0.0",
3133
"supertest": "^4.0.2",
3234
"ts-jest": "25.2.1",
3335
"ts-node": "8.6.2",
34-
"typescript": "3.8.3",
35-
"rxjs": "^6.0.0"
36+
"typescript": "3.8.3"
3637
},
3738
"jest": {
3839
"moduleFileExtensions": [

src/client-proxy.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { ClientProxy } from "@nestjs/microservices";
2+
import axios from "axios";
3+
import { resolve } from "dns";
4+
import { CodedRpcException } from ".";
5+
6+
export class JSONRPCClient extends ClientProxy {
7+
constructor(private readonly url: string) {
8+
super();
9+
}
10+
private counter: number = 0;
11+
private jsonrpc: string = "2.0";
12+
13+
connect(): Promise<any> {
14+
throw new Error('The "connect()" method is not supported in JSONRPC mode.');
15+
}
16+
close() {
17+
return Promise.resolve();
18+
}
19+
/**
20+
* Method is unsupported for JSONRPC
21+
*/
22+
protected publish(packet: any, callback: (packet: any) => any): any {
23+
throw new Error("Method is not supported in JSONRPC mode.");
24+
}
25+
/**
26+
* Method is unsupported for JSONRPC
27+
*/
28+
protected async dispatchEvent(packet: any): Promise<any> {
29+
throw new Error("Method is not supported in JSONRPC mode.");
30+
}
31+
getService<SvcInterface>(namespace: string): ServiceClient<SvcInterface> {
32+
let url = this.url;
33+
let id = ++this.counter;
34+
let jsonrpc = this.jsonrpc;
35+
return new Proxy(
36+
{},
37+
{
38+
get(obj, prop) {
39+
return function(params: any) {
40+
return axios
41+
.post(url, {
42+
method: namespace + "." + prop.toString(),
43+
params,
44+
jsonrpc: "2.0",
45+
id
46+
})
47+
.then(res => ({ jsonrpc, result: res, id }))
48+
.catch(err => {
49+
const { code, message, data } = err.response.data;
50+
51+
throw new CodedRpcException(message, code, data);
52+
});
53+
};
54+
}
55+
}
56+
) as ServiceClient<SvcInterface>;
57+
}
58+
}
59+
export type ServiceClient<Service> = {
60+
[MethodName in keyof Service]: Service[MethodName] extends (params: any) => Promise<any>
61+
? Service[MethodName]
62+
: Service[MethodName] extends (params: infer Params) => infer ReturnType
63+
? (params: Params) => Promise<ReturnType>
64+
: never;
65+
};

src/index.spec.ts

Lines changed: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
import * as request from "supertest";
22

33
import { Test } from "@nestjs/testing";
4-
import { INestMicroservice } from "@nestjs/common";
4+
import { INestMicroservice, ServiceUnavailableException } from "@nestjs/common";
55

66
import { TestService } from "./test-handler";
7-
import { JSONRPCServer } from ".";
7+
import { JSONRPCServer, CodedRpcException } from ".";
8+
import { JSONRPCClient } from "./client-proxy";
89

910
describe("json-rpc-e2e", () => {
1011
let app: INestMicroservice;
1112
let server: JSONRPCServer;
13+
let client: JSONRPCClient;
14+
let service: TestService;
1215

1316
beforeAll(async () => {
1417
let moduleRef = await Test.createTestingModule({
@@ -20,34 +23,30 @@ describe("json-rpc-e2e", () => {
2023
port: 8080
2124
});
2225

26+
client = new JSONRPCClient("http://localhost:8080/rpc/v1");
27+
28+
service = client.getService<TestService>("test");
29+
2330
app = moduleRef.createNestMicroservice({ strategy: server });
2431
await new Promise(resolve => app.listen(resolve));
2532
});
2633

27-
it(`/rpc/v1/ test.invoke (POST)`, () => {
28-
return request(server.server)
29-
.post("/rpc/v1")
30-
.send({ method: "test.invoke", params: { data: "hi" } })
31-
.expect(200)
32-
.expect({
33-
data: "hi"
34-
});
34+
it(`should make and RPC call with the JSONRPCClient`, () => {
35+
return service
36+
.invokeClientService({ data: "hi" })
37+
.then(res => expect(res.result.data).toStrictEqual({ data: "hi" }));
3538
});
3639

37-
it(`should throw an error on /rpc/v1/ test.testError (POST)`, () => {
38-
const errorObj = {
39-
message: "RPC EXCEPTION",
40-
code: 403,
41-
data: {
40+
it(`should return an error and check error data from JSONRPCClient call`, async () => {
41+
const expectedCodedException = expect.objectContaining(
42+
new CodedRpcException("RPC EXCEPTION", 403, {
4243
fromService: "Test Service",
4344
params: { data: "hi" }
44-
}
45-
};
46-
return request(server.server)
47-
.post("/rpc/v1")
48-
.send({ method: "test.testError", params: { data: "hi" } })
49-
.expect(403)
50-
.expect(errorObj);
45+
})
46+
);
47+
48+
const resp = service.testError({ data: "hi" });
49+
return expect(resp).rejects.toThrowError(expectedCodedException);
5150
});
5251

5352
afterAll(async () => {

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ export class JSONRPCServer extends Server implements CustomTransportStrategy {
6262
// let handlers = this.getHandlers();
6363

6464
let handler = this.getHandlerByPattern(req.body.method);
65+
6566
if (handler == null) {
6667
return res.status(404).json({ error: "Not Found" });
6768
}

src/test-handler.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ class TestGuard implements CanActivate {
6464
@JSONRpcService({
6565
namespace: "test"
6666
})
67-
export class TestService {
67+
export class TestService implements ITestClientService {
6868
constructor() {
6969
DecorationsState.serviceConstructorCount = DecorationsState.serviceConstructorCount + 1;
7070
console.log("TestService count now at", DecorationsState.serviceConstructorCount);
@@ -81,8 +81,22 @@ export class TestService {
8181
@UsePipes(TestPipe)
8282
@UseInterceptors(TestInterceptor)
8383
@UseGuards(TestGuard)
84-
public async testError(params: any) {
84+
public async testError(params: any): Promise<any> {
8585
// construct the error object with some data inside
8686
throw new CodedRpcException("RPC EXCEPTION", 403, { fromService: "Test Service", params });
8787
}
88+
89+
@UsePipes(TestPipe)
90+
@UseInterceptors(TestInterceptor)
91+
@UseGuards(TestGuard)
92+
public async invokeClientService(params: any) {
93+
console.log("Invoke Client Service WAS called");
94+
return params;
95+
}
96+
}
97+
98+
export interface ITestClientService {
99+
invoke(params: any): any;
100+
invokeClientService(params: any): any;
101+
testError(params: any): Promise<any>;
88102
}

yarn.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -722,7 +722,7 @@ aws4@^1.8.0:
722722
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.9.1.tgz#7e33d8f7d449b3f673cd72deb9abdc552dbe528e"
723723
integrity sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==
724724

725-
725+
[email protected], axios@^0.19.2:
726726
version "0.19.2"
727727
resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.2.tgz#3ea36c5d8818d0d5f8a8a97a6d36b86cdc00cb27"
728728
integrity sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==

0 commit comments

Comments
 (0)