1 module served.jsonrpc;
2 
3 import core.thread;
4 import core.exception;
5 
6 import painlessjson;
7 
8 import std.container : DList, SList;
9 import std.conv;
10 import std.experimental.logger;
11 import std.json;
12 import std.stdio;
13 import std.string;
14 import std.typecons;
15 
16 import served.protocol;
17 import served.filereader;
18 
19 import tinyevent;
20 
21 alias RequestHandler = ResponseMessage delegate(RequestMessage);
22 alias EventRequestHandler = void delegate(RequestMessage);
23 
24 class RPCProcessor : Fiber
25 {
26 	this(FileReader reader, File writer)
27 	{
28 		super(&run, 4096 * 16);
29 		this.reader = reader;
30 		this.writer = writer;
31 	}
32 
33 	void stop()
34 	{
35 		stopped = true;
36 	}
37 
38 	void send(ResponseMessage res)
39 	{
40 		auto msg = JSONValue(["jsonrpc" : JSONValue("2.0")]);
41 		if (res.id.hasData)
42 			msg["id"] = res.id.toJSON;
43 		if (res.result.type != JSON_TYPE.NULL)
44 			msg["result"] = res.result;
45 		if (!res.error.isNull)
46 		{
47 			msg["error"] = res.error.toJSON;
48 			stderr.writeln(msg["error"]);
49 		}
50 		send(msg);
51 	}
52 
53 	void send(RequestMessage req)
54 	{
55 		send(req.toJSON);
56 	}
57 
58 	void send(JSONValue raw)
59 	{
60 		if (!("jsonrpc" in raw))
61 		{
62 			error(raw);
63 			throw new Exception("Sent objects must have a jsonrpc");
64 		}
65 		const content = raw.toString().replace("\\/", "/");
66 		// Log on client side instead! (vscode setting: "serve-d.trace.server": "verbose")
67 		//trace(content);
68 		string data = "Content-Length: " ~ content.length.to!string ~ "\r\n\r\n" ~ content;
69 		writer.rawWrite(data);
70 		writer.flush();
71 	}
72 
73 	void notifyMethod(string method)
74 	{
75 		RequestMessage req;
76 		req.method = method;
77 		send(req);
78 	}
79 
80 	void notifyMethod(T)(string method, T value)
81 	{
82 		RequestMessage req;
83 		req.method = method;
84 		req.params = value.toJSON;
85 		send(req);
86 	}
87 
88 	void sendMethod(string method)
89 	{
90 		RequestMessage req;
91 		req.id = RequestToken.random;
92 		req.method = method;
93 		send(req);
94 	}
95 
96 	void sendMethod(T)(string method, T value)
97 	{
98 		RequestMessage req;
99 		req.id = RequestToken.random;
100 		req.method = method;
101 		req.params = value.toJSON;
102 		send(req);
103 	}
104 
105 	ResponseMessage sendRequest(T)(string method, T value)
106 	{
107 		RequestMessage req;
108 		req.id = RequestToken.random;
109 		req.method = method;
110 		req.params = value.toJSON;
111 		send(req);
112 		return awaitResponse(req.id);
113 	}
114 
115 	void log(MessageType type = MessageType.log, Args...)(Args args)
116 	{
117 		send(JSONValue(["jsonrpc" : JSONValue("2.0"), "method"
118 				: JSONValue("window/logMessage"), "params" : args.toJSON]));
119 	}
120 
121 	bool hasData()
122 	{
123 		return !messageQueue.empty;
124 	}
125 
126 	RequestMessage poll()
127 	{
128 		if (!hasData)
129 			throw new Exception("No Data");
130 		auto ret = messageQueue.front;
131 		messageQueue.removeFront();
132 		return ret;
133 	}
134 
135 	bool running = true;
136 
137 	WindowFunctions window()
138 	{
139 		return WindowFunctions(this);
140 	}
141 
142 	ResponseMessage awaitResponse(RequestToken tok)
143 	{
144 		size_t i;
145 		bool found = false;
146 		foreach (n, t; responseTokens)
147 		{
148 			if (t.handled)
149 			{
150 				// replace handled responses (overwrite reusable memory)
151 				i = n;
152 				found = true;
153 				break;
154 			}
155 		}
156 		if (!found)
157 			i = responseTokens.length++;
158 		responseTokens[i] = RequestWait(tok);
159 		while (!responseTokens[i].got)
160 			yield(); // yield until main loop placed a response
161 		auto res = responseTokens[i].ret;
162 		responseTokens[i].handled = true; // make memory reusable
163 		return res;
164 	}
165 
166 private:
167 	void onData(RequestMessage req)
168 	{
169 		messageQueue.insertBack(req);
170 	}
171 
172 	FileReader reader;
173 	File writer;
174 	bool stopped;
175 	DList!RequestMessage messageQueue;
176 
177 	struct RequestWait
178 	{
179 		RequestToken token;
180 		bool got = false;
181 		bool handled = false;
182 		ResponseMessage ret;
183 	}
184 
185 	RequestWait[] responseTokens;
186 
187 	void run()
188 	{
189 		while (!stopped)
190 		{
191 			bool inHeader = true;
192 			size_t contentLength = 0;
193 			do // dmd -O has an issue on mscoff where it forgets to emit a cmp here so this would break with while (inHeader)
194 			{
195 				string line = reader.yieldLine;
196 				if (!line.length && contentLength > 0)
197 					inHeader = false;
198 				else if (line.startsWith("Content-Length:"))
199 					contentLength = line["Content-Length:".length .. $].strip.to!size_t;
200 			}
201 			while (inHeader);
202 			assert(contentLength > 0);
203 			auto content = cast(string) reader.yieldData(contentLength);
204 			assert(content.length == contentLength);
205 			RequestMessage request;
206 			bool validRequest = false;
207 			try
208 			{
209 				auto json = parseJSON(content);
210 				auto id = "id" in json;
211 				bool isResponse = false;
212 				if (id)
213 				{
214 					auto tok = RequestToken(id);
215 					foreach (ref waiting; responseTokens)
216 					{
217 						if (!waiting.got && waiting.token == tok)
218 						{
219 							waiting.got = true;
220 							waiting.ret.id = tok;
221 							auto res = "result" in json;
222 							auto err = "error" in json;
223 							if (res)
224 								waiting.ret.result = *res;
225 							if (err)
226 								waiting.ret.error = (*err).fromJSON!ResponseError;
227 							isResponse = true;
228 							break;
229 						}
230 					}
231 				}
232 				if (!isResponse)
233 				{
234 					request = RequestMessage(json);
235 					validRequest = true;
236 				}
237 			}
238 			catch (Exception e)
239 			{
240 				try
241 				{
242 					trace(e);
243 					trace(content);
244 					auto idx = content.indexOf("\"id\":");
245 					auto endIdx = content.indexOf(",", idx);
246 					JSONValue fallback;
247 					if (idx != -1 && endIdx != -1)
248 						fallback = parseJSON(content[idx .. endIdx].strip);
249 					else
250 						fallback = JSONValue(0);
251 					send(ResponseMessage(RequestToken(&fallback), ResponseError(ErrorCode.parseError)));
252 				}
253 				catch (Exception e)
254 				{
255 					errorf("Got invalid request '%s'!", content);
256 					trace(e);
257 				}
258 			}
259 			if (validRequest)
260 			{
261 				onData(request);
262 				Fiber.yield();
263 			}
264 		}
265 	}
266 }
267 
268 struct WindowFunctions
269 {
270 	RPCProcessor rpc;
271 	private bool safeShowMessage;
272 
273 	void showMessage(MessageType type, string message)
274 	{
275 		if (!safeShowMessage)
276 			warningf("%s message: %s", type, message);
277 		rpc.notifyMethod("window/showMessage", ShowMessageParams(type, message));
278 		safeShowMessage = false;
279 	}
280 
281 	MessageActionItem requestMessage(MessageType type, string message, MessageActionItem[] actions)
282 	{
283 		auto res = rpc.sendRequest("window/showMessageRequest",
284 				ShowMessageRequestParams(type, message, actions.opt));
285 		if (res.result == JSONValue.init)
286 			return MessageActionItem(null);
287 		return res.result.fromJSON!MessageActionItem;
288 	}
289 
290 	string requestMessage(MessageType type, string message, string[] actions)
291 	{
292 		MessageActionItem[] a = new MessageActionItem[actions.length];
293 		foreach (i, action; actions)
294 			a[i] = MessageActionItem(action);
295 		return requestMessage(type, message, a).title;
296 	}
297 
298 	void runOrMessage(lazy void fn, MessageType type, string message)
299 	{
300 		try
301 		{
302 			fn();
303 		}
304 		catch (Exception e)
305 		{
306 			stderr.writeln(e);
307 			showMessage(type, message);
308 		}
309 	}
310 
311 	void showErrorMessage(string message)
312 	{
313 		error("Error message: ", message);
314 		safeShowMessage = true;
315 		showMessage(MessageType.error, message);
316 	}
317 
318 	void showWarningMessage(string message)
319 	{
320 		warning("Warning message: ", message);
321 		safeShowMessage = true;
322 		showMessage(MessageType.warning, message);
323 	}
324 
325 	void showInformationMessage(string message)
326 	{
327 		info("Info message: ", message);
328 		safeShowMessage = true;
329 		showMessage(MessageType.info, message);
330 	}
331 
332 	void showLogMessage(string message)
333 	{
334 		trace("Log message: ", message);
335 		safeShowMessage = true;
336 		showMessage(MessageType.log, message);
337 	}
338 }