MLIR  20.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 /// A transport class that performs the JSON-RPC communication with the LSP
47 /// client.
49 public:
50  JSONTransport(std::FILE *in, raw_ostream &out,
52  bool prettyOutput = false)
53  : in(in), out(out), style(style), prettyOutput(prettyOutput) {}
54 
55  /// The following methods are used to send a message to the LSP client.
56  void notify(StringRef method, llvm::json::Value params);
57  void call(StringRef method, llvm::json::Value params, llvm::json::Value id);
58  void reply(llvm::json::Value id, llvm::Expected<llvm::json::Value> result);
59 
60  /// Start executing the JSON-RPC transport.
61  llvm::Error run(MessageHandler &handler);
62 
63 private:
64  /// Dispatches the given incoming json message to the message handler.
65  bool handleMessage(llvm::json::Value msg, MessageHandler &handler);
66  /// Writes the given message to the output stream.
67  void sendMessage(llvm::json::Value msg);
68 
69  /// Read in a message from the input stream.
70  LogicalResult readMessage(std::string &json) {
71  return style == JSONStreamStyle::Delimited ? readDelimitedMessage(json)
72  : readStandardMessage(json);
73  }
74  LogicalResult readDelimitedMessage(std::string &json);
75  LogicalResult readStandardMessage(std::string &json);
76 
77  /// An output buffer used when building output messages.
78  SmallVector<char, 0> outputBuffer;
79  /// The input file stream.
80  std::FILE *in;
81  /// The output file stream.
82  raw_ostream &out;
83  /// The JSON stream style to use.
84  JSONStreamStyle style;
85  /// If the output JSON should be formatted for easier readability.
86  bool prettyOutput;
87 };
88 
89 //===----------------------------------------------------------------------===//
90 // MessageHandler
91 //===----------------------------------------------------------------------===//
92 
93 /// A Callback<T> is a void function that accepts Expected<T>. This is
94 /// accepted by functions that logically return T.
95 template <typename T>
96 using Callback = llvm::unique_function<void(llvm::Expected<T>)>;
97 
98 /// An OutgoingNotification<T> is a function used for outgoing notifications
99 /// send to the client.
100 template <typename T>
101 using OutgoingNotification = llvm::unique_function<void(const T &)>;
102 
103 /// An OutgoingRequest<T> is a function used for outgoing requests to send to
104 /// the client.
105 template <typename T>
107  llvm::unique_function<void(const T &, llvm::json::Value id)>;
108 
109 /// An `OutgoingRequestCallback` is invoked when an outgoing request to the
110 /// client receives a response in turn. It is passed the original request's ID,
111 /// as well as the response result.
112 template <typename T>
114  std::function<void(llvm::json::Value, llvm::Expected<T>)>;
115 
116 /// A handler used to process the incoming transport messages.
118 public:
119  MessageHandler(JSONTransport &transport) : transport(transport) {}
120 
121  bool onNotify(StringRef method, llvm::json::Value value);
122  bool onCall(StringRef method, llvm::json::Value params, llvm::json::Value id);
123  bool onReply(llvm::json::Value id, llvm::Expected<llvm::json::Value> result);
124 
125  template <typename T>
126  static llvm::Expected<T> parse(const llvm::json::Value &raw,
127  StringRef payloadName, StringRef payloadKind) {
128  T result;
129  llvm::json::Path::Root root;
130  if (fromJSON(raw, result, root))
131  return std::move(result);
132 
133  // Dump the relevant parts of the broken message.
134  std::string context;
135  llvm::raw_string_ostream os(context);
136  root.printErrorContext(raw, os);
137 
138  // Report the error (e.g. to the client).
139  return llvm::make_error<LSPError>(
140  llvm::formatv("failed to decode {0} {1}: {2}", payloadName, payloadKind,
141  fmt_consume(root.getError())),
143  }
144 
145  template <typename Param, typename Result, typename ThisT>
146  void method(llvm::StringLiteral method, ThisT *thisPtr,
147  void (ThisT::*handler)(const Param &, Callback<Result>)) {
148  methodHandlers[method] = [method, handler,
149  thisPtr](llvm::json::Value rawParams,
151  llvm::Expected<Param> param = parse<Param>(rawParams, method, "request");
152  if (!param)
153  return reply(param.takeError());
154  (thisPtr->*handler)(*param, std::move(reply));
155  };
156  }
157 
158  template <typename Param, typename ThisT>
159  void notification(llvm::StringLiteral method, ThisT *thisPtr,
160  void (ThisT::*handler)(const Param &)) {
161  notificationHandlers[method] = [method, handler,
162  thisPtr](llvm::json::Value rawParams) {
163  llvm::Expected<Param> param =
164  parse<Param>(rawParams, method, "notification");
165  if (!param) {
166  return llvm::consumeError(
167  llvm::handleErrors(param.takeError(), [](const LSPError &lspError) {
168  Logger::error("JSON parsing error: {0}",
169  lspError.message.c_str());
170  }));
171  }
172  (thisPtr->*handler)(*param);
173  };
174  }
175 
176  /// Create an OutgoingNotification object used for the given method.
177  template <typename T>
179  return [&, method](const T &params) {
180  std::lock_guard<std::mutex> transportLock(transportOutputMutex);
181  Logger::info("--> {0}", method);
182  transport.notify(method, llvm::json::Value(params));
183  };
184  }
185 
186  /// Create an OutgoingRequest function that, when called, sends a request with
187  /// the given method via the transport. Should the outgoing request be
188  /// met with a response, the result JSON is parsed and the response callback
189  /// is invoked.
190  template <typename Param, typename Result>
192  outgoingRequest(llvm::StringLiteral method,
194  return [&, method, callback](const Param &param, llvm::json::Value id) {
195  auto callbackWrapper = [method, callback = std::move(callback)](
196  llvm::json::Value id,
198  if (!value)
199  return callback(std::move(id), value.takeError());
200 
201  std::string responseName = llvm::formatv("reply:{0}({1})", method, id);
202  llvm::Expected<Result> result =
203  parse<Result>(*value, responseName, "response");
204  if (!result)
205  return callback(std::move(id), result.takeError());
206 
207  return callback(std::move(id), *result);
208  };
209 
210  {
211  std::lock_guard<std::mutex> lock(responseHandlersMutex);
212  responseHandlers.insert(
213  {debugString(id), std::make_pair(method.str(), callbackWrapper)});
214  }
215 
216  std::lock_guard<std::mutex> transportLock(transportOutputMutex);
217  Logger::info("--> {0}({1})", method, id);
218  transport.call(method, llvm::json::Value(param), id);
219  };
220  }
221 
222 private:
223  template <typename HandlerT>
224  using HandlerMap = llvm::StringMap<llvm::unique_function<HandlerT>>;
225 
226  HandlerMap<void(llvm::json::Value)> notificationHandlers;
227  HandlerMap<void(llvm::json::Value, Callback<llvm::json::Value>)>
228  methodHandlers;
229 
230  /// A pair of (1) the original request's method name, and (2) the callback
231  /// function to be invoked for responses.
232  using ResponseHandlerTy =
233  std::pair<std::string, OutgoingRequestCallback<llvm::json::Value>>;
234  /// A mapping from request/response ID to response handler.
235  llvm::StringMap<ResponseHandlerTy> responseHandlers;
236  /// Mutex to guard insertion into the response handler map.
237  std::mutex responseHandlersMutex;
238 
239  JSONTransport &transport;
240 
241  /// Mutex to guard sending output messages to the transport.
242  std::mutex transportOutputMutex;
243 };
244 
245 } // namespace lsp
246 } // namespace mlir
247 
248 #endif
A transport class that performs the JSON-RPC communication with the LSP client.
Definition: Transport.h:48
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::FILE *in, raw_ostream &out, JSONStreamStyle style=JSONStreamStyle::Standard, bool prettyOutput=false)
Definition: Transport.h:50
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:117
void notification(llvm::StringLiteral method, ThisT *thisPtr, void(ThisT::*handler)(const Param &))
Definition: Transport.h:159
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:146
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:178
static llvm::Expected< T > parse(const llvm::json::Value &raw, StringRef payloadName, StringRef payloadKind)
Definition: Transport.h:126
MessageHandler(JSONTransport &transport)
Definition: Transport.h:119
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:192
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:101
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:107
llvm::unique_function< void(llvm::Expected< T >)> Callback
A Callback<T> is a void function that accepts Expected<T>.
Definition: Transport.h:96
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:114
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)