1
+ #pragma once
2
+ #include " common.hpp"
3
+ #include < exception>
4
+ #include < nlohmann/json.hpp>
5
+ #include < string>
6
+ #include < variant>
7
+
8
+ namespace jsonrpccpp {
9
+ enum class version { v1, v2 };
10
+ enum class param_type { by_position, by_name };
11
+
12
+ typedef std::vector<json> positional_parameter;
13
+ typedef std::map<std::string, json> named_parameter;
14
+ typedef std::variant<int , std::string> id_type;
15
+
16
+ struct JsonRpcResponse {
17
+ std::string id;
18
+ json result;
19
+ };
20
+
21
+ class IClientConnector {
22
+ public:
23
+ virtual ~IClientConnector () = default ;
24
+ virtual std::string Send (const std::string &request) = 0;
25
+ };
26
+
27
+ // TODO: add batch calls
28
+
29
+ class JsonRpcClient {
30
+ public:
31
+ JsonRpcClient (IClientConnector &connector, version v) : connector(connector), v(v) {}
32
+ virtual ~JsonRpcClient () = default ;
33
+
34
+ JsonRpcResponse CallMethod (const id_type &id, const std::string &name, const positional_parameter ¶ms = {}) { return call_method (id, name, params); }
35
+ JsonRpcResponse CallMethodNamed (const id_type &id, const std::string &name, const named_parameter ¶ms = {}) { return call_method (id, name, params); }
36
+
37
+ void CallNotification (const std::string &name, const positional_parameter ¶ms = {}) { call_notification (name, params); }
38
+ void CallNotificationNamed (const std::string &name, const named_parameter ¶ms = {}) { call_notification (name, params); }
39
+
40
+ protected:
41
+ IClientConnector &connector;
42
+
43
+ private:
44
+ version v;
45
+ static inline bool has_key (const json &v, const std::string &key) { return v.find (key) != v.end (); }
46
+ static inline bool has_key_type (const json &v, const std::string &key, json::value_t type) { return has_key (v, key) && v.at (key).type () == type; }
47
+
48
+ inline JsonRpcException get_error (const json &value) {
49
+ bool has_code = has_key_type (value, " code" , json::value_t ::number_integer);
50
+ bool has_message = has_key_type (value, " message" , json::value_t ::string);
51
+ bool has_data = has_key (value, " data" );
52
+ if (has_code && has_message) {
53
+ if (has_data) {
54
+ return JsonRpcException (value[" code" ], value[" message" ], value[" data" ].get <json>());
55
+ } else {
56
+ return JsonRpcException (value[" code" ], value[" message" ]);
57
+ }
58
+ }
59
+ return JsonRpcException (-32603 , R"( invalid error response: "code" (negative number) and "message" (string) are required)" );
60
+ }
61
+
62
+ JsonRpcResponse call_method (const id_type &id, const std::string &name, const json ¶ms) {
63
+ json j = {{" method" , name}};
64
+ if (std::get_if<int >(&id) != nullptr ) {
65
+ j[" id" ] = std::get<int >(id);
66
+ } else {
67
+ j[" id" ] = std::get<std::string>(id);
68
+ }
69
+ if (v == version::v2) {
70
+ j[" jsonrpc" ] = " 2.0" ;
71
+ }
72
+ if (!params.empty () && !params.is_null ()) {
73
+ j[" params" ] = params;
74
+ } else if (v == version::v1) {
75
+ j[" params" ] = nullptr ;
76
+ }
77
+ try {
78
+ json response = json::parse (connector.Send (j.dump ()));
79
+ if (has_key_type (response, " error" , json::value_t ::object)) {
80
+ throw get_error (response[" error" ]);
81
+ }
82
+ if (has_key (response, " result" ) && has_key (response, " id" )) {
83
+ return JsonRpcResponse{response[" id" ].get <std::string>(), response[" result" ].get <json>()};
84
+ }
85
+ throw JsonRpcException (-32603 , R"( invalid server response: neither "result" nor "error" fields found)" );
86
+ } catch (json::parse_error &e) {
87
+ throw JsonRpcException (-32700 , std::string (" invalid JSON response from server: " ) + e.what ());
88
+ }
89
+ }
90
+
91
+ void call_notification (const std::string &name, const nlohmann::json ¶ms) {
92
+ nlohmann::json j = {{" method" , name}};
93
+ if (v == version::v2) {
94
+ j[" jsonrpc" ] = " 2.0" ;
95
+ } else {
96
+ j[" id" ] = nullptr ;
97
+ }
98
+ if (!params.empty () && !params.is_null ()) {
99
+ j[" params" ] = params;
100
+ } else if (v == version::v1) {
101
+ j[" params" ] = nullptr ;
102
+ }
103
+ connector.Send (j.dump ());
104
+ }
105
+ };
106
+
107
+ class BatchRequest {
108
+ public:
109
+ BatchRequest () : call(json::array()) {}
110
+
111
+ BatchRequest& AddMethodCall (const id_type &id, const std::string& name, const positional_parameter ¶ms = {}) {
112
+ json request = {{" method" , name}, {" params" , params}, {" jsonrpc" , " 2.0" }};
113
+ if (std::get_if<int >(&id) != nullptr ) {
114
+ request[" id" ] = std::get<int >(id);
115
+ } else {
116
+ request[" id" ] = std::get<std::string>(id);
117
+ }
118
+ call.push_back (request);
119
+ return *this ;
120
+ }
121
+
122
+ BatchRequest& AddNamedMethodCall (const id_type &id, const std::string& name, const named_parameter ¶ms = {}) {
123
+ json request = {{" method" , name}, {" params" , params}, {" jsonrpc" , " 2.0" }};
124
+ if (std::get_if<int >(&id) != nullptr ) {
125
+ request[" id" ] = std::get<int >(id);
126
+ } else {
127
+ request[" id" ] = std::get<std::string>(id);
128
+ }
129
+ call.push_back (request);
130
+ return *this ;
131
+ }
132
+
133
+ BatchRequest& AddNotificationCall (const std::string& name, const positional_parameter ¶ms = {}) {
134
+ call.push_back ({{" method" , name}, {" params" , params}, {" jsonrpc" , " 2.0" }});
135
+ return *this ;
136
+ }
137
+
138
+ BatchRequest& AddNamedNotificationCall (const std::string& name, const named_parameter ¶ms = {}) {
139
+ call.push_back ({{" method" , name}, {" params" , params}, {" jsonrpc" , " 2.0" }});
140
+ return *this ;
141
+ }
142
+
143
+ const json& Build () const {
144
+ return call;
145
+ }
146
+ private:
147
+ json call;
148
+ };
149
+
150
+ class BatchResponse {
151
+ public:
152
+ BatchResponse (std::map<json, json>&& results, std::map<json, JsonRpcException> &&errors) {
153
+ this ->errors = errors;
154
+ this ->results = results;
155
+ }
156
+
157
+ template <typename T>
158
+ T GetResult (const json& id) {
159
+ if (results.find (id) != results.end ()) {
160
+ // TOOD: catch type conversion error
161
+ return results[id].get <T>();
162
+ } else if (errors.find (id) != errors.end ()) {
163
+ throw errors[id];
164
+ }
165
+ throw JsonRpcException (-32700 , std::string (" no result found for id " ) + id.dump ());
166
+ }
167
+
168
+ private:
169
+ std::map<json, json> results;
170
+ std::map<json, JsonRpcException> errors;
171
+ };
172
+
173
+ class JsonRpc2Client : public JsonRpcClient {
174
+ public:
175
+ JsonRpc2Client (IClientConnector &connector) : JsonRpcClient(connector, version::v2) {}
176
+
177
+ BatchResponse BatchCall (const BatchRequest& request) {
178
+ try {
179
+ json response = json::parse (connector.Send (request.Build ().dump ()));
180
+ if (!response.is_array ()) {
181
+ throw JsonRpcException (-32700 , std::string (" invalid JSON response from server: expected array" ));
182
+ }
183
+ std::map<json, json> results;
184
+ std::map<json, JsonRpcException> errors;
185
+
186
+ return BatchResponse (std::move (results), std::move (errors));
187
+ } catch (json::parse_error &e) {
188
+ throw JsonRpcException (-32700 , std::string (" invalid JSON response from server: " ) + e.what ());
189
+ }
190
+ }
191
+
192
+ };
193
+ } // namespace jsonrpccpp
0 commit comments