1 /*
2  *Copyright (C) 2018 Laurent Tréguier
3  *
4  *This file is part of DLS.
5  *
6  *DLS is free software: you can redistribute it and/or modify
7  *it under the terms of the GNU General Public License as published by
8  *the Free Software Foundation, either version 3 of the License, or
9  *(at your option) any later version.
10  *
11  *DLS is distributed in the hope that it will be useful,
12  *but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *GNU General Public License for more details.
15  *
16  *You should have received a copy of the GNU General Public License
17  *along with DLS.  If not, see <http://www.gnu.org/licenses/>.
18  *
19  */
20 
21 module dls.protocol.jsonrpc;
22 
23 import dls.util.i18n : Tr;
24 import std.json : JSONValue;
25 import std.typecons :  Tuple, tuple;
26 import dls.util.nullable: Nullable;
27 private enum jsonrpcVersion = "2.0";
28 private enum jsonrpcSeparator = "\r\n\r\n";
29 
30 abstract class Message
31 {
32     string jsonrpc = jsonrpcVersion;
33 }
34 
35 class RequestMessage : Message
36 {
37     JSONValue id;
38     string method;
39     Nullable!JSONValue params;
40 }
41 
42 class ResponseMessage : Message
43 {
44     JSONValue id;
45     Nullable!JSONValue result;
46     Nullable!ResponseError error;
47 }
48 
49 class ResponseError
50 {
51     long code;
52     string message;
53     Nullable!JSONValue data;
54 
55     static ResponseError fromErrorCode(ErrorCodes errorCode, JSONValue data)
56     {
57         import dls.util.i18n : tr;
58         import dls.util.nullable: nullable;
59 
60         auto response = new ResponseError();
61         response.code = errorCode[0];
62         response.message = tr(errorCode[1]);
63         response.data = data;
64         return response.nullable;
65     }
66 }
67 
68 class NotificationMessage : Message
69 {
70     string method;
71     Nullable!JSONValue params;
72 }
73 
74 enum ErrorCodes : Tuple!(long, Tr)
75 {
76     //dfmt off
77     parseError = tuple(-32_700L, Tr.app_rpc_errorCodes_parseError),
78     invalidRequest = tuple(-32_600L, Tr.app_rpc_errorCodes_invalidRequest),
79     methodNotFound = tuple(-32_601L, Tr.app_rpc_errorCodes_methodNotFound),
80     invalidParams = tuple(-32_602L, Tr.app_rpc_errorCodes_invalidParams),
81     internalError = tuple(-32_603L, Tr.app_rpc_errorCodes_internalError),
82     serverErrorStart = tuple(-32_099L, Tr._),
83     serverErrorEnd = tuple(-32_000L, Tr._),
84     serverNotInitialized = tuple(-32_002L, Tr.app_rpc_errorCodes_serverNotInitialized),
85     unknownErrorCode = tuple(-32_001L, Tr.app_rpc_errorCodes_unknownErrorCode),
86     requestCancelled = tuple(-32_800L, Tr.app_rpc_errorCodes_requestCancelled),
87     //dfmt on
88 }
89 
90 class CancelParams
91 {
92     JSONValue id;
93 }
94 
95 void sendError(ErrorCodes error, RequestMessage request, JSONValue data)
96 {
97     import dls.util.nullable: nullable;
98     if (request !is null)
99     {
100         send(request.id, Nullable!JSONValue(), ResponseError.fromErrorCode(error, data).nullable);
101     }
102 }
103 
104 /// Sends a request or a notification message.
105 string send(string method, Nullable!JSONValue params = Nullable!JSONValue())
106 {
107     import dls.protocol.handlers : hasResponseHandler, pushHandler;
108     import dls.protocol.logger : logger;
109     import std.uuid : randomUUID;
110 
111     if (hasResponseHandler(method))
112     {
113         immutable id = randomUUID().toString();
114         pushHandler(id, method);
115         logger.log(`Sending request "%s": %s`, id, method);
116         send!RequestMessage(JSONValue(id), method, params, Nullable!ResponseError());
117         return id;
118     }
119 
120     logger.log("Sending notification: %s", method);
121     send!NotificationMessage(JSONValue(), method, params, Nullable!ResponseError());
122     return null;
123 }
124 
125 /// Sends a request or a notification message.
126 string send(T)(string method, T params) if (!is(T : Nullable!JSONValue))
127 {
128     import dls.util.json : convertToJSON;
129     import dls.util.nullable: nullable;
130 
131     return send(method, convertToJSON(params));
132 }
133 
134 /// Sends a response message.
135 void send(JSONValue id, Nullable!JSONValue result,
136         Nullable!ResponseError error = Nullable!ResponseError())
137 {
138     import dls.protocol.logger : logger;
139 
140     logger.log("Sending response with %s for request %s", error.isNull ? "result" : "error", id);
141     send!ResponseMessage(id, null, result, error);
142 }
143 
144 /// Sends a response message.
145 private void send(T : Message)(JSONValue id, string method,
146         Nullable!JSONValue payload, Nullable!ResponseError error)
147 {
148     import std.meta : AliasSeq;
149     import std.traits : select;
150 
151     auto message = new T();
152 
153     __traits(getMember, message, select!(__traits(hasMember, T, "params"))("params", "result")) = payload;
154 
155     foreach (member; AliasSeq!("id", "method", "error"))
156     {
157         static if (__traits(hasMember, T, member))
158         {
159             mixin("message." ~ member ~ " = " ~ member ~ ";");
160         }
161     }
162 
163     send(message);
164 }
165 
166 private void send(T : Message)(T m)
167 {
168     import dls.util.communicator : communicator;
169     import dls.util.json : convertToJSON;
170     import std.conv : text;
171 
172     auto message = convertToJSON(m);
173     auto messageString = message.get().toString();
174 
175     synchronized
176     {
177         communicator.write(
178             "Content-Length: "
179             ~ text(messageString.length)
180             ~ jsonrpcSeparator
181             ~ messageString
182         );
183 
184         communicator.flush();
185     }
186 }