MLIR  21.0.0git
Transport.h
Go to the documentation of this file.
1 //===--- Transport.h - Sending and Receiving LSP messages -------*- C++ -*-===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 //
9 // The language server protocol is usually implemented by writing messages as
10 // JSON-RPC over the stdin/stdout of a subprocess. This file contains a JSON
11 // transport interface that handles this communication.
12 //
13 //===----------------------------------------------------------------------===//
14 
15 #ifndef MLIR_TOOLS_LSPSERVERSUPPORT_TRANSPORT_H
16 #define MLIR_TOOLS_LSPSERVERSUPPORT_TRANSPORT_H
17 
19 #include "mlir/Support/LLVM.h"
22 #include "llvm/ADT/FunctionExtras.h"
23 #include "llvm/ADT/StringMap.h"
24 #include "llvm/ADT/StringRef.h"
25 #include "llvm/Support/FormatAdapters.h"
26 #include "llvm/Support/JSON.h"
27 #include "llvm/Support/raw_ostream.h"
28 #include <atomic>
29 
30 namespace mlir {
31 namespace lsp {
32 class MessageHandler;
33 
34 //===----------------------------------------------------------------------===//
35 // JSONTransport
36 //===----------------------------------------------------------------------===//
37 
38 /// The encoding style of the JSON-RPC messages (both input and output).
40  /// Encoding per the LSP specification, with mandatory Content-Length header.
42  /// Messages are delimited by a '// -----' line. Comment lines start with //.
43  Delimited
44 };
45 
46 /// An abstract class used by the JSONTransport to read JSON message.
48 public:
50  : style(style) {}
51  virtual ~JSONTransportInput() = default;
52 
53  virtual bool hasError() const = 0;
54  virtual bool isEndOfInput() const = 0;
55 
56  /// Read in a message from the input stream.
57  LogicalResult readMessage(std::string &json) {
58  return style == JSONStreamStyle::Delimited ? readDelimitedMessage(json)
59  : readStandardMessage(json);
60  }
61  virtual LogicalResult readDelimitedMessage(std::string &json) = 0;
62  virtual LogicalResult readStandardMessage(std::string &json) = 0;
63 
64 private:
65  /// The JSON stream style to use.
66  JSONStreamStyle style;
67 };
68 
69 /// Concrete implementation of the JSONTransportInput that reads from a file.
71 public:
73  std::FILE *in, JSONStreamStyle style = JSONStreamStyle::Standard)
74  : JSONTransportInput(style), in(in) {}
75 
76  bool hasError() const final { return ferror(in); }
77  bool isEndOfInput() const final { return feof(in); }
78 
79  LogicalResult readDelimitedMessage(std::string &json) final;
80  LogicalResult readStandardMessage(std::string &json) final;
81 
82 private:
83  std::FILE *in;
84 };
85 
86 /// A transport class that performs the JSON-RPC communication with the LSP
87 /// client.
89 public:
90  JSONTransport(std::unique_ptr<JSONTransportInput> in, raw_ostream &out,
91  bool prettyOutput = false)
92  : in(std::move(in)), out(out), prettyOutput(prettyOutput) {}
93 
94  JSONTransport(std::FILE *in, raw_ostream &out,
96  bool prettyOutput = false)
97  : in(std::make_unique<JSONTransportInputOverFile>(in, style)), out(out),
98  prettyOutput(prettyOutput) {}
99 
100  /// The following methods are used to send a message to the LSP client.
101  void notify(StringRef method, llvm::json::Value params);
102  void call(StringRef method, llvm::json::Value params, llvm::json::Value id);
103  void reply(llvm::json::Value id, llvm::Expected<llvm::json::Value> result);
104 
105  /// Start executing the JSON-RPC transport.
106  llvm::Error run(MessageHandler &handler);
107 
108 private:
109  /// Dispatches the given incoming json message to the message handler.
110  bool handleMessage(llvm::json::Value msg, MessageHandler &handler);
111  /// Writes the given message to the output stream.
112  void sendMessage(llvm::json::Value msg);
113 
114 private:
115  /// The input to read a message from.
116  std::unique_ptr<JSONTransportInput> in;
117  SmallVector<char, 0> outputBuffer;
118  /// The output file stream.
119  raw_ostream &out;
120  /// If the output JSON should be formatted for easier readability.
121  bool prettyOutput;
122 };
123 
124 //===----------------------------------------------------------------------===//
125 // MessageHandler
126 //===----------------------------------------------------------------------===//
127 
128 /// A Callback<T> is a void function that accepts Expected<T>. This is
129 /// accepted by functions that logically return T.
130 template <typename T>
131 using Callback = llvm::unique_function<void(llvm::Expected<T>)>;
132 
133 /// An OutgoingNotification<T> is a function used for outgoing notifications
134 /// send to the client.
135 template <typename T>
136 using OutgoingNotification = llvm::unique_function<void(const T &)>;
137 
138 /// An OutgoingRequest<T> is a function used for outgoing requests to send to
139 /// the client.
140 template <typename T>
142  llvm::unique_function<void(const T &, llvm::json::Value id)>;
143 
144 /// An `OutgoingRequestCallback` is invoked when an outgoing request to the
145 /// client receives a response in turn. It is passed the original request's ID,
146 /// as well as the response result.
147 template <typename T>
149  std::function<void(llvm::json::Value, llvm::Expected<T>)>;
150 
151 /// A handler used to process the incoming transport messages.
153 public:
154  MessageHandler(JSONTransport &transport) : transport(transport) {}
155 
156  bool onNotify(StringRef method, llvm::json::Value value);
157  bool onCall(StringRef method, llvm::json::Value params, llvm::json::Value id);
158  bool onReply(llvm::json::Value id, llvm::Expected<llvm::json::Value> result);
159 
160  template <typename T>
161  static llvm::Expected<T> parse(const llvm::json::Value &raw,
162  StringRef payloadName, StringRef payloadKind) {
163  T result;
164  llvm::json::Path::Root root;
165  if (fromJSON(raw, result, root))
166  return std::move(result);
167 
168  // Dump the relevant parts of the broken message.
169  std::string context;
170  llvm::raw_string_ostream os(context);
171  root.printErrorContext(raw, os);
172 
173  // Report the error (e.g. to the client).
174  return llvm::make_error<LSPError>(
175  llvm::formatv("failed to decode {0} {1}: {2}", payloadName, payloadKind,
176  fmt_consume(root.getError())),
178  }
179 
180  template <typename Param, typename Result, typename ThisT>
181  void method(llvm::StringLiteral method, ThisT *thisPtr,
182  void (ThisT::*handler)(const Param &, Callback<Result>)) {
183  methodHandlers[method] = [method, handler,
184  thisPtr](llvm::json::Value rawParams,
186  llvm::Expected<Param> param = parse<Param>(rawParams, method, "request");
187  if (!param)
188  return reply(param.takeError());
189  (thisPtr->*handler)(*param, std::move(reply));
190  };
191  }
192 
193  template <typename Param, typename ThisT>
194  void notification(llvm::StringLiteral method, ThisT *thisPtr,
195  void (ThisT::*handler)(const Param &)) {
196  notificationHandlers[method] = [method, handler,
197  thisPtr](llvm::json::Value rawParams) {
198  llvm::Expected<Param> param =
199  parse<Param>(rawParams, method, "notification");
200  if (!param) {
201  return llvm::consumeError(
202  llvm::handleErrors(param.takeError(), [](const LSPError &lspError) {
203  Logger::error("JSON parsing error: {0}",
204  lspError.message.c_str());
205  }));
206  }
207  (thisPtr->*handler)(*param);
208  };
209  }
210 
211  /// Create an OutgoingNotification object used for the given method.
212  template <typename T>
214  return [&, method](const T &params) {
215  std::lock_guard<std::mutex> transportLock(transportOutputMutex);
216  Logger::info("--> {0}", method);
217  transport.notify(method, llvm::json::Value(params));
218  };
219  }
220 
221  /// Create an OutgoingRequest function that, when called, sends a request with
222  /// the given method via the transport. Should the outgoing request be
223  /// met with a response, the result JSON is parsed and the response callback
224  /// is invoked.
225  template <typename Param, typename Result>
227  outgoingRequest(llvm::StringLiteral method,
229  return [&, method, callback](const Param &param, llvm::json::Value id) {
230  auto callbackWrapper = [method, callback = std::move(callback)](
231  llvm::json::Value id,
233  if (!value)
234  return callback(std::move(id), value.takeError());
235 
236  std::string responseName = llvm::formatv("reply:{0}({1})", method, id);
237  llvm::Expected<Result> result =
238  parse<Result>(*value, responseName, "response");
239  if (!result)
240  return callback(std::move(id), result.takeError());
241 
242  return callback(std::move(id), *result);
243  };
244 
245  {
246  std::lock_guard<std::mutex> lock(responseHandlersMutex);
247  responseHandlers.insert(
248  {debugString(id), std::make_pair(method.str(), callbackWrapper)});
249  }
250 
251  std::lock_guard<std::mutex> transportLock(transportOutputMutex);
252  Logger::info("--> {0}({1})", method, id);
253  transport.call(method, llvm::json::Value(param), id);
254  };
255  }
256 
257 private:
258  template <typename HandlerT>
259  using HandlerMap = llvm::StringMap<llvm::unique_function<HandlerT>>;
260 
261  HandlerMap<void(llvm::json::Value)> notificationHandlers;
262  HandlerMap<void(llvm::json::Value, Callback<llvm::json::Value>)>
263  methodHandlers;
264 
265  /// A pair of (1) the original request's method name, and (2) the callback
266  /// function to be invoked for responses.
267  using ResponseHandlerTy =
268  std::pair<std::string, OutgoingRequestCallback<llvm::json::Value>>;
269  /// A mapping from request/response ID to response handler.
270  llvm::StringMap<ResponseHandlerTy> responseHandlers;
271  /// Mutex to guard insertion into the response handler map.
272  std::mutex responseHandlersMutex;
273 
274  JSONTransport &transport;
275 
276  /// Mutex to guard sending output messages to the transport.
277  std::mutex transportOutputMutex;
278 };
279 
280 } // namespace lsp
281 } // namespace mlir
282 
283 #endif
Concrete implementation of the JSONTransportInput that reads from a file.
Definition: Transport.h:70
LogicalResult readDelimitedMessage(std::string &json) final
For lit tests we support a simplified syntax:
Definition: Transport.cpp:354
JSONTransportInputOverFile(std::FILE *in, JSONStreamStyle style=JSONStreamStyle::Standard)
Definition: Transport.h:72
LogicalResult readStandardMessage(std::string &json) final
Definition: Transport.cpp:307
An abstract class used by the JSONTransport to read JSON message.
Definition: Transport.h:47
virtual LogicalResult readStandardMessage(std::string &json)=0
virtual bool hasError() const =0
virtual ~JSONTransportInput()=default
JSONTransportInput(JSONStreamStyle style=JSONStreamStyle::Standard)
Definition: Transport.h:49
virtual LogicalResult readDelimitedMessage(std::string &json)=0
virtual bool isEndOfInput() const =0
LogicalResult readMessage(std::string &json)
Read in a message from the input stream.
Definition: Transport.h:57
A transport class that performs the JSON-RPC communication with the LSP client.
Definition: Transport.h:88
void notify(StringRef method, llvm::json::Value params)
The following methods are used to send a message to the LSP client.
Definition: Transport.cpp:177
JSONTransport(std::unique_ptr< JSONTransportInput > in, raw_ostream &out, bool prettyOutput=false)
Definition: Transport.h:90
JSONTransport(std::FILE *in, raw_ostream &out, JSONStreamStyle style=JSONStreamStyle::Standard, bool prettyOutput=false)
Definition: Transport.h:94
void call(StringRef method, llvm::json::Value params, llvm::json::Value id)
Definition: Transport.cpp:184
llvm::Error run(MessageHandler &handler)
Start executing the JSON-RPC transport.
Definition: Transport.cpp:210
void reply(llvm::json::Value id, llvm::Expected< llvm::json::Value > result)
Definition: Transport.cpp:193
This class models an LSP error as an llvm::Error.
Definition: Protocol.h:75
static void info(const char *fmt, Ts &&...vals)
Definition: Logging.h:38
A handler used to process the incoming transport messages.
Definition: Transport.h:152
void notification(llvm::StringLiteral method, ThisT *thisPtr, void(ThisT::*handler)(const Param &))
Definition: Transport.h:194
bool onCall(StringRef method, llvm::json::Value params, llvm::json::Value id)
Definition: Transport.cpp:102
void method(llvm::StringLiteral method, ThisT *thisPtr, void(ThisT::*handler)(const Param &, Callback< Result >))
Definition: Transport.h:181
bool onReply(llvm::json::Value id, llvm::Expected< llvm::json::Value > result)
Definition: Transport.cpp:118
OutgoingNotification< T > outgoingNotification(llvm::StringLiteral method)
Create an OutgoingNotification object used for the given method.
Definition: Transport.h:213
static llvm::Expected< T > parse(const llvm::json::Value &raw, StringRef payloadName, StringRef payloadKind)
Definition: Transport.h:161
MessageHandler(JSONTransport &transport)
Definition: Transport.h:154
OutgoingRequest< Param > outgoingRequest(llvm::StringLiteral method, OutgoingRequestCallback< Result > callback)
Create an OutgoingRequest function that, when called, sends a request with the given method via the t...
Definition: Transport.h:227
bool onNotify(StringRef method, llvm::json::Value value)
Definition: Transport.cpp:87
llvm::unique_function< void(const T &)> OutgoingNotification
An OutgoingNotification<T> is a function used for outgoing notifications send to the client.
Definition: Transport.h:136
llvm::unique_function< void(const T &, llvm::json::Value id)> OutgoingRequest
An OutgoingRequest<T> is a function used for outgoing requests to send to the client.
Definition: Transport.h:142
llvm::unique_function< void(llvm::Expected< T >)> Callback
A Callback<T> is a void function that accepts Expected<T>.
Definition: Transport.h:131
std::function< void(llvm::json::Value, llvm::Expected< T >)> OutgoingRequestCallback
An OutgoingRequestCallback is invoked when an outgoing request to the client receives a response in t...
Definition: Transport.h:149
JSONStreamStyle
The encoding style of the JSON-RPC messages (both input and output).
Definition: Transport.h:39
@ Delimited
Messages are delimited by a '// --—' line. Comment lines start with //.
Definition: Transport.h:43
@ Standard
Encoding per the LSP specification, with mandatory Content-Length header.
Definition: Transport.h:41
bool fromJSON(const llvm::json::Value &value, URIForFile &result, llvm::json::Path path)
Definition: Protocol.cpp:247
Include the generated interface declarations.
static std::string debugString(T &&op)