1 /**
2 	Json-Rpc 2.0 protocol implementation.
3 
4 	Copyright: © 2018 Eliott Dumeix
5 	License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
6 */
7 module rpc.protocol.json;
8 
9 import rpc.core;
10 import std.typecons: Nullable, nullable;
11 import vibe.data.json;
12 import vibe.core.log;
13 import autointf : InterfaceInfo;
14 
15 
16 /** Json-Rpc 2.0 error.
17 */
18 class JsonRPCError
19 {
20 public:
21     int code;
22     string message;
23     @optional Json data;
24 
25     /// Default constructor.
26     this() @safe nothrow {}
27 
28     /// Standard error constructor.
29     this(StdCodes code)
30     @safe nothrow {
31         this.code = code;
32         this.message = CODES_MESSAGE[this.code];
33     }
34 
35     static enum StdCodes
36     {
37         parseError      = -32700,
38         invalidRequest  = -32600,
39         methodNotFound  = -32601,
40         invalidParams   = -32602,
41         internalError   = -32603
42     }
43 
44     private static immutable string[int] CODES_MESSAGE;
45 
46     static this()
47     @safe {
48         CODES_MESSAGE[StdCodes.parseError]     = "Parse error";
49         CODES_MESSAGE[StdCodes.invalidRequest] = "Invalid Request";
50         CODES_MESSAGE[StdCodes.methodNotFound] = "Method not found";
51         CODES_MESSAGE[StdCodes.invalidParams]  = "Invalid params";
52         CODES_MESSAGE[StdCodes.internalError]  = "Internal error";
53     }
54 }
55 
56 /** Json-Rpc request.
57 
58     Template_Params:
59 		TId = The type used to identify rpc request.
60 */
61 class JsonRPCRequest(TId): IRPCRequest!TId
62 {
63 public:
64     @property TId requestId() { return id; }
65     string jsonrpc;
66     string method;
67     @optional TId id;
68     @optional Nullable!Json params;
69 
70     this() @safe
71     {
72         params = Nullable!Json.init;
73     }
74 
75     static JsonRPCRequest!TId make(T)(TId id, string method, T params)
76     {
77         import vibe.data.json : serializeToJson;
78 
79         auto request = new JsonRPCRequest!TId();
80         request.id = id;
81         request.method = method;
82         request.params = serializeToJson!T(params);
83 
84         return request;
85     }
86 
87     static JsonRPCRequest!TId make(TId id, string method)
88     {
89         import vibe.data.json : serializeToJson;
90 
91         auto request = new JsonRPCRequest!TId();
92         request.id = id;
93         request.method = method;
94 
95         return request;
96     }
97 
98     bool hasParams() @safe
99     {
100         return !params.isNull;
101     }
102 
103     Json toJson() const @safe
104     {
105         Json json = Json.emptyObject;
106         json["jsonrpc"] = "2.0";
107         json["method"] = method;
108         json["id"] = id;
109         if (!params.isNull)
110             json["params"] = params.get;
111         return json;
112     }
113 
114     static JsonRPCRequest fromJson(Json src) @safe
115     {
116         JsonRPCRequest request = new JsonRPCRequest();
117         request.jsonrpc = src["jsonrpc"].to!string;
118         request.method = src["method"].to!string;
119         if (src["id"].type != Json.Type.undefined)
120             request.id = src["id"].to!TId;
121         if (src["params"].type != Json.Type.undefined)
122             request.params = src["params"].nullable;
123         return request;
124     }
125 
126     override string toString() const @safe
127     {
128         return toJson().toString();
129     }
130 
131 
132     static JsonRPCRequest fromString(string src) @safe
133     {
134         return fromJson(parseJson(src));
135     }
136 }
137 
138 @("Test JsonRpcRequest")
139 unittest
140 {
141     import vibe.data.json;
142 
143     auto r1 = new JsonRPCRequest!int();
144     auto json = Json.emptyObject;
145     json["foo"] = 42;
146 
147     r1.method = "foo";
148     assert(`{"jsonrpc":"2.0","id":0,"method":"foo"}` == r1.toString());
149 
150     auto r2 = new JsonRPCRequest!int();
151     r2.method = "foo";
152     r2.params = json;
153     assert(`{"jsonrpc":"2.0","id":0,"method":"foo","params":{"foo":42}}` == r2.toString());
154 
155     auto r3 = deserializeJson!(JsonRPCRequest!int)(r1.toString());
156     assert(r3.id == r1.id);
157     assert(r3.params == r1.params);
158     assert(r3.method == r1.method);
159 
160     // string id:
161     auto r10 = new JsonRPCRequest!string();
162     r10.method = "foo";
163     r10.id = "bar";
164     assert(`{"jsonrpc":"2.0","id":"bar","method":"foo"}` == r10.toString());
165 }
166 
167 /** Json-Rpc response.
168 
169     Template_Params:
170 		TId = The type used to identify rpc request.
171 */
172 class JsonRPCResponse(TId): IRPCResponse
173 {
174 public:
175     string jsonrpc;
176     Nullable!TId id;
177     @optional Nullable!Json result;
178     @optional Nullable!JsonRPCError error;
179 
180     this() @safe nothrow
181     {
182         result = Nullable!Json.init;
183         error = Nullable!JsonRPCError.init;
184     }
185 
186     bool isError() @safe nothrow
187     {
188         return !error.isNull;
189     }
190 
191     bool isSuccess() @safe nothrow
192     {
193         return !result.isNull;
194     }
195 
196     Json toJson() const @safe
197     {
198         Json json = Json.emptyObject;
199         json["jsonrpc"] = "2.0";
200         // the id must be 'null' in case of parse error
201         if (!id.isNull)
202             json["id"] = id.get;
203         else
204             json["id"] = null;
205         if (!result.isNull)
206             json["result"] = result.get;
207         if (!error.isNull)
208             json["error"] = serializeToJson!(const(JsonRPCError))(error.get);
209         return json;
210     }
211 
212     static JsonRPCResponse fromJson(Json src) @safe
213     {
214         JsonRPCResponse request = new JsonRPCResponse();
215         request.jsonrpc = src["jsonrpc"].to!string;
216         if (src["id"].type != Json.Type.undefined)
217         {
218             if (src["id"].type == Json.Type.null_)
219                 request.id.nullify;
220             else
221                 request.id = src["id"].to!TId;
222         }
223         if (src["result"].type != Json.Type.undefined)
224             request.result = src["result"].nullable;
225         if (src["error"].type != Json.Type.undefined)
226             request.error = deserializeJson!JsonRPCError(src["error"]).nullable;
227         return request;
228     }
229 
230     override string toString() const @safe
231     {
232         return toJson().toString();
233     }
234 
235     static JsonRPCResponse fromString(string src) @safe
236     {
237         return fromJson(parseJson(src));
238     }
239 }
240 
241 @("Test JsonRpcResponse")
242 unittest
243 {
244     auto r1 = new JsonRPCResponse!int();
245     Json json = "hello";
246     r1.result = json;
247     r1.id = 42;
248     assert(`{"jsonrpc":"2.0","result":"hello","id":42}` == r1.toString());
249 
250     auto error = new JsonRPCError();
251     error.code = -32600;
252     error.message = "Invalid Request";
253 
254     auto r2 = new JsonRPCResponse!int();
255     r2.error = error;
256     r2.id = 1;
257     assert(`{"jsonrpc":"2.0","id":1,"error":{"message":"Invalid Request","code":-32600}}` == r2.toString());
258 }
259 
260 /// Encapsulate a json-rpc error response.
261 class JsonRPCMethodException: RPCException
262 {
263     import std.conv: to;
264 
265     this(JsonRPCError error) @safe
266     {
267         if (error.data.type == Json.Type.object)
268             super(error.message ~ " (" ~ to!string(error.code) ~ "): " ~ error.data.toString());
269         else
270             super(error.message ~ " (" ~ to!string(error.code) ~ ")");
271     }
272 
273     this(string msg) @safe
274     {
275         super(msg);
276     }
277 }
278 
279 /// Exception to be used inside rpc handler, to throw user defined json-rpc error.
280 class JsonRPCUserException: RPCException
281 {
282     import vibe.data.json;
283 
284 public:
285     int code;
286     Json data;
287 
288     this(T)(int code, string msg, T data) @safe
289     {
290         super(msg);
291         this.code = code;
292         this.data = serializeToJson(data);
293     }
294 }
295 
296 
297 
298 class RawJsonRPCClient(TId): RawRPCClient!(TId, JsonRPCRequest!TId, JsonRPCResponse!TId)
299 {
300     import core.time : Duration;
301     import vibe.data.json;
302     import vibe.stream.operations: readAllUTF8;
303     import vibe.core.stream: InputStream, OutputStream;
304 
305 private:
306     IIdGenerator!TId _idGenerator;
307     JsonRPCResponse!TId[TId] _pendingResponse;
308 
309 public:
310     this(OutputStream ostream, InputStream istream) @safe
311     {
312         super(ostream, istream);
313         _idGenerator = new IdGenerator!TId();
314     }
315 
316     /** Send a request with an auto-generated id.
317         Throws:
318             JSONException
319     */
320     JsonRPCResponse!TId sendRequestAndWait(JsonRPCRequest!TId request, Duration timeout = Duration.max()) @safe
321     {
322         request.id = _idGenerator.getNextId();
323         _ostream.write(request.toString());
324         return waitForResponse(request.id, timeout);
325     }
326 
327     // Process the input stream once.
328     void tick() @safe
329     {
330         string rawJson = _istream.readAllUTF8();
331         Json json = parseJson(rawJson);
332 
333         void process(Json jsonObject)
334         {
335             auto response = deserializeJson!(JsonRPCResponse!TId)(jsonObject);
336             _pendingResponse[response.id] = response;
337         }
338 
339         // batch of commands
340         if (json.type == Json.Type.array)
341         {
342             foreach(object; json.byValue)
343             {
344                 process(object);
345             }
346         }
347         else
348         {
349             process(json);
350         }
351     }
352 
353 protected:
354     JsonRPCResponse!TId waitForResponse(TId id, Duration timeout) @safe
355     {
356         import std.conv: to;
357 
358         // check if response already received
359         if (id in _pendingResponse)
360         {
361             scope(exit) _pendingResponse.remove(id);
362             return _pendingResponse[id];
363         }
364         else
365         {
366             throw new RPCTimeoutException("No reponse");
367         }
368 
369     }
370 }
371 
372 alias IJsonRPCClient(TId) = IRPCClient!(TId, JsonRPCRequest!TId, JsonRPCResponse!TId);
373 
374 /// An http json-rpc client
375 alias HTTPJsonRPCClient(TId) = HttpRPCClient!(TId, JsonRPCRequest!TId, JsonRPCResponse!TId);
376 
377 class TCPJsonRPCClient(TId): IJsonRPCClient!TId
378 {
379     import vibe.core.net : TCPConnection, TCPListener, connectTCP;
380     import vibe.stream.operations : readLine;
381     import core.time;
382     import std.conv: to;
383 
384 private:
385     string _host;
386     ushort _port;
387     bool _connected;
388     IIdGenerator!TId _idGenerator;
389     JsonRPCResponse!TId[TId] _pendingResponse;
390     TCPConnection _conn;
391 
392 public:
393     @property bool connected() { return _connected; }
394 
395     this(string host, ushort port)
396     {
397         _host = host;
398         _port = port;
399         this.connect();
400         _idGenerator = new IdGenerator!TId();
401     }
402 
403     bool connect() @safe nothrow
404     in {
405         assert(!_connected);
406     }
407     do {
408         try {
409             _conn = connectTCP(_host, _port);
410             _connected = true;
411         } catch (Exception e) {
412             _connected = false;
413         }
414 
415         return _connected;
416     }
417 
418     /// auto-generate id
419     JsonRPCResponse!TId sendRequestAndWait(JsonRPCRequest!TId request, core.time.Duration timeout = core.time.Duration.max()) @safe
420     {
421         if (!_connected)
422             throw new RPCNotConnectedException("tcp client not connected ! call connect() first.");
423 
424         request.id = _idGenerator.getNextId();
425         logTrace("tcp send request: %s", request);
426         _conn.write(request.toString() ~ "\r\n");
427 
428         if (_conn.waitForData(timeout)) {
429             char[] raw = cast(char[]) _conn.readLine();
430             string json = to!string(raw);
431             logTrace("tcp server request response: %s", json);
432             auto response = deserializeJson!(JsonRPCResponse!TId)(json);
433             return response;
434         }
435         else
436             throw new RPCTimeoutException("waitForData timeout");
437     }
438 
439     @disable void tick() @safe {}
440 }
441 
442 
443 
444 alias IJsonRPCServer(TId) = IRPCServer!(TId, JsonRPCRequest!TId, JsonRPCResponse!TId);
445 
446 alias JsonRPCRequestHandler(TId) = RPCRequestHandler!(JsonRPCRequest!TId, JsonRPCResponse!TId);
447 
448 class RawJsonRPCServer(TId): RawRPCServer!(TId, JsonRPCRequest!TId, JsonRPCResponse!TId),
449     IRPCServerOutput!(JsonRPCResponse!TId)
450 {
451     import vibe.stream.operations: readAllUTF8;
452     import vibe.core.stream: InputStream, OutputStream;
453 
454 private:
455     JsonRPCRequestHandler!TId[string] _requestHandler;
456 
457 public:
458     this(OutputStream ostream, InputStream istream) @safe
459     {
460         super(ostream, istream);
461     }
462 
463     void registerRequestHandler(string method, JsonRPCRequestHandler!TId handler)
464     {
465         _requestHandler[method] = handler;
466     }
467 
468     void sendResponse(JsonRPCResponse!TId reponse) @safe
469     {
470         _ostream.write(reponse.toString());
471     }
472 
473     void tick() @safe
474     {
475         string rawJson = _istream.readAllUTF8();
476         Json json = parseJson(rawJson);
477 
478         void process(Json jsonObject)
479         {
480             auto request = deserializeJson!(JsonRPCRequest!TId)(jsonObject);
481             if (request.method in _requestHandler)
482             {
483                 _requestHandler[request.method](request, this);
484             }
485         }
486 
487         // batch of commands
488         if (json.type == Json.Type.array)
489         {
490             foreach(object; json.byValue)
491             {
492                 process(object);
493             }
494         }
495         else
496         {
497             process(json);
498         }
499     }
500 }
501 
502 /** An http json-rpc server.
503 
504     Template_Params:
505 	    TId = The type to use for request and response json-rpc id.
506 */
507 
508 /// An http json-rpc client
509 class HTTPJsonRPCServer(TId): HttpRPCServer!(TId, JsonRPCRequest!TId, JsonRPCResponse!TId)
510 {
511     import vibe.data.json: JSONException;
512     import vibe.http.router: URLRouter;
513 
514     this(URLRouter router, string path)
515     {
516         super(router, path);
517     }
518 
519     @disable void tick() @safe {}
520 
521     protected override JsonRPCResponse!TId buildResponseFromException(Exception e) @safe nothrow
522     {
523         auto response = new JsonRPCResponse!TId();
524         if (is(typeof(e) == JSONException))
525         {
526             response.error = new JsonRPCError(JsonRPCError.StdCodes.parseError);
527             return response;
528         }
529         else
530         {
531             response.error = new JsonRPCError(JsonRPCError.StdCodes.internalError);
532             return response;
533         }
534     }
535 
536     void registerInterface(I)(I instance, RPCInterfaceSettings settings = null)
537     {
538         import std.algorithm : filter, map, all;
539         import std.array : array;
540         import std.range : front;
541         import vibe.internal.meta.uda : findFirstUDA;
542 
543         alias Info = InterfaceInfo!I;
544         InterfaceInfo!I* info = new Info();
545 
546         foreach (i, ovrld; Info.SubInterfaceFunctions) {
547             enum fname = __traits(identifier, Info.SubInterfaceFunctions[i]);
548             alias R = ReturnType!ovrld;
549 
550             static if (isInstanceOf!(Collection, R)) {
551                 auto ret = __traits(getMember, instance, fname)(R.ParentIDs.init);
552                 router.registerRestInterface!(R.Interface)(ret.m_interface, info.subInterfaces[i].settings);
553             } else {
554                 auto ret = __traits(getMember, instance, fname)();
555                 router.registerRestInterface!R(ret, info.subInterfaces[i].settings);
556             }
557         }
558 
559         foreach (i, Func; Info.Methods) {
560             enum methodNameAtt = findFirstUDA!(RPCMethodAttribute, Func);
561             enum smethod = Info.staticMethods[i];
562 
563             auto handler = jsonRpcMethodHandler!(TId, Func, i, I)(instance, *info);
564 
565             // select rpc name (attribute or function name):
566             static if (methodNameAtt.found)
567                 this.registerRequestHandler(methodNameAtt.value.method, handler);
568             else
569                 this.registerRequestHandler(smethod.name, handler);
570 
571         }
572     }
573 }
574 
575 class TCPJsonRPCServer(TId): IJsonRPCServer!TId
576 {
577     import vibe.core.net : TCPConnection, TCPListener, listenTCP;
578     import vibe.stream.operations : readLine;
579 
580 private:
581     alias JsonRpcRespHandler = IRPCServerOutput!(JsonRPCResponse!TId);
582     JsonRPCRequestHandler!TId[string] _requestHandler;
583     RPCInterfaceSettings _settings;
584 
585     class ResponseWriter: JsonRpcRespHandler
586     {
587         private TCPConnection _conn;
588 
589         this(TCPConnection conn)
590         {
591             _conn = conn;
592         }
593 
594         void sendResponse(JsonRPCResponse!TId reponse)
595         @safe {
596             logTrace("tcp request response: %s", reponse);
597             try {
598                 _conn.write(reponse.toString() ~ "\r\n");
599             } catch (Exception e) {
600                 logTrace("unable to send response: %s", e.msg);
601                 // TODO: add a delgate to allow the user to handle error
602             }
603         }
604     }
605 
606 public:
607     this(ushort port, RPCInterfaceSettings settings = null)
608     {
609         _settings = settings;
610 
611         listenTCP(port, (conn) {
612             logTrace("new client: %s", conn);
613             try {
614 
615                 auto writer = new ResponseWriter(conn);
616 
617                 while (!conn.empty) {
618                     auto json = cast(const(char)[])conn.readLine();
619                     logTrace("tcp request received: %s", json);
620 
621                     this.process(cast(string) json, writer);
622                 }
623             } catch (Exception e) {
624                 logError("Failed to read from client: %s", e.msg);
625                 if (_settings !is null)
626                     _settings.errorHandler(e);
627             }
628 
629             conn.close();
630         });
631     }
632 
633     void registerInterface(I)(I instance, RPCInterfaceSettings settings = null)
634     {
635         import std.algorithm : filter, map, all;
636         import std.array : array;
637         import std.range : front;
638 
639         alias Info = InterfaceInfo!I;
640         InterfaceInfo!I* info = new Info();
641 
642         foreach (i, Func; Info.Methods) {
643             enum smethod = Info.staticMethods[i];
644 
645             // normal handler
646             auto handler = jsonRpcMethodHandler!(TId, Func, i)(instance, *info);
647 
648             this.registerRequestHandler(smethod.name, handler);
649         }
650 
651     }
652 
653     @disable void tick() @safe {}
654 
655     void registerRequestHandler(string method, JsonRPCRequestHandler!TId handler)
656     {
657         _requestHandler[method] = handler;
658     }
659 
660     void process(string data, JsonRpcRespHandler respHandler)
661     {
662         Json json = parseJson(data);
663 
664         void process(Json jsonObject)
665         {
666             auto request = deserializeJson!(JsonRPCRequest!TId)(jsonObject);
667             if (request.method in _requestHandler)
668             {
669                 _requestHandler[request.method](request, respHandler);
670             }
671         }
672 
673         // batch of commands
674         if (json.type == Json.Type.array)
675         {
676             foreach(object; json.byValue)
677             {
678                 process(object);
679             }
680         }
681         else
682         {
683             process(json);
684         }
685     }
686 }
687 
688 
689 /// Return an handler to match a json-rpc request on an interface method.
690 public JsonRPCRequestHandler!TId jsonRpcMethodHandler(TId, alias Func, size_t n, T)(T inst, ref InterfaceInfo!T intf)
691 {
692     import std.traits;
693     import std.meta : AliasSeq;
694     import std.string : format;
695     import vibe.utils.string : sanitizeUTF8;
696     import vibe.internal.meta.funcattr : IsAttributedParameter, computeAttributedParameterCtx;
697     import vibe.internal.meta.traits : derivedMethod;
698 
699     enum Method = __traits(identifier, Func);
700     alias PTypes = ParameterTypeTuple!Func;
701     alias PDefaults = ParameterDefaultValueTuple!Func;
702     alias CFuncRaw = derivedMethod!(T, Func);
703     static if (AliasSeq!(CFuncRaw).length > 0) alias CFunc = CFuncRaw;
704     else alias CFunc = Func;
705     alias RT = ReturnType!(FunctionTypeOf!Func);
706     static const sroute = InterfaceInfo!T.staticMethods[n];
707     auto method = intf.methods[n];
708 
709     void handler(JsonRPCRequest!TId req, IRPCServerOutput!(JsonRPCResponse!TId) serv) @safe
710     {
711         auto response = new JsonRPCResponse!TId();
712         response.id = req.id;
713         PTypes params;
714 
715         // build a custom json error object tobe sent in json rpc error response.
716         Json buildErrorData(string details)
717         @safe {
718             auto json = Json.emptyObject;
719             json["details"] = details;
720             json["request"] = req.toJson();
721             return json;
722         }
723 
724         try {
725             // check params consistency beetween rpc-request and function parameters
726             if (PTypes.length > 1)
727             {
728                 // we expect a json array
729                 if (req.params.type != Json.Type.array)
730                 {
731                     response.error = new JsonRPCError(JsonRPCError.StdCodes.invalidParams);
732                     response.error.data = buildErrorData("Expected a json array for params");
733                     serv.sendResponse(response);
734                     return;
735                 }
736                 // req.params is a json array
737                 else if (req.params.length != PTypes.length)
738                 {
739                     response.error = new JsonRPCError(JsonRPCError.StdCodes.invalidParams);
740                     response.error.data = buildErrorData("Missing params");
741                     serv.sendResponse(response);
742                     return;
743                 }
744             }
745 
746             foreach (i, PT; PTypes) {
747                 enum sparam = sroute.parameters[i];
748 
749                 enum pname = sparam.name;
750                 auto fieldname = sparam.name;
751                 static if (isInstanceOf!(Nullable, PT)) PT v;
752                 else Nullable!PT v;
753 
754                 v = deserializeJson!PT(req.params[i]);
755 
756                 params[i] = v;
757             }
758         } catch (Exception e) {
759             //handleException(e, HTTPStatus.badRequest);
760             return;
761         }
762 
763         try {
764             import vibe.internal.meta.funcattr;
765 
766             static if (!__traits(compiles, () @safe { __traits(getMember, inst, Method)(params); }))
767                 pragma(msg, "Non-@safe methods are deprecated in REST interfaces - Mark "~T.stringof~"."~Method~" as @safe.");
768 
769             static if (is(RT == void)) {
770                 // TODO: return null
771             } else {
772                 auto ret = () @trusted { return __traits(getMember, inst, Method)(params); } (); // TODO: remove after deprecation period
773 
774                 static if (!__traits(compiles, () @safe { evaluateOutputModifiers!Func(ret, req, res); } ()))
775                     pragma(msg, "Non-@safe @after evaluators are deprecated - annotate @after evaluator function for "~T.stringof~"."~Method~" as @safe.");
776 
777                 static if (!__traits(compiles, () @safe { res.writeJsonBody(ret); }))
778                     pragma(msg, "Non-@safe serialization of REST return types deprecated - ensure that "~RT.stringof~" is safely serializable.");
779                 () @trusted {
780                     // build reponse
781                     response.id = req.id;
782                     response.result = serializeToJson(ret);
783                     serv.sendResponse(response);
784                 }();
785             }
786         }
787         // catch user-defined json-rpc errors.
788         catch (JsonRPCUserException e)
789         {
790             response.error = new JsonRPCError();
791             response.error.code = e.code;
792             response.error.message = e.msg;
793             response.error.data = e.data;
794             serv.sendResponse(response);
795             return;
796         }
797         catch (Exception e) {
798             //returnHeaders();
799             //handleException(e, HTTPStatus.internalServerError);
800             response.error = new JsonRPCError();
801             response.error.code = 0;
802             response.error.message = e.msg;
803             serv.sendResponse(response);
804             return;
805         }
806     }
807 
808     return &handler;
809 }
810 
811 /** Base class to create a Json RPC automatic client.
812 */
813 abstract class JsonRPCAutoClient(I) : I
814 {
815     import std.traits : hasUDA;
816 
817 private:
818         // The json rpc id type to use: string or int
819         static if (hasUDA!(I, RPCIdTypeAttribute!int))
820             alias TId = int;
821         else static if (hasUDA!(I, RPCIdTypeAttribute!string))
822             alias TId = string;
823         else
824             alias TId = int;
825 
826     protected:
827         IRPCClient!(TId, JsonRPCRequest!TId, JsonRPCResponse!TId) _client;
828         RPCInterfaceSettings _settings;
829 
830         RT executeMethod(I, RT, int n, ARGS...)(ref InterfaceInfo!I info, ARGS args) @safe
831         {
832             import vibe.internal.meta.uda : findFirstUDA;
833             import std.traits;
834             import std.array : appender;
835             import core.time;
836             import vibe.data.json;
837 
838             // retrieve some compile time informations
839             // alias Info  = RpcInterface!I;
840             alias Func  = info.Methods[n];
841             alias RT    = ReturnType!Func;
842             alias PTT   = ParameterTypeTuple!Func;
843             enum sroute = info.staticMethods[n];
844             auto method = info.methods[n];
845 
846             enum objectParamAtt = findFirstUDA!(RPCMethodObjectParams, Func);
847             enum methodNameAtt = findFirstUDA!(RPCMethodAttribute, Func);
848 
849             try
850             {
851                 auto jsonParams = Json.undefined;
852 
853                 // Render params as unique param or array
854                 static if (!objectParamAtt.found)
855                 {
856                     // if several params, then build an a json array
857                     if (PTT.length > 1)
858                         jsonParams = Json.emptyArray;
859 
860                     // fill the json array or the unique value
861                     foreach (i, PT; PTT) {
862                         if (PTT.length > 1)
863                             jsonParams.appendArrayElement(serializeToJson(args[i]));
864                         else
865                             jsonParams = serializeToJson(args[i]);
866                     }
867                 }
868                 // render params as a json object by using the param name
869                 // for the key or the uda if exists
870                 else
871                 {
872                     jsonParams = Json.emptyObject;
873 
874                     // fill object
875                     foreach (i, PT; PTT) {
876                         if (sroute.parameters[i].name in objectParamAtt.value.names)
877                             jsonParams[objectParamAtt.value.names[sroute.parameters[i].name]] = serializeToJson(args[i]);
878                         else
879                             jsonParams[sroute.parameters[i].name] = serializeToJson(args[i]);
880                     }
881                 }
882 
883 
884                 static if (!is(RT == void))
885                     RT jret;
886 
887                 // create a json-rpc request
888                 auto request = new JsonRPCRequest!TId();
889                 static if (methodNameAtt.found)
890                     request.method = methodNameAtt.value.method;
891                 else
892                     request.method = method.name;
893                 request.params = jsonParams; // set rpc call params
894 
895                 auto response = _client.sendRequestAndWait(request, _settings.responseTimeout); // send packet and wait
896 
897                 if (response.isError())
898                 {
899                     throw new JsonRPCMethodException(response.error);
900                 }
901 
902                 // void return type
903                 static if (is(RT == void))
904                 {
905 
906                 }
907                 else
908                 {
909                     return deserializeJson!RT(response.result);
910                 }
911             }
912             catch (JSONException e)
913             {
914                 throw new RPCParsingException(e.msg, e);
915             }
916             catch (Exception e)
917             {
918                 throw new RPCException(e.msg, e);
919             }
920         }
921 
922     public:
923         @property auto client() @safe { return _client; }
924 
925     // mixin(autoImplementMethods!I());
926 }
927 
928 class RawJsonRPCAutoClient(I) : JsonRPCAutoClient!I
929 {
930     import vibe.core.stream: InputStream, OutputStream;
931     import autointf;
932 
933 public:
934     this(OutputStream ostream, InputStream istream) @safe
935     {
936         _client = new RawJsonRPCClient!TId(ostream, istream);
937         _settings = new RPCInterfaceSettings();
938     }
939 
940     mixin(autoImplementMethods!I());
941 }
942 
943 class HTTPJsonRPCAutoClient(I) : JsonRPCAutoClient!I
944 {
945     import autointf;
946 
947 public:
948     this(string host) @safe
949     {
950         _client = new HTTPJsonRPCClient!TId(host);
951         _settings = new RPCInterfaceSettings();
952     }
953 
954     mixin(autoImplementMethods!I());
955 }
956 
957 class TCPJsonRPCAutoClient(I) : JsonRPCAutoClient!I
958 {
959     import autointf;
960 
961 public:
962     this(string host, ushort port) @safe
963     {
964         _client = new TCPJsonRPCClient!TId(host, port);
965         _settings = new RPCInterfaceSettings();
966     }
967 
968     mixin(autoImplementMethods!I());
969 }