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