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 std.json : JSONValue;
24 import std.typecons : Nullable, Tuple, tuple;
25 
26 private enum jsonrpcVersion = "2.0";
27 private enum eol = "\r\n";
28 
29 abstract class Message
30 {
31     string jsonrpc = jsonrpcVersion;
32 }
33 
34 class RequestMessage : Message
35 {
36     JSONValue id;
37     string method;
38     Nullable!JSONValue params;
39 }
40 
41 class ResponseMessage : Message
42 {
43     JSONValue id;
44     Nullable!JSONValue result;
45     Nullable!ResponseError error;
46 }
47 
48 class ResponseError
49 {
50     long code;
51     string message;
52     Nullable!JSONValue data;
53 
54     static ResponseError fromErrorCode(ErrorCodes errorCode, JSONValue data)
55     {
56         import std.typecons : nullable;
57 
58         auto response = new ResponseError();
59         response.code = errorCode[0];
60         response.message = errorCode[1];
61         response.data = data;
62         return response.nullable;
63     }
64 }
65 
66 class NotificationMessage : Message
67 {
68     string method;
69     Nullable!JSONValue params;
70 }
71 
72 enum ErrorCodes : Tuple!(long, string)
73 {
74     parseError = tuple(-32_700L, "Parse error"),
75     invalidRequest = tuple(-32_600L,
76             "Invalid Request"),
77     methodNotFound = tuple(
78             -32_601L, "Method not found"),
79     invalidParams = tuple(-32_602L,
80             "Invalid params"),
81     internalError = tuple(-32_603L,
82             "Internal error"),
83     serverNotInitialized = tuple(-32_202L,
84             "Server not initialized"),
85     unknownErrorCode = tuple(
86             -32_201L, "Unknown error"),
87     requestCancelled = tuple(-32_800L, "Request cancelled")
88 }
89 
90 class CancelParams
91 {
92     JSONValue id;
93 }
94 
95 class InvalidParamsException : Exception
96 {
97     this(string msg)
98     {
99         super("Invalid parameters: " ~ msg);
100     }
101 }
102 
103 void sendError(ErrorCodes error, RequestMessage request, JSONValue data)
104 {
105     import std.typecons : nullable;
106 
107     if (request !is null)
108     {
109         send(request.id, Nullable!JSONValue(), ResponseError.fromErrorCode(error, data).nullable);
110     }
111 }
112 
113 /++ Sends a request or a notification message. +/
114 string send(string method, Nullable!JSONValue params = Nullable!JSONValue())
115 {
116     import dls.protocol.handlers : hasResponseHandler, pushHandler;
117     import std.uuid : randomUUID;
118 
119     if (hasResponseHandler(method))
120     {
121         auto id = randomUUID().toString();
122         pushHandler(id, method);
123         send!RequestMessage(JSONValue(id), method, params, Nullable!ResponseError());
124         return id;
125     }
126 
127     send!NotificationMessage(JSONValue(), method, params, Nullable!ResponseError());
128     return null;
129 }
130 
131 string send(T)(string method, T params) if (!is(T : Nullable!JSONValue))
132 {
133     import dls.util.json : convertToJSON;
134     import std.typecons : nullable;
135 
136     return send(method, convertToJSON(params).nullable);
137 }
138 
139 /++ Sends a response message. +/
140 void send(JSONValue id, Nullable!JSONValue result,
141         Nullable!ResponseError error = Nullable!ResponseError())
142 {
143     send!ResponseMessage(id, null, result, error);
144 }
145 
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.json : convertToJSON;
170     import std.conv : to;
171     import std.stdio : stdout;
172     import std.utf : toUTF8;
173 
174     auto message = convertToJSON(m);
175     auto messageString = message.get().toString();
176 
177     synchronized
178     {
179         foreach (chunk; ["Content-Length: ", messageString.length.to!string,
180                 eol, eol, messageString])
181         {
182             stdout.rawWrite(chunk.toUTF8());
183         }
184 
185         stdout.flush();
186     }
187 }