1 /**
2     Json-Rpc 2.0 protocol implementation.
3 
4     This module has 3 entry point:
5         $(UL
6 			$(LI `RawJsonRpcAutoClient`)
7 			$(LI `HttpJsonRpcAutoClient`)
8 			$(LI `TcpJsonRpcAutoClient`)
9 		)
10 
11     Copyright: © 2018 Eliott Dumeix
12     License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
13 */
14 module rpc.protocol.json;
15 
16 public import rpc.core;
17 import std.typecons: Nullable, nullable;
18 import vibe.data.json;
19 import vibe.core.log;
20 import autointf : InterfaceInfo;
21 import std.traits : hasUDA;
22 
23 
24 /** Json-Rpc 2.0 error.
25 */
26 class JsonRpcError
27 {
28 public:
29     /// Error code
30     int code;
31 
32     /// Error message
33     string message;
34 
35     /// Optional json data
36     @optional Json data;
37 
38     /// Default constructor.
39     this() @safe nothrow {}
40 
41     /// Standard error constructor.
42     this(StdCodes code)
43     @safe nothrow {
44         this.code = code;
45         this.message = CODES_MESSAGE[this.code];
46     }
47 
48     ///
49     static enum StdCodes
50     {
51         parseError      = -32700, /// Parse error
52         invalidRequest  = -32600, /// Invalid request
53         methodNotFound  = -32601, /// Method not found
54         invalidParams   = -32602, /// Invalid params
55         internalError   = -32603  /// Internal error
56     }
57 
58     ///
59     private static immutable string[int] CODES_MESSAGE;
60 
61     shared static this()
62     @safe {
63         CODES_MESSAGE[StdCodes.parseError]     = "Parse error";
64         CODES_MESSAGE[StdCodes.invalidRequest] = "Invalid Request";
65         CODES_MESSAGE[StdCodes.methodNotFound] = "Method not found";
66         CODES_MESSAGE[StdCodes.invalidParams]  = "Invalid params";
67         CODES_MESSAGE[StdCodes.internalError]  = "Internal error";
68     }
69 }
70 
71 /** A Json-Rpc request that use TId as id type.
72 */
73 class JsonRpcRequest(TId): IRpcRequest!TId
74 {
75 public:
76     ///
77     override @property TId requestId() { return id; }
78 
79     /// json rpc string
80     string jsonrpc;
81 
82     /// Json rpc method
83     string method;
84 
85     // id
86     @optional TId id;
87 
88     // Parameters
89     @optional Nullable!Json params;
90 
91     ///
92     this() @safe
93     {
94         params = Nullable!Json.init;
95     }
96 
97     /// Create a new json request.
98     static JsonRpcRequest!TId make(T)(TId id, string method, T params)
99     {
100         import vibe.data.json : serializeToJson;
101 
102         auto request = new JsonRpcRequest!TId();
103         request.id = id;
104         request.method = method;
105         request.params = serializeToJson!T(params);
106 
107         return request;
108     }
109 
110     /// ditto
111     static JsonRpcRequest!TId make(TId id, string method)
112     {
113         auto request = new JsonRpcRequest!TId();
114         request.id = id;
115         request.method = method;
116 
117         return request;
118     }
119 
120     ///
121     bool hasParams() @safe
122     {
123         return !params.isNull;
124     }
125 
126     /// Convert to Json.
127     Json toJson() const @safe
128     {
129         Json json = Json.emptyObject;
130         json["jsonrpc"] = "2.0";
131         json["method"] = method;
132         json["id"] = id;
133         if (!params.isNull)
134             json["params"] = params.get;
135         return json;
136     }
137 
138     /// Parse from Json.
139     static JsonRpcRequest fromJson(Json src) @safe
140     {
141         JsonRpcRequest request = new JsonRpcRequest();
142         request.jsonrpc = src["jsonrpc"].to!string;
143         request.method = src["method"].to!string;
144         if (src["id"].type != Json.Type.undefined)
145             request.id = src["id"].to!TId;
146         if (src["params"].type != Json.Type.undefined)
147             request.params = src["params"].nullable;
148         return request;
149     }
150 
151     /// Convert to a Json string.
152     override string toString() const @safe
153     {
154         return toJson().toString();
155     }
156 
157     /// Parse from a Json string.
158     static JsonRpcRequest fromString(string src) @safe
159     {
160         return fromJson(parseJson(src));
161     }
162 }
163 
164 ///
165 @("Test JsonRpcRequest")
166 unittest
167 {
168     import vibe.data.json;
169 
170     auto r1 = new JsonRpcRequest!int();
171     auto json = Json.emptyObject;
172     json["foo"] = 42;
173 
174     r1.method = "foo";
175     r1.toString().should == `{"method":"foo","id":0,"jsonrpc":"2.0"}`;
176 
177     auto r2 = new JsonRpcRequest!int();
178     r2.method = "foo";
179     r2.params = json;
180     r2.toString().should == `{"params":{"foo":42},"method":"foo","id":0,"jsonrpc":"2.0"}`;
181 
182     auto r3 = deserializeJson!(JsonRpcRequest!int)(r1.toString());
183     r3.id.should == r1.id;
184     r3.params.should == r1.params;
185     r3.method.should == r1.method;
186 
187     // string id:
188     auto r10 = new JsonRpcRequest!string();
189     r10.method = "foo";
190     r10.id = "bar";
191     r10.toString().should == `{"method":"foo","id":"bar","jsonrpc":"2.0"}`;
192 }
193 
194 /** A Json-Rpc response with an id of type TId.
195 */
196 class JsonRpcResponse(TId): IRpcResponse
197 {
198 public:
199     ///
200     string jsonrpc;
201 
202     /// id
203     Nullable!TId id;
204 
205     /// Optional result.
206     @optional Nullable!Json result;
207 
208     /// Optional error.
209     @optional Nullable!JsonRpcError error;
210 
211     ///
212     this() @safe nothrow
213     {
214         result = Nullable!Json.init;
215         error = Nullable!JsonRpcError.init;
216     }
217 
218     /// Tells if the response is an error.
219     bool isError() @safe nothrow
220     {
221         return !error.isNull;
222     }
223 
224     /// Tells if the response is in success.
225     bool isSuccess() @safe nothrow
226     {
227         return !result.isNull;
228     }
229 
230     /// Convert to Json.
231     Json toJson() const @safe
232     {
233         Json json = Json.emptyObject;
234         json["jsonrpc"] = "2.0";
235         // the id must be 'null' in case of parse error
236         if (!id.isNull)
237             json["id"] = id.get;
238         else
239             json["id"] = null;
240         if (!result.isNull)
241             json["result"] = result.get;
242         if (!error.isNull)
243             json["error"] = serializeToJson!(const(JsonRpcError))(error.get);
244         return json;
245     }
246 
247     /// Parse from Json.
248     static JsonRpcResponse fromJson(Json src) @safe
249     {
250         JsonRpcResponse request = new JsonRpcResponse();
251         request.jsonrpc = src["jsonrpc"].to!string;
252         if (src["id"].type != Json.Type.undefined)
253         {
254             if (src["id"].type == Json.Type.null_)
255                 request.id.nullify;
256             else
257                 request.id = src["id"].to!TId;
258         }
259         if (src["result"].type != Json.Type.undefined)
260             request.result = src["result"].nullable;
261         if (src["error"].type != Json.Type.undefined)
262             request.error = deserializeJson!JsonRpcError(src["error"]).nullable;
263         return request;
264     }
265 
266     /// Convert to Json string.
267     override string toString() const @safe
268     {
269         return toJson().toString();
270     }
271 
272     /// Parse from Json string.
273     static JsonRpcResponse fromString(string src) @safe
274     {
275         return fromJson(parseJson(src));
276     }
277 }
278 
279 ///
280 @("Test JsonRpcResponse")
281 unittest
282 {
283     auto r1 = new JsonRpcResponse!int();
284     Json json = "hello";
285     r1.result = json;
286     r1.id = 42;
287     r1.toString().should == `{"result":"hello","id":42,"jsonrpc":"2.0"}`;
288 
289     auto error = new JsonRpcError();
290     error.code = -32600;
291     error.message = "Invalid Request";
292 
293     auto r2 = new JsonRpcResponse!int();
294     r2.error = error;
295     r2.id = 1;
296     r2.toString().should == `{"error":{"code":-32600,"message":"Invalid Request"},"id":1,"jsonrpc":"2.0"}`;
297 }
298 
299 /// Encapsulate a json-rpc error response.
300 class JsonRpcMethodException: RpcException
301 {
302     import std.conv: to;
303 
304     this(JsonRpcError error) @safe
305     {
306         if (error.data.type == Json.Type.object)
307             super(error.message ~ " (" ~ to!string(error.code) ~ "): " ~ error.data.toString());
308         else
309             super(error.message ~ " (" ~ to!string(error.code) ~ ")");
310     }
311 
312     this(string msg) @safe
313     {
314         super(msg);
315     }
316 }
317 
318 /// Exception to be used inside rpc handler, to throw user defined json-rpc error.
319 class JsonRpcUserException: RpcException
320 {
321     import vibe.data.json;
322 
323 public:
324     int code;
325     Json data;
326 
327     this(T)(int code, string msg, T data) @safe
328     {
329         super(msg);
330         this.code = code;
331         this.data = serializeToJson(data);
332     }
333 }
334 
335 ///
336 package class RawJsonRpcClient(TId,
337     TReq: JsonRpcRequest!TId=JsonRpcRequest!TId,
338     TResp: JsonRpcResponse!TId=JsonRpcResponse!TId) :
339         RawRpcClient!(TId, TReq, TResp)
340 {
341     import core.time : Duration;
342     import vibe.data.json;
343     import vibe.stream.operations: readAllUTF8;
344     import vibe.core.stream: InputStream, OutputStream;
345 
346 private:
347     IIdGenerator!TId _idGenerator;
348     TResp[TId] _pendingResponse;
349 
350 public:
351     this(OutputStream ostream, InputStream istream) @safe
352     {
353         super(ostream, istream);
354         _idGenerator = new IdGenerator!TId();
355     }
356 
357     /** Send a request with an auto-generated id.
358         Throws:
359             JSONException
360     */
361     TResp sendRequestAndWait(TReq request, Duration timeout = Duration.max()) @safe
362     {
363         request.id = _idGenerator.getNextId();
364         _ostream.write(request.toString());
365         return waitForResponse(request.id, timeout);
366     }
367 
368     // Process the input stream once.
369     override void tick() @safe
370     {
371         string rawJson = _istream.readAllUTF8();
372         Json json = parseJson(rawJson);
373 
374         void process(Json jsonObject)
375         {
376             auto response = deserializeJson!TResp(jsonObject);
377             _pendingResponse[response.id] = response;
378         }
379 
380         // batch of commands
381         if (json.type == Json.Type.array)
382         {
383             foreach(object; json.byValue)
384             {
385                 process(object);
386             }
387         }
388         else
389         {
390             process(json);
391         }
392     }
393 
394 protected:
395     TResp waitForResponse(TId id, Duration timeout) @safe
396     {
397         import std.conv: to;
398 
399         // check if response already received
400         if (id in _pendingResponse)
401         {
402             scope(exit) _pendingResponse.remove(id);
403             return _pendingResponse[id];
404         }
405         else
406         {
407             throw new RpcTimeoutException("No reponse");
408         }
409 
410     }
411 }
412 
413 /// Represents a Json rpc client.
414 alias IJsonRpcClient(TId, TReq: JsonRpcRequest!TId, TResp: JsonRpcResponse!TId) = IRpcClient!(TId, TReq, TResp);
415 
416 /// An http json-rpc client
417 package alias HttpJsonRpcClient(TId,
418     TReq: JsonRpcRequest!TId=JsonRpcRequest!TId,
419     TResp: JsonRpcResponse!TId=JsonRpcResponse!TId) = HttpRpcClient!(TId, TReq, TResp);
420 
421 ///
422 package class TcpJsonRpcClient(TId,
423     TReq: JsonRpcRequest!TId=JsonRpcRequest!TId,
424     TResp: JsonRpcResponse!TId=JsonRpcResponse!TId): IJsonRpcClient!(TId, TReq, TResp)
425 {
426     import vibe.core.net : TCPConnection, TCPListener, connectTCP;
427     import vibe.stream.operations : readLine;
428     import core.time;
429     import std.conv: to;
430 
431 private:
432     string _host;
433     ushort _port;
434     bool _connected;
435     IIdGenerator!TId _idGenerator;
436     JsonRpcResponse!TId[TId] _pendingResponse;
437     TCPConnection _conn;
438     RpcInterfaceSettings _settings;
439 
440 public:
441     @property bool connected() { return _connected; }
442 
443     this(string host, ushort port, RpcInterfaceSettings settings)
444     {
445         _host = host;
446         _port = port;
447         _settings = settings;
448         this.connect();
449         _idGenerator = new IdGenerator!TId();
450     }
451 
452     bool connect() @safe nothrow
453     in {
454         assert(!_connected);
455     }
456     do {
457         try {
458             _conn = connectTCP(_host, _port);
459             _connected = true;
460         } catch (Exception e) {
461             _connected = false;
462         }
463 
464         return _connected;
465     }
466 
467     /// auto-generate id
468     TResp sendRequestAndWait(TReq request, core.time.Duration timeout = core.time.Duration.max()) @safe
469     {
470         if (!_connected)
471             throw new RpcNotConnectedException("tcp client not connected ! call connect() first.");
472 
473         request.id = _idGenerator.getNextId();
474         logTrace("tcp send request: %s", request);
475         _conn.write(request.toString() ~ _settings.linesep);
476 
477         if (_conn.waitForData(timeout)) {
478             char[] raw = cast(char[]) _conn.readLine(size_t.max, _settings.linesep);
479             string json = to!string(raw);
480             logTrace("tcp server request response: %s", json);
481             auto response = deserializeJson!TResp(json);
482             return response;
483         }
484         else
485             throw new RpcTimeoutException("waitForData timeout");
486     }
487 
488     @disable void tick() @safe {}
489 }
490 
491 /// Represents a Json rpc server.
492 alias IJsonRpcServer(TId,
493     TReq: JsonRpcRequest!TId=JsonRpcRequest!TId,
494     TResp: JsonRpcResponse!TId=JsonRpcResponse!TId) =
495         IRpcServer!(TId, TReq, TResp);
496 
497 ///
498 alias JsonRpcRequestHandler(TId, TReq=JsonRpcRequest!TId, TResp=JsonRpcResponse!TId) =
499     RpcRequestHandler!(TReq, TResp);
500 
501 ///
502 class RawJsonRpcServer(TId, TReq=JsonRpcRequest!TId, TResp=JsonRpcResponse!TId):
503     RawRpcServer!(TId, TReq, TResp),
504     IRpcServerOutput!TResp
505 {
506     import vibe.stream.operations: readAllUTF8;
507     import vibe.core.stream: InputStream, OutputStream;
508 
509     alias RequestHandler = JsonRpcRequestHandler!(TId, TReq, TResp);
510 
511 private:
512     RequestHandler[string] _requestHandler;
513 
514 public:
515     this(OutputStream ostream, InputStream istream) @safe
516     {
517         super(ostream, istream);
518     }
519 
520     void registerRequestHandler(string method, RequestHandler handler)
521     {
522         _requestHandler[method] = handler;
523     }
524 
525     void sendResponse(TResp reponse) @safe
526     {
527         _ostream.write(reponse.toString());
528     }
529 
530     void tick() @safe
531     {
532         string rawJson = _istream.readAllUTF8();
533         Json json = parseJson(rawJson);
534 
535         void process(Json jsonObject)
536         {
537             auto request = deserializeJson!TReq(jsonObject);
538             if (request.method in _requestHandler)
539             {
540                 _requestHandler[request.method](request, this);
541             }
542         }
543 
544         // batch of commands
545         if (json.type == Json.Type.array)
546         {
547             foreach(object; json.byValue)
548             {
549                 process(object);
550             }
551         }
552         else
553         {
554             process(json);
555         }
556     }
557 }
558 
559 /// An http json-rpc client
560 class HttpJsonRpcServer(TId,
561     TReq: JsonRpcRequest!TId=JsonRpcRequest!TId,
562     TResp: JsonRpcResponse!TId=JsonRpcResponse!TId):
563         HttpRpcServer!(TId, TReq, TResp)
564 {
565     import vibe.data.json: JSONException;
566     import vibe.http.router: URLRouter;
567 
568     this(URLRouter router, string path)
569     {
570         super(router, path);
571     }
572 
573     override RpcRespHandler createReponseHandler(HTTPServerResponse res)
574     @safe nothrow {
575         return new class RpcRespHandler
576         {
577             override void sendResponse(TResp reponse) @safe nothrow
578             {
579                 logTrace("post request response: %s", reponse);
580                 try {
581                     res.writeJsonBody(reponse.toJson());
582                 } catch (Exception e) {
583                     logCritical("unable to send response: %s", e.msg);
584                     // TODO: add a delgate to allow the user to handle error
585                 }
586             }
587         };
588     }
589 
590     @disable void tick() @safe {}
591 
592     protected override TResp buildResponseFromException(Exception e) @safe nothrow
593     {
594         auto response = new TResp();
595         if (is(typeof(e) == JSONException))
596         {
597             response.error = new JsonRpcError(JsonRpcError.StdCodes.parseError);
598             return response;
599         }
600         else
601         {
602             response.error = new JsonRpcError(JsonRpcError.StdCodes.internalError);
603             return response;
604         }
605     }
606 
607     void registerInterface(I)(I instance, RpcInterfaceSettings settings = null)
608     {
609         import std.algorithm : filter, map, all;
610         import std.array : array;
611         import std.range : front;
612         import vibe.internal.meta.uda : findFirstUDA;
613 
614         alias Info = InterfaceInfo!I;
615 
616         foreach (i, Func; Info.Methods) {
617             enum methodNameAtt = findFirstUDA!(RpcMethodAttribute, Func);
618             enum smethod = Info.staticMethods[i];
619 
620             auto handler = jsonRpcMethodHandler!(TId, TReq, TResp, Func, i, I)(instance);
621 
622             // select rpc name (attribute or function name):
623             static if (methodNameAtt.found)
624                 this.registerRequestHandler(methodNameAtt.value.method, handler);
625             else
626                 this.registerRequestHandler(smethod.name, handler);
627 
628         }
629     }
630 }
631 
632 ///
633 class TcpJsonRpcServer(TId,
634     TReq: JsonRpcRequest!TId=JsonRpcRequest!TId,
635     TResp: JsonRpcResponse!TId=JsonRpcResponse!TId): IJsonRpcServer!(TId, TReq, TResp)
636 {
637     import vibe.core.net : TCPConnection, TCPListener, listenTCP;
638     import vibe.stream.operations : readLine;
639     import std.conv : to;
640 
641 private:
642     class ResponseWriter: JsonRpcRespHandler
643     {
644         private TCPConnection _conn;
645 
646         this(TCPConnection conn)
647         {
648             _conn = conn;
649         }
650 
651         void sendResponse(TResp reponse)
652         @safe {
653             logTrace("tcp request response: %s", reponse);
654             try {
655                 _conn.write(reponse.toString() ~ _settings.linesep);
656             } catch (Exception e) {
657                 logTrace("unable to send response: %s", e.msg);
658                 // TODO: add a delgate to allow the user to handle error
659             }
660         }
661     }
662 
663     class TCPClient
664     {
665     private:
666         TCPConnection _connection;
667         JsonRpcRequestHandler!(TId, TReq, TResp)[string] _requestHandler;
668 
669     public /*properties*/:
670         @property auto conn() { return _connection; }
671 
672     public:
673         this(TCPConnection connection)
674         {
675             _connection = connection;
676         }
677 
678         void run() @safe nothrow
679         {
680             try {
681                 auto writer = new ResponseWriter(_connection);
682 
683                 while (!_connection.empty) {
684                     auto json = cast(const(char)[])_connection.readLine(size_t.max, _settings.linesep);
685                     logTrace("tcp request received: %s", json);
686 
687                     this.process(json.to!string, writer);
688                 }
689             } catch (Exception e) {
690                 logError("Failed to read from client: %s", e.msg);
691                 if (_settings !is null)
692                     _settings.errorHandler(e);
693             }
694         }
695 
696         void process(string data, JsonRpcRespHandler respHandler) @safe
697         {
698             Json json = parseJson(data);
699 
700             void process(Json jsonObject)
701             {
702                 auto request = deserializeJson!TReq(jsonObject);
703                 if (request.method in _requestHandler)
704                 {
705                     _requestHandler[request.method](request, respHandler);
706                 }
707             }
708 
709             // batch of commands
710             if (json.type == Json.Type.array)
711             {
712                 foreach(object; json.byValue)
713                 {
714                     process(object);
715                 }
716             }
717             else
718             {
719                 process(json);
720             }
721         }
722 
723         void registerRequestHandler(string method, JsonRpcRequestHandler!(TId, TReq, TResp) handler) @safe nothrow
724         {
725             _requestHandler[method] = handler;
726         }
727 
728         void registerInterface(I)(I instance) @safe nothrow
729         {
730             import std.algorithm : filter, map, all;
731             import std.array : array;
732             import std.range : front;
733 
734             alias Info = InterfaceInfo!I;
735 
736             foreach (i, Func; Info.Methods) {
737                 enum smethod = Info.staticMethods[i];
738 
739                 // normal handler
740                 auto handler = jsonRpcMethodHandler!(TId, TReq, TResp, Func, i)(instance);
741 
742                 this.registerRequestHandler(smethod.name, handler);
743             }
744 
745         }
746     }
747 
748     alias FactoryDel(I) = I delegate(TCPConnection);
749     alias NewClientDel = void delegate(TCPClient) @safe nothrow;
750     alias JsonRpcRespHandler = IRpcServerOutput!TResp;
751     JsonRpcRequestHandler!(TId, TReq, TResp)[string] _requestHandler;
752     RpcInterfaceSettings _settings;
753     NewClientDel[] _newClientDelegates;
754 
755 public:
756     this(ushort port, RpcInterfaceSettings settings = new RpcInterfaceSettings())
757     {
758         _settings = settings;
759 
760         listenTCP(port, (conn) @safe nothrow {
761             logTrace("new client: %s", conn);
762             auto client = new TCPClient(conn);
763 
764             foreach(newClientDel; _newClientDelegates)
765                     newClientDel(client);
766 
767             client.run();
768 
769             conn.close();
770         });
771     }
772 
773     void registerInterface(I)(I instance)
774     {
775         _newClientDelegates ~= (client) @safe nothrow {
776             client.registerInterface!I(instance);
777         };
778     }
779 
780     void registerInterface(I)(FactoryDel!I factory)
781     {
782         _newClientDelegates ~= (client) {
783             // instanciate an API for each client:
784             I instance = factory(client.conn);
785 
786             client.registerInterface!I(instance);
787         };
788     }
789 
790     @disable void tick() @safe {}
791 
792     void registerRequestHandler(string method, JsonRpcRequestHandler!(TId, TReq, TResp) handler)
793     {
794         _requestHandler[method] = handler;
795     }
796 
797     void process(string data, JsonRpcRespHandler respHandler)
798     {
799         Json json = parseJson(data);
800 
801         void process(Json jsonObject)
802         {
803             auto request = deserializeJson!TReq(jsonObject);
804             if (request.method in _requestHandler)
805             {
806                 _requestHandler[request.method](request, respHandler);
807             }
808         }
809 
810         // batch of commands
811         if (json.type == Json.Type.array)
812         {
813             foreach(object; json.byValue)
814             {
815                 process(object);
816             }
817         }
818         else
819         {
820             process(json);
821         }
822     }
823 }
824 
825 /// Return an handler to match a json-rpc request on an interface method.
826 package JsonRpcRequestHandler!(TId, TReq, TResp) jsonRpcMethodHandler(TId, TReq, TResp, alias Func, size_t n, T)(T inst)
827 {
828     import std.traits;
829     import std.meta : AliasSeq;
830     import std..string : format;
831     import vibe.utils..string : sanitizeUTF8;
832     import vibe.internal.meta.funcattr : IsAttributedParameter, computeAttributedParameterCtx;
833     import vibe.internal.meta.traits : derivedMethod;
834     import vibe.internal.meta.uda : findFirstUDA;
835 
836     enum Method = __traits(identifier, Func);
837     alias PTypes = ParameterTypeTuple!Func;
838     alias PDefaults = ParameterDefaultValueTuple!Func;
839     alias CFuncRaw = derivedMethod!(T, Func);
840     static if (AliasSeq!(CFuncRaw).length > 0) alias CFunc = CFuncRaw;
841     else alias CFunc = Func;
842     alias RT = ReturnType!(FunctionTypeOf!Func);
843     static const sroute = InterfaceInfo!T.staticMethods[n];
844     enum objectParamAtt = findFirstUDA!(RpcMethodObjectParams, Func);
845 
846     void handler(TReq req, IRpcServerOutput!TResp serv) @safe
847     {
848         auto response = new TResp();
849         response.id = req.id;
850         PTypes params;
851 
852         // build a custom json error object tobe sent in json rpc error response.
853         Json buildErrorData(string details)
854         @safe {
855             auto json = Json.emptyObject;
856             json["details"] = details;
857             json["request"] = req.toJson();
858             return json;
859         }
860 
861         try {
862             // check params consistency beetween rpc-request and function parameters
863             static if (PTypes.length > 1 && !objectParamAtt.found)
864             {
865                 // we expect a json array
866                 if (req.params.type != Json.Type.array)
867                 {
868                     response.error = new JsonRpcError(JsonRpcError.StdCodes.invalidParams);
869                     response.error.data = buildErrorData("Expected a json array for params");
870                     serv.sendResponse(response);
871                     return;
872                 }
873                 // req.params is a json array
874                 else if (req.params.length != PTypes.length)
875                 {
876                     response.error = new JsonRpcError(JsonRpcError.StdCodes.invalidParams);
877                     response.error.data = buildErrorData("Missing params");
878                     serv.sendResponse(response);
879                     return;
880                 }
881             }
882             else if (PTypes.length > 1 && objectParamAtt.found)
883             {
884                 // in object mode, check param count
885                 if (req.params.type == Json.Type.object)
886                 {
887                     if (req.params.length != PTypes.length)
888                     {
889                         response.error = new JsonRpcError(JsonRpcError.StdCodes.invalidParams);
890                         response.error.data = buildErrorData("Missing entry in params");
891                         serv.sendResponse(response);
892                         return;
893                     }
894                 }
895             }
896 
897             foreach (i, PT; PTypes) {
898                 enum sparam = sroute.parameters[i];
899 
900                 static if (!objectParamAtt.found)
901                 {
902                     enum pname = sparam.name;
903                     auto fieldname = sparam.name;
904 
905                     params[i] = deserializeJson!PT(req.params[i]);
906                 }
907                 else
908                 {
909                     enum pname = sparam.name;
910 
911                     if (pname in objectParamAtt.value.names)
912                         params[i] = deserializeJson!PT(req.params[objectParamAtt.value.names[pname]]);
913                     else
914                         params[i] = deserializeJson!PT(req.params[pname]);
915                 }
916             }
917         } catch (Exception e) {
918             // handleException(e, HTTPStatus.badRequest);
919             return;
920         }
921 
922         try {
923             import vibe.internal.meta.funcattr;
924 
925             static if (!__traits(compiles, () @safe { __traits(getMember, inst, Method)(params); }))
926                 pragma(msg, "Non-@safe methods are deprecated in REST interfaces - Mark "~T.stringof~"."~Method~" as @safe.");
927 
928             static if (is(RT == void)) {
929                 // TODO: return null
930             } else {
931                 auto ret = () @trusted { return __traits(getMember, inst, Method)(params); } (); // TODO: remove after deprecation period
932 
933                 static if (!__traits(compiles, () @safe { evaluateOutputModifiers!Func(ret, req, res); } ()))
934                     pragma(msg, "Non-@safe @after evaluators are deprecated - annotate @after evaluator function for "~T.stringof~"."~Method~" as @safe.");
935 
936                 static if (!__traits(compiles, () @safe { res.writeJsonBody(ret); }))
937                     pragma(msg, "Non-@safe serialization of REST return types deprecated - ensure that "~RT.stringof~" is safely serializable.");
938                 () @trusted {
939                     // build reponse
940                     response.id = req.id;
941                     response.result = serializeToJson(ret);
942                     serv.sendResponse(response);
943                 }();
944             }
945         }
946         // catch user-defined json-rpc errors.
947         catch (JsonRpcUserException e)
948         {
949             response.error = new JsonRpcError();
950             response.error.code = e.code;
951             response.error.message = e.msg;
952             response.error.data = e.data;
953             serv.sendResponse(response);
954             return;
955         }
956         catch (Exception e) {
957             response.error = new JsonRpcError();
958             response.error.code = 0;
959             response.error.message = e.msg;
960             serv.sendResponse(response);
961             return;
962         }
963     }
964 
965     return &handler;
966 }
967 
968 /** Base class for creating a Json RPC automatic client.
969 */
970 package class JsonRpcAutoClient(I,
971     TId,
972     TReq: JsonRpcRequest!TId=JsonRpcRequest!TId,
973     TResp: JsonRpcResponse!TId=JsonRpcResponse!TId)
974 {
975     import std.traits;
976 
977 protected:
978     IRpcClient!(TId, TReq, TResp) _client;
979     RpcInterfaceSettings _settings;
980 
981     ReturnType!Func executeMethod(alias Func, ARGS...)(ARGS args) @safe
982     {
983         import vibe.internal.meta.uda : findFirstUDA;
984         import std.traits;
985         import std.array : appender;
986         import core.time;
987         import vibe.data.json;
988 
989         // retrieve some compile time informations
990         // alias Info  = RpcInterface!I;
991         alias RT    = ReturnType!Func;
992         alias PTT   = ParameterTypeTuple!Func;
993         alias PTN   = ParameterIdentifierTuple!Func;
994 
995         enum objectParamAtt = findFirstUDA!(RpcMethodObjectParams, Func);
996         enum methodNameAtt = findFirstUDA!(RpcMethodAttribute, Func);
997         // parameters are rendered as array if annotated with @rpcArrayParams
998         enum bool paramsAsArray = hasRpcArrayParams!Func || (PTT.length > 1);
999 
1000         try
1001         {
1002             auto jsonParams = Json.undefined;
1003 
1004             // Render params as unique param or array
1005             static if (!objectParamAtt.found)
1006             {
1007                 // if several params, then build an a json array
1008                 static if (paramsAsArray)
1009                     jsonParams = Json.emptyArray;
1010 
1011                 // fill the json array or the unique value
1012                 static foreach (i, PT; PTT) {
1013                     static if (paramsAsArray)
1014                         jsonParams.appendArrayElement(serializeToJson(args[i]));
1015                     else
1016                         jsonParams = serializeToJson(args[i]);
1017                 }
1018             }
1019             // render params as a json object by using the param name
1020             // for the key or the uda if exists
1021             else
1022             {
1023                 jsonParams = Json.emptyObject;
1024 
1025                 // fill object
1026                 static foreach (i, PT; PTT) {
1027                     if (PTN[i] in objectParamAtt.value.names)
1028                         jsonParams[objectParamAtt.value.names[PTN[i]]] = serializeToJson(args[i]);
1029                     else
1030                         jsonParams[PTN[i]] = serializeToJson(args[i]);
1031                 }
1032             }
1033 
1034 
1035             static if (!is(RT == void))
1036                 RT jret;
1037 
1038             // create a json-rpc request
1039             auto request = new TReq();
1040 
1041             static if (methodNameAtt.found)
1042                 request.method = methodNameAtt.value.method;
1043             else
1044                 request.method = __traits(identifier, Func);
1045 
1046             request.params = jsonParams; // set rpc call params
1047 
1048             auto response = _client.sendRequestAndWait(request, _settings.responseTimeout); // send packet and wait
1049 
1050             if (response.isError())
1051             {
1052                 throw new JsonRpcMethodException(response.error.get);
1053             }
1054 
1055             // void return type
1056             static if (is(RT == void))
1057             {
1058 
1059             }
1060             else
1061             {
1062                 return deserializeJson!RT(response.result.get);
1063             }
1064         }
1065         catch (JSONException e)
1066         {
1067             throw new RpcParsingException(e.msg, e);
1068         }
1069         catch (Exception e)
1070         {
1071             throw new RpcException(e.msg, e);
1072         }
1073     }
1074 
1075 public:
1076     this(IRpcClient!(TId, TReq, TResp) client, RpcInterfaceSettings settings)
1077     {
1078         _client = client;
1079         _settings = settings;
1080     }
1081 
1082     @property auto client() @safe { return _client; }
1083 }
1084 
1085 ///
1086 package class JsonRpcAutoAttributeClient(I) : I
1087 {
1088     import autointf;
1089 
1090 private:
1091     // compile-time
1092     // Extract RPC id type from attribute:
1093     static if (hasUDA!(I, RpcIdTypeAttribute!int))
1094         alias TId = int;
1095     else static if (hasUDA!(I, RpcIdTypeAttribute!string))
1096         alias TId = string;
1097     else
1098         alias TId = int;
1099 
1100 protected:
1101     alias TReq = JsonRpcRequest!TId;
1102     alias TResp = JsonRpcResponse!TId;
1103     alias AutoClient(I) = JsonRpcAutoClient!(I, TId, TReq, TResp);
1104     alias RpcClient = IRpcClient!(TId, TReq, TResp);
1105     AutoClient!I _autoClient;
1106 
1107     pragma(inline, true)
1108     ReturnType!Func executeMethod(alias Func, ARGS...)(ARGS args) @safe
1109     {
1110         return _autoClient.executeMethod!(Func, ARGS)(args);
1111     }
1112 
1113 public:
1114     this(RpcClient client, RpcInterfaceSettings settings) @safe
1115     {
1116         _autoClient = new AutoClient!I(client, settings);
1117     }
1118 
1119     pragma(inline, true)
1120     @property auto client() @safe { return _autoClient.client; }
1121 
1122     mixin(autoImplementMethods!(I, executeMethod)());
1123 }
1124 
1125 /** Used to create an auto-implemented raw Rpc client from an interface.
1126 
1127     ---
1128     auto input = `{"jsonrpc":"2.0","id":2,"result":` ~ to!string(getValue!int) ~ "}";
1129     auto istream = createMemoryStream(input.toBytes());
1130     auto ostream = createMemoryOutputStream();
1131 
1132     auto api = new RawJsonRpcAutoClient!IAPI(ostream, istream);
1133 
1134     // test the client side:
1135     // inputstream not processed: timeout
1136     api.add(1, 2).shouldThrowExactly!RpcException;
1137     ostream.str.should.be == JsonRpcRequest!int.make(1, "add", [1, 2]).toString();
1138 
1139     // process input stream
1140     api.client.tick();
1141 
1142     // client must send a reponse
1143     api.add(1, 2).should.be == getValue!int;
1144     ---
1145 */
1146 class RawJsonRpcAutoClient(I) : JsonRpcAutoAttributeClient!I
1147 {
1148     import vibe.core.stream: InputStream, OutputStream;
1149 
1150 public:
1151     this(OutputStream ostream, InputStream istream, RpcInterfaceSettings settings = new RpcInterfaceSettings()) @safe
1152     {
1153         super(new RawJsonRpcClient!TId(ostream, istream), settings);
1154     }
1155 }
1156 
1157 /** Used to create an auto-implemented Http Rpc client from an interface.
1158 
1159     ---
1160     interface IAPI
1161     {
1162         void send(string data);
1163     }
1164 
1165     auto client = new HttpJsonRpcAutoClient!IAPI("http://127.0.0.1:8080/rpc_2");
1166     client.send("data");
1167     ---
1168 */
1169 class HttpJsonRpcAutoClient(I) : JsonRpcAutoAttributeClient!I
1170 {
1171 public:
1172     this(string host, RpcInterfaceSettings settings = new RpcInterfaceSettings()) @safe
1173     {
1174         super(new HttpJsonRpcClient!TId(host), settings);
1175     }
1176 }
1177 
1178 /** Used to create an auto-implemented Tcp Rpc client from an interface.
1179 
1180     ---
1181     interface IAPI
1182     {
1183         void send(string data);
1184     }
1185 
1186     auto client = new TcpJsonRpcAutoClient!IAPI("http://127.0.0.1:8080/rpc_2");
1187     client.send("data");
1188     ---
1189 */
1190 class TcpJsonRpcAutoClient(I) : JsonRpcAutoAttributeClient!I
1191 {
1192 public:
1193     this(string host, ushort port, RpcInterfaceSettings settings = new RpcInterfaceSettings()) @safe
1194     {
1195         super(new TcpJsonRpcClient!TId(host, port, settings), settings);
1196     }
1197 }