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 : Nullable, Tuple, tuple;
26 
27 private enum jsonrpcVersion = "2.0";
28 private enum eol = "\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 std.typecons : 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 std.typecons : nullable;
98 
99     if (request !is null)
100     {
101         send(request.id, Nullable!JSONValue(), ResponseError.fromErrorCode(error, data).nullable);
102     }
103 }
104 
105 /// Sends a request or a notification message.
106 string send(string method, Nullable!JSONValue params = Nullable!JSONValue())
107 {
108     import dls.protocol.handlers : hasResponseHandler, pushHandler;
109     import dls.protocol.logger : logger;
110     import std.uuid : randomUUID;
111 
112     if (hasResponseHandler(method))
113     {
114         immutable id = randomUUID().toString();
115         pushHandler(id, method);
116         logger.log(`Sending request "%s": %s`, id, method);
117         send!RequestMessage(JSONValue(id), method, params, Nullable!ResponseError());
118         return id;
119     }
120 
121     logger.log("Sending notification: %s", method);
122     send!NotificationMessage(JSONValue(), method, params, Nullable!ResponseError());
123     return null;
124 }
125 
126 /// Sends a request or a notification message.
127 string send(T)(string method, T params) if (!is(T : Nullable!JSONValue))
128 {
129     import dls.util.json : convertToJSON;
130     import std.typecons : nullable;
131 
132     return send(method, convertToJSON(params).nullable);
133 }
134 
135 /// Sends a response message.
136 void send(JSONValue id, Nullable!JSONValue result,
137         Nullable!ResponseError error = Nullable!ResponseError())
138 {
139     import dls.protocol.logger : logger;
140 
141     logger.log("Sending response with %s for request %s", error.isNull ? "result" : "error", id);
142     send!ResponseMessage(id, null, result, error);
143 }
144 
145 /// Sends a response message.
146 private void send(T : Message)(JSONValue id, string method,
147         Nullable!JSONValue payload, Nullable!ResponseError error)
148 {
149     import std.meta : AliasSeq;
150     import std.traits : select;
151 
152     auto message = new T();
153 
154     __traits(getMember, message, select!(__traits(hasMember, T, "params"))("params", "result")) = payload;
155 
156     foreach (member; AliasSeq!("id", "method", "error"))
157     {
158         static if (__traits(hasMember, T, member))
159         {
160             mixin("message." ~ member ~ " = " ~ member ~ ";");
161         }
162     }
163 
164     send(message);
165 }
166 
167 private void send(T : Message)(T m)
168 {
169     import dls.util.communicator : communicator;
170     import dls.util.json : convertToJSON;
171     import std.conv : text;
172     import std.utf : toUTF8;
173 
174     auto message = convertToJSON(m);
175     auto messageString = message.get().toString().toUTF8();
176 
177     synchronized
178     {
179         foreach (chunk; ["Content-Length: ", text(messageString.length), eol, eol, messageString])
180         {
181             communicator.write(chunk);
182         }
183 
184         communicator.flush();
185     }
186 }