1 module served.lsp.jsonrpc;
2 
3 // version = TracePackets;
4 
5 import core.exception;
6 import core.thread;
7 
8 import std.container : DList;
9 import std.conv;
10 import std.experimental.logger;
11 import std.json;
12 import std.stdio;
13 import std.string;
14 
15 import served.lsp.filereader;
16 import served.lsp.protocol;
17 
18 /// Fiber which runs in the background, reading from a FileReader, and calling methods when requested over the RPC interface.
19 class RPCProcessor : Fiber
20 {
21 	/// Constructs this RPC processor using a FileReader to read RPC commands from and a std.stdio.File to write RPC commands to.
22 	/// Creates this fiber with a reasonable fiber size.
23 	this(FileReader reader, File writer)
24 	{
25 		super(&run, 4096 * 32);
26 		this.reader = reader;
27 		this.writer = writer;
28 
29 		// LDC requires we don't use any TLS variables in the RPC processor on OSX
30 		// this assumption holds true for most of serve-d, but should be verified
31 		// every once in a while using `-vtls` to print all TLS variables.
32 		static if (__traits(hasMember, Fiber, "allowMigration"))
33 			allowMigration();
34 	}
35 
36 	/// Instructs the RPC processor to stop at the next IO read instruction.
37 	void stop()
38 	{
39 		stopped = true;
40 	}
41 
42 	/// Sends an RPC response or error.
43 	/// If `result` or `error` is not given on the response message, they won't be sent.
44 	/// Otherwise on success `result` must be set or on error `error` must be set.
45 	/// This also logs the error to stderr if it is given.
46 	/// Params:
47 	///   res = the response message to send.
48 	void send(ResponseMessage res)
49 	{
50 		const(char)[][7] buf;
51 		int len;
52 		buf[len++] = `{"jsonrpc":"2.0","id":`;
53 		buf[len++] = res.id.serializeJson;
54 
55 		if (!res.result.isNone)
56 		{
57 			buf[len++] = `,"result":`;
58 			buf[len++] = res.result.serializeJson;
59 		}
60 
61 		if (!res.error.isNone)
62 		{
63 			buf[len++] = `,"error":`;
64 			buf[len++] = res.error.serializeJson;
65 			stderr.writeln(buf[len - 1]);
66 		}
67 
68 		buf[len++] = `}`;
69 		sendRawPacket(buf[0 .. len]);
70 	}
71 	/// ditto
72 	void send(ResponseMessageRaw res)
73 	{
74 		const(char)[][7] buf;
75 		int len;
76 		buf[len++] = `{"jsonrpc":"2.0","id":`;
77 		buf[len++] = res.id.serializeJson;
78 
79 		if (res.resultJson.length)
80 		{
81 			buf[len++] = `,"result":`;
82 			buf[len++] = res.resultJson;
83 		}
84 
85 		if (!res.error.isNone)
86 		{
87 			buf[len++] = `,"error":`;
88 			buf[len++] = res.error.serializeJson;
89 			stderr.writeln(buf[len - 1]);
90 		}
91 
92 		buf[len++] = `}`;
93 		sendRawPacket(buf[0 .. len]);
94 	}
95 
96 	/// Sends an RPC request (method call) to the other side. Doesn't do any additional processing.
97 	/// Params:
98 	///   req = The request to send
99 	void send(RequestMessage req)
100 	{
101 		sendRawPacket(req.serializeJson);
102 	}
103 
104 	/// ditto
105 	void send(RequestMessageRaw req)
106 	{
107 		int i;
108 		const(char)[][7] buffer;
109 		buffer[i++] = `{"jsonrpc":"2.0","method":`;
110 		buffer[i++] = req.method.serializeJson;
111 		if (!req.id.isNone)
112 		{
113 			buffer[i++] = `,"id":`;
114 			buffer[i++] = req.id.serializeJson;
115 		}
116 		if (req.paramsJson.length)
117 		{
118 			buffer[i++] = `,"params":`;
119 			buffer[i++] = req.paramsJson;
120 		}
121 		buffer[i++] = `}`;
122 		sendRawPacket(buffer[0 .. i]);
123 	}
124 
125 	/// Sends a raw JSON object to the other RPC side. 
126 	deprecated void send(JSONValue raw)
127 	{
128 		if (!("jsonrpc" in raw))
129 		{
130 			error(raw);
131 			throw new Exception("Sent objects must have a jsonrpc");
132 		}
133 		const content = raw.toString(JSONOptions.doNotEscapeSlashes);
134 		sendRawPacket(content);
135 	}
136 
137 	/// ditto
138 	void sendRawPacket(scope const(char)[] rawJson)
139 	{
140 		sendRawPacket((&rawJson)[0 .. 1]);
141 	}
142 
143 	/// ditto
144 	void sendRawPacket(scope const(char)[][] parts)
145 	{
146 		// Consider turning on logging on client side instead!
147 		// (vscode setting: "serve-d.trace.server": "verbose")
148 		version (TracePackets)
149 		{
150 			import std.algorithm;
151 
152 			trace(">> ", parts.join);
153 		}
154 
155 		size_t len = 0;
156 		foreach (part; parts)
157 			len += part.length;
158 
159 		{
160 			scope w = writer.lockingBinaryWriter;
161 			w.put("Content-Length: ");
162 			w.put(len.to!string);
163 			w.put("\r\n\r\n");
164 			foreach (part; parts)
165 				w.put(part);
166 		}
167 		writer.flush();
168 	}
169 
170 	/// Sends a notification with the given `method` name to the other RPC side without any parameters.
171 	void notifyMethod(string method)
172 	{
173 		RequestMessageRaw req;
174 		req.method = method;
175 		send(req);
176 	}
177 
178 	/// Sends a notification with the given `method` name to the other RPC side with the given `value` parameter serialized to JSON.
179 	void notifyMethod(T)(string method, T value)
180 	{
181 		notifyMethodRaw(method, value.serializeJson);
182 	}
183 
184 	/// ditto
185 	deprecated void notifyMethod(string method, JSONValue value)
186 	{
187 		notifyMethodRaw(method, value.toString);
188 	}
189 
190 	/// ditto
191 	void notifyMethod(string method, JsonValue value)
192 	{
193 		RequestMessage req;
194 		req.method = method;
195 		req.params = value;
196 		send(req);
197 	}
198 
199 	/// ditto
200 	void notifyMethodRaw(string method, scope const(char)[] value)
201 	{
202 		const(char)[][5] parts = [
203 			`{"jsonrpc":"2.0","method":`,
204 			method.serializeJson,
205 			`,"params":`,
206 			value,
207 			`}`
208 		];
209 		sendRawPacket(parts[]);
210 	}
211 
212 	void notifyProgressRaw(scope const(char)[] token, scope const(char)[] value)
213 	{
214 		const(char)[][5] parts = [
215 			`{"jsonrpc":"2.0","method":"$/progress","params":{"token":`,
216 			token,
217 			`,"value":`,
218 			value,
219 			`}}`
220 		];
221 		sendRawPacket(parts[]);
222 	}
223 
224 	void registerCapability(T)(scope const(char)[] id, scope const(char)[] method, T options)
225 	{
226 		const(char)[][7] parts = [
227 			`{"jsonrpc":"2.0","method":"client/registerCapability","registrations":[{"id":"`,
228 			id.escapeJsonStringContent,
229 			`","method":"`,
230 			method.escapeJsonStringContent,
231 			`","registerOptions":`,
232 			options.serializeJson,
233 			`]}`
234 		];
235 		sendRawPacket(parts[]);
236 	}
237 
238 	/// Sends a request with the given `method` name to the other RPC side without any parameters.
239 	/// Doesn't handle the response by the other RPC side.
240 	/// Returns: a RequestToken to use with $(LREF awaitResponse) to get the response. Can be ignored if the response isn't important.
241 	RequestToken sendMethod(string method)
242 	{
243 		auto id = RequestToken.next();
244 		const(char)[][5] parts = [
245 			`{"jsonrpc":"2.0","id":`,
246 			id.serializeJson,
247 			`,"method":`,
248 			method.serializeJson,
249 			`}`
250 		];
251 		sendRawPacket(parts[]);
252 		return id;
253 	}
254 
255 	/// Sends a request with the given `method` name to the other RPC side with the given `value` parameter serialized to JSON.
256 	/// Doesn't handle the response by the other RPC side.
257 	/// Returns: a RequestToken to use with $(LREF awaitResponse) to get the response. Can be ignored if the response isn't important.
258 	RequestToken sendMethod(T)(string method, T value)
259 	{
260 		return sendMethodRaw(method, value.serializeJson);
261 	}
262 
263 	/// ditto
264 	deprecated RequestToken sendMethod(string method, JSONValue value)
265 	{
266 		return sendMethod(method, value.toJsonValue);
267 	}
268 
269 	/// ditto
270 	RequestToken sendMethod(string method, JsonValue value)
271 	{
272 		return sendMethodRaw(method, value.serializeJson);
273 	}
274 
275 	/// ditto
276 	RequestToken sendMethodRaw(string method, scope const(char)[] value)
277 	{
278 		auto id = RequestToken.next();
279 		const(char)[][7] parts = [
280 			`{"jsonrpc":"2.0","id":`,
281 			id.serializeJson,
282 			`,"method":`,
283 			method.serializeJson,
284 			`,"params":`,
285 			value,
286 			`}`
287 		];
288 		sendRawPacket(parts[]);
289 		return id;
290 	}
291 
292 	/// Sends a request with the given `method` name to the other RPC side with the given `value` parameter serialized to JSON.
293 	/// Awaits the response (using yield) and returns once it's there.
294 	///
295 	/// This is a small wrapper to call `awaitResponse(sendMethod(method, value))`
296 	///
297 	/// Returns: The response deserialized from the RPC.
298 	ResponseMessageRaw sendRequest(T)(string method, T value, Duration timeout = Duration.max)
299 	{
300 		return awaitResponse(sendMethod(method, value), timeout);
301 	}
302 
303 	/// ditto
304 	deprecated ResponseMessageRaw sendRequest(string method, JSONValue value, Duration timeout = Duration.max)
305 	{
306 		return awaitResponse(sendMethod(method, value), timeout);
307 	}
308 
309 	/// ditto
310 	ResponseMessageRaw sendRequest(string method, JsonValue value, Duration timeout = Duration.max)
311 	{
312 		return awaitResponse(sendMethod(method, value), timeout);
313 	}
314 
315 	/// Calls the `window/logMessage` method with all arguments concatenated together using text()
316 	/// Params:
317 	///   type = the $(REF MessageType, served,lsp,protocol) to use as $(REF LogMessageParams, served,lsp,protocol) type
318 	///   args = the message parts to send
319 	void log(MessageType type = MessageType.log, Args...)(Args args)
320 	{
321 		scope const(char)[][3] parts = [
322 			`{"jsonrpc":"2.0","method":"window/logMessage","params":`,
323 			LogMessageParams(type, text(args)).serializeJson,
324 			`}`
325 		];
326 		sendRawPacket(parts[]);
327 	}
328 
329 	/// Returns: `true` if there has been any messages been sent to us from the other RPC side, otherwise `false`.
330 	bool hasData() const @property
331 	{
332 		return !messageQueue.empty;
333 	}
334 
335 	/// Returns: the first message from the message queue. Removes it from the message queue so it will no longer be processed.
336 	/// Throws: Exception if $(LREF hasData) is false.
337 	RequestMessageRaw poll()
338 	{
339 		if (!hasData)
340 			throw new Exception("No Data");
341 		auto ret = messageQueue.front;
342 		messageQueue.removeFront();
343 		return ret;
344 	}
345 
346 	/// Convenience wrapper around $(LREF WindowFunctions) for `this`.
347 	WindowFunctions window()
348 	{
349 		return WindowFunctions(this);
350 	}
351 
352 	/// Registers a wait handler for the given request token. When the other RPC
353 	/// side sends a response to this token, the value will be saved, before it
354 	/// is being awaited.
355 	private size_t prepareWait(RequestToken tok)
356 	{
357 		size_t i;
358 		bool found = false;
359 		foreach (n, t; responseTokens)
360 		{
361 			if (t.handled)
362 			{
363 				// replace handled responses (overwrite reusable memory)
364 				i = n;
365 				found = true;
366 				break;
367 			}
368 		}
369 		if (!found)
370 			i = responseTokens.length++;
371 		responseTokens[i] = RequestWait(tok);
372 		return i;
373 	}
374 
375 	/// Waits until the given responseToken wait handler is resolved, then
376 	/// return its result and makes the memory reusable.
377 	private ResponseMessageRaw resolveWait(size_t i, Duration timeout = Duration.max)
378 	{
379 		import std.datetime.stopwatch;
380 
381 		StopWatch sw;
382 		sw.start();
383 		while (!responseTokens[i].got)
384 		{
385 			if (timeout != Duration.max
386 				&& sw.peek > timeout)
387 				throw new Exception("RPC response wait timed out");
388 			yield(); // yield until main loop placed a response
389 		}
390 		auto res = responseTokens[i].ret;
391 		responseTokens[i].handled = true; // make memory reusable
392 		return res;
393 	}
394 
395 	private bool hasResponse(size_t handle)
396 	{
397 		return responseTokens[handle].got;
398 	}
399 
400 	/**
401 		Waits for a response message to a request from the other RPC side.
402 
403 		If this is called after the response has already been sent and processed
404 		by yielding after sending the request, this will yield forever and use
405 		up memory.
406 
407 		So it is important, if you are going to await a response, to do it
408 		immediately when sending any request.
409 	*/
410 	ResponseMessageRaw awaitResponse(RequestToken tok, Duration timeout = Duration.max)
411 	{
412 		auto i = prepareWait(tok);
413 		return resolveWait(i, timeout);
414 	}
415 
416 private:
417 	void onData(RequestMessageRaw req)
418 	{
419 		version (TracePackets)
420 		{
421 			import std.algorithm;
422 
423 			trace("<< ", req.id, ": ", req.method, ": ", req.paramsJson);
424 		}
425 
426 		messageQueue.insertBack(req);
427 	}
428 
429 	FileReader reader;
430 	File writer;
431 	bool stopped;
432 	DList!RequestMessageRaw messageQueue;
433 
434 	struct RequestWait
435 	{
436 		RequestToken token;
437 		bool got = false;
438 		bool handled = false;
439 		ResponseMessageRaw ret;
440 	}
441 
442 	RequestWait[] responseTokens;
443 
444 	void run()
445 	{
446 		assert(reader.isReading, "must start jsonrpc after file reader!");
447 		while (!stopped && reader.isReading)
448 		{
449 			bool gotAnyHeader;
450 			bool inHeader = true;
451 			size_t contentLength = 0;
452 			do // dmd -O has an issue on mscoff where it forgets to emit a cmp here so this would break with while (inHeader)
453 			{
454 				string line = reader.yieldLine(&stopped, false);
455 				if (!reader.isReading)
456 					stop(); // abort in header
457 
458 				if (line.length)
459 					gotAnyHeader = true;
460 
461 				if (!line.length && gotAnyHeader)
462 					inHeader = false;
463 				else if (line.startsWith("Content-Length:"))
464 					contentLength = line["Content-Length:".length .. $].strip.to!size_t;
465 			}
466 			while (inHeader && !stopped);
467 
468 			if (stopped)
469 				break;
470 
471 			if (contentLength <= 0)
472 			{
473 				send(ResponseMessage(RequestToken.init, ResponseError(ErrorCode.invalidRequest, "Invalid/no content length specified")));
474 				continue;
475 			}
476 
477 			auto content = cast(const(char)[]) reader.yieldData(contentLength, &stopped, false);
478 			if (stopped || content is null)
479 				break;
480 			assert(content.length == contentLength);
481 			RequestMessageRaw request;
482 			RequestMessageRaw[] extraRequests;
483 			try
484 			{
485 				if (content.length && content[0] == '[')
486 				{
487 					int count;
488 					content.visitJsonArray!((item) {
489 						count++;
490 
491 						auto res = handleRequestImpl(item);
492 						if (request == RequestMessageRaw.init)
493 							request = res;
494 						else if (res != RequestMessageRaw.init)
495 							extraRequests ~= res;
496 					});
497 					if (count == 0)
498 						send(ResponseMessage(null, ResponseError(ErrorCode.invalidRequest, "Empty batch request")));
499 				}
500 				else if (content.length && content[0] == '{')
501 				{
502 					request = handleRequestImpl(content);
503 				}
504 				else
505 				{
506 					send(ResponseMessage(null, ResponseError(ErrorCode.invalidRequest, "Invalid request type (must be object or array)")));
507 				}
508 			}
509 			catch (Exception e)
510 			{
511 				try
512 				{
513 					trace(e);
514 					trace(content);
515 					auto idx = content.indexOf("\"id\":");
516 					auto endIdx = content.indexOf(",", idx);
517 					RequestToken fallback;
518 					if (!content.startsWith("[") && idx != -1 && endIdx != -1)
519 						fallback = deserializeJson!RequestToken(content[idx .. endIdx].strip);
520 					send(ResponseMessage(fallback, ResponseError(ErrorCode.parseError,
521 						"Parse error: " ~ e.msg)));
522 				}
523 				catch (Exception e)
524 				{
525 					errorf("Got invalid request '%s'!", content);
526 					trace(e);
527 				}
528 			}
529 
530 			if (request != RequestMessageRaw.init)
531 			{
532 				onData(request);
533 				Fiber.yield();
534 			}
535 
536 			foreach (req; extraRequests)
537 			{
538 				onData(request);
539 				Fiber.yield();
540 			}
541 		}
542 	}
543 
544 	RequestMessageRaw handleRequestImpl(scope const(char)[] json)
545 	{
546 		if (!json.looksLikeJsonObject)
547 			throw new Exception("malformed request JSON, must be object");
548 		auto slices = json.parseKeySlices!("id", "result", "error", "method", "params");
549 
550 		auto id = slices.id;
551 		if (slices.result.length && slices.method.length
552 			|| !slices.result.length && !slices.method.length && !slices.error.length)
553 		{
554 			ResponseMessage res;
555 			if (id.length)
556 				res.id = id.deserializeJson!RequestToken;
557 			res.error = ResponseError(ErrorCode.invalidRequest, "missing required members or has ambiguous members");
558 			send(res);
559 			return RequestMessageRaw.init;
560 		}
561 
562 		bool isResponse = false;
563 		if (id.length)
564 		{
565 			auto tok = id.deserializeJson!RequestToken;
566 			foreach (ref waiting; responseTokens)
567 			{
568 				if (!waiting.got && waiting.token == tok)
569 				{
570 					waiting.got = true;
571 					waiting.ret.id = tok;
572 					auto res = slices.result;
573 					auto err = slices.error;
574 					if (res.length)
575 						waiting.ret.resultJson = res.idup;
576 					if (err.length)
577 						waiting.ret.error = err.deserializeJson!ResponseError;
578 					isResponse = true;
579 					break;
580 				}
581 			}
582 
583 			if (!isResponse && slices.result.length)
584 			{
585 				send(ResponseMessage(tok,
586 					ResponseError(ErrorCode.invalidRequest, "unknown request response ID")));
587 				return RequestMessageRaw.init;
588 			}
589 		}
590 		if (!isResponse)
591 		{
592 			RequestMessageRaw request;
593 			if (slices.id.length)
594 				request.id = slices.id.deserializeJson!RequestToken;
595 			if (slices.method.length)
596 				request.method = slices.method.deserializeJson!string;
597 			if (slices.params.length)
598 				request.paramsJson = slices.params.idup;
599 
600 			if (request.paramsJson.length
601 				&& request.paramsJson.ptr[0] != '['
602 				&& request.paramsJson.ptr[0] != '{')
603 			{
604 				auto err = ResponseError(ErrorCode.invalidParams,
605 					"`params` MUST be an object (named arguments) or array "
606 					~ "(positional arguments), other types are not allowed by spec"
607 				);
608 				if (request.id.isNone)
609 					send(ResponseMessage(null, err));
610 				else
611 					send(ResponseMessage(RequestToken(request.id.deref), err));
612 			}
613 			else
614 			{
615 				return request;
616 			}
617 		}
618 		return RequestMessageRaw.init;
619 	}
620 }
621 
622 /// Helper struct to simulate RPC connections with an RPCProcessor.
623 /// Intended for use with tests, not for real-world use.
624 struct MockRPC
625 {
626 	import std.process;
627 
628 	enum shortDelay = 10.msecs;
629 
630 	Pipe rpcPipe;
631 	Pipe resultPipe;
632 
633 	void writePacket(string s, string epilogue = "", string prologue = "")
634 	{
635 		with (rpcPipe.writeEnd.lockingBinaryWriter)
636 		{
637 			put(prologue);
638 			put("Content-Length: ");
639 			put(s.length.to!string);
640 			put("\r\n\r\n");
641 			put(s);
642 			put(epilogue);
643 		}
644 		rpcPipe.writeEnd.flush();
645 	}
646 
647 	string readPacket()
648 	{
649 		auto lenStr = resultPipe.readEnd.readln();
650 		assert(lenStr.startsWith("Content-Length: "));
651 		auto len = lenStr["Content-Length: ".length .. $].strip.to!int;
652 		resultPipe.readEnd.readln();
653 		ubyte[] buf = new ubyte[len];
654 		size_t i;
655 		while (!resultPipe.readEnd.eof && i < buf.length)
656 			i += resultPipe.readEnd.rawRead(buf[i .. $]).length;
657 		assert(i == buf.length);
658 		return cast(string)buf;
659 	}
660 
661 	void expectPacket(string s)
662 	{
663 		auto res = readPacket();
664 		assert(res == s, res);
665 	}
666 
667 	void testRPC(void delegate(RPCProcessor rpc) cb)
668 	{
669 		rpcPipe = pipe();
670 		resultPipe = pipe();
671 
672 		auto rpcInput = newFileReader(rpcPipe.readEnd);
673 		rpcInput.start();
674 		scope (exit)
675 		{
676 			rpcInput.stop();
677 			Thread.sleep(shortDelay);
678 		}
679 
680 		auto rpc = new RPCProcessor(rpcInput, resultPipe.writeEnd);
681 		rpc.call();
682 
683 		cb(rpc);
684 
685 		rpc.stop();
686 		if (rpc.state != Fiber.State.TERM)
687 			rpc.call();
688 		assert(rpc.state == Fiber.State.TERM);
689 	}
690 }
691 
692 unittest
693 {
694 	import served.lsp.protocol;
695 
696 	MockRPC mockRPC;
697 
698 	void writePacket(string s, string epilogue = "", string prologue = "")
699 	{
700 		mockRPC.writePacket(s, epilogue, prologue);
701 	}
702 
703 	string readPacket()
704 	{
705 		return mockRPC.readPacket();
706 	}
707 
708 	void expectPacket(string s)
709 	{
710 		mockRPC.expectPacket(s);
711 	}
712 
713 	void testRPC(void delegate(RPCProcessor rpc) cb)
714 	{
715 		mockRPC.testRPC(cb);
716 	}
717 
718 	testRPC((rpc) { /* test immediate close */ });
719 
720 	foreach (i; 0 .. 2)
721 	testRPC((rpc) {
722 		InitializeParams initializeParams = {
723 			processId: 1234,
724 			rootUri: "file:///root/uri",
725 			capabilities: ClientCapabilities.init
726 		};
727 		auto tok = rpc.sendMethod("initialize", initializeParams);
728 		auto waitHandle = rpc.prepareWait(tok);
729 		expectPacket(`{"jsonrpc":"2.0","id":` ~ tok.value.toString ~ `,"method":"initialize","params":{"processId":1234,"rootUri":"file:///root/uri","capabilities":{}}}`);
730 		writePacket(`{"jsonrpc":"2.0","id":` ~ tok.value.toString ~ `,"result":{"capabilities":{}}}`);
731 		rpc.call();
732 		Thread.sleep(MockRPC.shortDelay);
733 		rpc.call();
734 		assert(rpc.hasResponse(waitHandle));
735 		auto res = rpc.resolveWait(waitHandle);
736 		assert(res.resultJson == `{"capabilities":{}}`);
737 	});
738 
739 	// test close after unfinished message (e.g. client crashed)
740 	testRPC((rpc) {
741 		mockRPC.rpcPipe.writeEnd.lockingBinaryWriter.put("Content-Length: 5\r\n\r\n");
742 		mockRPC.rpcPipe.writeEnd.flush();
743 		rpc.call();
744 		Thread.sleep(MockRPC.shortDelay);
745 		rpc.call();
746 	});
747 	testRPC((rpc) {
748 		mockRPC.rpcPipe.writeEnd.lockingBinaryWriter.put("Content-Length: 5\r\n\r\ntrue");
749 		mockRPC.rpcPipe.writeEnd.flush();
750 		rpc.call();
751 		Thread.sleep(MockRPC.shortDelay);
752 		rpc.call();
753 	});
754 
755 	// test unlucky buffering
756 	testRPC((rpc) {
757 		void slowIO()
758 		{
759 			rpc.call();
760 			Thread.sleep(MockRPC.shortDelay);
761 			rpc.call();
762 		}
763 
764 		InitializeParams initializeParams = {
765 			processId: 1234,
766 			rootUri: "file:///root/uri",
767 			capabilities: ClientCapabilities.init
768 		};
769 		auto tok = rpc.sendMethod("initialize", initializeParams);
770 		auto waitHandle = rpc.prepareWait(tok);
771 		expectPacket(`{"jsonrpc":"2.0","id":` ~ tok.value.toString ~ `,"method":"initialize","params":{"processId":1234,"rootUri":"file:///root/uri","capabilities":{}}}`);
772 		auto s = `{"jsonrpc":"2.0","id":` ~ tok.value.toString ~ `,"result":{"capabilities":{}}}`;
773 		mockRPC.rpcPipe.writeEnd.lockingBinaryWriter.put("Content-Length: ");
774 		mockRPC.rpcPipe.writeEnd.flush();
775 		slowIO();
776 		assert(!rpc.hasResponse(waitHandle));
777 		mockRPC.rpcPipe.writeEnd.lockingBinaryWriter.put(s.length.to!string);
778 		mockRPC.rpcPipe.writeEnd.flush();
779 		slowIO();
780 		assert(!rpc.hasResponse(waitHandle));
781 		mockRPC.rpcPipe.writeEnd.lockingBinaryWriter.put("\r\n\r\n");
782 		mockRPC.rpcPipe.writeEnd.flush();
783 		slowIO();
784 		assert(!rpc.hasResponse(waitHandle));
785 		mockRPC.rpcPipe.writeEnd.lockingBinaryWriter.put(s[0 .. $ / 2]);
786 		mockRPC.rpcPipe.writeEnd.flush();
787 		slowIO();
788 		assert(!rpc.hasResponse(waitHandle));
789 		mockRPC.rpcPipe.writeEnd.lockingBinaryWriter.put(s[$ / 2 .. $]);
790 		mockRPC.rpcPipe.writeEnd.flush();
791 		slowIO();
792 		assert(rpc.hasResponse(waitHandle));
793 		auto res = rpc.resolveWait(waitHandle);
794 		assert(res.resultJson == `{"capabilities":{}}`);
795 	});
796 
797 	// test spec-resiliance with whitespace before/after message
798 	foreach (pair; [["", "\r\n"], ["\r\n", ""], ["\r\n\r\n", "\r\n\r\n"]])
799 	testRPC((rpc) {
800 		InitializeParams initializeParams = {
801 			processId: 1234,
802 			rootUri: "file:///root/uri",
803 			capabilities: ClientCapabilities.init
804 		};
805 		auto tok = rpc.sendMethod("initialize", initializeParams);
806 		auto waitHandle = rpc.prepareWait(tok);
807 		expectPacket(`{"jsonrpc":"2.0","id":` ~ tok.value.toString ~ `,"method":"initialize","params":{"processId":1234,"rootUri":"file:///root/uri","capabilities":{}}}`);
808 		writePacket(`{"jsonrpc":"2.0","id":` ~ tok.value.toString ~ `,"result":{"capabilities":{}}}`, pair[1], pair[0]);
809 		rpc.call();
810 		Thread.sleep(MockRPC.shortDelay);
811 		rpc.call();
812 		assert(rpc.hasResponse(waitHandle));
813 		auto res = rpc.resolveWait(waitHandle);
814 		assert(res.resultJson == `{"capabilities":{}}`);
815 
816 		writePacket(`{"jsonrpc":"2.0","id":"sendtest","method":"test","params":{"x":{"a":4}}}`, pair[1], pair[0]);
817 		rpc.call();
818 		Thread.sleep(MockRPC.shortDelay);
819 		rpc.call();
820 		assert(rpc.hasData);
821 		auto msg = rpc.poll;
822 		assert(!msg.id.isNone);
823 		assert(msg.id.deref == "sendtest");
824 		assert(msg.method == "test");
825 		assert(msg.paramsJson == `{"x":{"a":4}}`);
826 	});
827 
828 	// test error handling
829 	testRPC((rpc) {
830 		InitializeParams initializeParams = {
831 			processId: 1234,
832 			rootUri: "file:///root/uri",
833 			capabilities: ClientCapabilities.init
834 		};
835 		auto tok = rpc.sendMethod("initialize", initializeParams);
836 		auto waitHandle = rpc.prepareWait(tok);
837 		expectPacket(`{"jsonrpc":"2.0","id":` ~ tok.value.toString ~ `,"method":"initialize","params":{"processId":1234,"rootUri":"file:///root/uri","capabilities":{}}}`);
838 		writePacket(`{"jsonrpc":"2.0","id":` ~ tok.value.toString ~ `,"result":{"capabilities":{}}}`);
839 		rpc.call();
840 		Thread.sleep(MockRPC.shortDelay);
841 		rpc.call();
842 		assert(rpc.hasResponse(waitHandle));
843 		auto res = rpc.resolveWait(waitHandle);
844 		assert(res.resultJson == `{"capabilities":{}}`);
845 
846 		void errorTest(string send, string recv)
847 		{
848 			writePacket(send);
849 			rpc.call();
850 			Thread.sleep(MockRPC.shortDelay);
851 			rpc.call();
852 			expectPacket(recv);
853 		}
854 
855 		errorTest(`{"jsonrpc":"2.0","id":"invalid-token","result":{"capabilities":{}}}`,
856 			`{"jsonrpc":"2.0","id":"invalid-token","error":{"code":-32600,"message":"unknown request response ID"}}`);
857 
858 		// twice to see that the same error gets handled
859 		errorTest(`{"jsonrpc":"2.0","id":"invalid-token","result":{"capabilities":{}}}`,
860 			`{"jsonrpc":"2.0","id":"invalid-token","error":{"code":-32600,"message":"unknown request response ID"}}`);
861 
862 		errorTest(`{"jsonrpc": "2.0", "method": "foobar, "params": "bar", "baz]`,
863 			`{"jsonrpc":"2.0","id":null,"error":{"code":-32700,"message":"Parse error: malformed request JSON, must be object"}}`);
864 
865 		errorTest(`[]`,
866 			`{"jsonrpc":"2.0","id":null,"error":{"code":-32600,"message":"Empty batch request"}}`);
867 
868 		errorTest(`[{}]`,
869 			`{"jsonrpc":"2.0","id":null,"error":{"code":-32600,"message":"missing required members or has ambiguous members"}}`);
870 
871 		writePacket(`[{},{},{}]`);
872 		rpc.call();
873 		Thread.sleep(MockRPC.shortDelay);
874 		rpc.call();
875 		expectPacket(`{"jsonrpc":"2.0","id":null,"error":{"code":-32600,"message":"missing required members or has ambiguous members"}}`);
876 		expectPacket(`{"jsonrpc":"2.0","id":null,"error":{"code":-32600,"message":"missing required members or has ambiguous members"}}`);
877 		expectPacket(`{"jsonrpc":"2.0","id":null,"error":{"code":-32600,"message":"missing required members or has ambiguous members"}}`);
878 	});
879 }
880 
881 /// Utility functions for common LSP methods performing UI things.
882 struct WindowFunctions
883 {
884 	/// The RPC processor to use for sending/receiving
885 	RPCProcessor rpc;
886 	private bool safeShowMessage;
887 
888 	/// Runs window/showMessage which typically shows a notification box, without any buttons or feedback.
889 	/// Logs the message to stderr too.
890 	void showMessage(MessageType type, string message)
891 	{
892 		if (!safeShowMessage)
893 			warningf("%s message: %s", type, message);
894 		rpc.notifyMethod("window/showMessage", ShowMessageParams(type, message));
895 		safeShowMessage = false;
896 	}
897 
898 	/// Runs window/showMessageRequest which typically shows a message box with possible action buttons to click. Returns the action which got clicked or one with null title if it has been dismissed.
899 	MessageActionItem requestMessage(MessageType type, string message, MessageActionItem[] actions)
900 	{
901 		auto res = rpc.sendRequest("window/showMessageRequest",
902 				ShowMessageRequestParams(type, message, actions.opt));
903 		if (!res.resultJson.length || res.resultJson == `null`)
904 			return MessageActionItem(null);
905 		return res.resultJson.deserializeJson!MessageActionItem;
906 	}
907 
908 	/// ditto
909 	string requestMessage(MessageType type, string message, string[] actions)
910 	{
911 		MessageActionItem[] a = new MessageActionItem[actions.length];
912 		foreach (i, action; actions)
913 			a[i] = MessageActionItem(action);
914 		return requestMessage(type, message, a).title;
915 	}
916 
917 	/// Runs a function and shows a UI message on failure and logs the error.
918 	/// Returns: true if fn was successfully run or false if an exception occured.
919 	bool runOrMessage(lazy void fn, MessageType type, string message,
920 			string file = __FILE__, size_t line = __LINE__)
921 	{
922 		try
923 		{
924 			fn();
925 			return true;
926 		}
927 		catch (Exception e)
928 		{
929 			errorf("Error running in %s(%s): %s", file, line, e);
930 			showMessage(type, message);
931 			return false;
932 		}
933 	}
934 
935 	/// Calls $(LREF showMessage) with MessageType.error
936 	/// Also logs the message to stderr in a more readable way.
937 	void showErrorMessage(string message)
938 	{
939 		error("Error message: ", message);
940 		safeShowMessage = true;
941 		showMessage(MessageType.error, message);
942 	}
943 
944 	/// Calls $(LREF showMessage) with MessageType.warning
945 	/// Also logs the message to stderr in a more readable way.
946 	void showWarningMessage(string message)
947 	{
948 		warning("Warning message: ", message);
949 		safeShowMessage = true;
950 		showMessage(MessageType.warning, message);
951 	}
952 
953 	/// Calls $(LREF showMessage) with MessageType.info
954 	/// Also logs the message to stderr in a more readable way.
955 	void showInformationMessage(string message)
956 	{
957 		info("Info message: ", message);
958 		safeShowMessage = true;
959 		showMessage(MessageType.info, message);
960 	}
961 
962 	/// Calls $(LREF showMessage) with MessageType.log
963 	/// Also logs the message to stderr in a more readable way.
964 	void showLogMessage(string message)
965 	{
966 		trace("Log message: ", message);
967 		safeShowMessage = true;
968 		showMessage(MessageType.log, message);
969 	}
970 }