| // Copyright 2021 The Verible Authors. | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| #ifndef VERIBLE_COMMON_LSP_JSON_RPC_DISPATCHER_H | |
| #define VERIBLE_COMMON_LSP_JSON_RPC_DISPATCHER_H | |
| #include <functional> | |
| #include <map> | |
| #include <sstream> | |
| #include <string> | |
| #include <unordered_map> | |
| #include "absl/strings/str_cat.h" | |
| #include "absl/strings/string_view.h" | |
| #include "nlohmann/json.hpp" | |
| namespace verible { | |
| namespace lsp { | |
| // A Dispatcher that is fed JSON as string, parses them to json objects and | |
| // dispatches the contained method call to pre-registered handlers. | |
| // Results of RPCCallHandlers are wrapped in a json rpc response object | |
| // and written out to the provided write function. | |
| // | |
| // This implements the JSON RPC specification [1]. | |
| // | |
| // All receiving (call to DispatchMessage()) and writing of response (WriteFun) | |
| // is abstracted out to make the dispatcher agnostic of the transport layer. | |
| // | |
| // The RPCHandlers take and return json objects, but since nlohmann::json | |
| // provides ways to auto-convert objects to json, it is possible to | |
| // register properly typed handlers. To create the boilerplate for custom | |
| // types and their conversion, simplest is to use a code generator such | |
| // as jcxxgen [2]. | |
| // | |
| // With that, you then can register fully typed handlers with seamless | |
| // conversion | |
| // dispatcer.AddRequestHandler("MyMethod", | |
| // [](const MyParamType &p) -> MyResponseType { | |
| // return doSomething(p); | |
| // }); | |
| // | |
| // [1]: https://www.jsonrpc.org/specification | |
| // [2]: https://github.com/hzeller/jcxxgen | |
| class JsonRpcDispatcher { | |
| public: | |
| // Magic constants defined in https://www.jsonrpc.org/specification | |
| static constexpr int kParseError = -32700; | |
| static constexpr int kMethodNotFound = -32601; | |
| static constexpr int kInternalError = -32603; | |
| // A notification receives a request, but does not return anything | |
| using RPCNotification = std::function<void(const nlohmann::json &r)>; | |
| // A RPC call receives a request and returns a response. | |
| // If we ever have a meaningful set of error conditions to convey, maybe | |
| // change this to absl::StatusOr<nlohmann::json> as return value. | |
| using RPCCallHandler = std::function<nlohmann::json(const nlohmann::json &)>; | |
| // A function of type WriteFun is called by the dispatcher to send the | |
| // string-formatted json response. The user of the JsonRpcDispatcher then | |
| // can wire that to the underlying transport. | |
| using WriteFun = std::function<void(absl::string_view response)>; | |
| // Some statistical counters of method calls or exceptions encountered. | |
| using StatsMap = std::map<std::string, int>; | |
| // Responses are written using the "out" write function. | |
| explicit JsonRpcDispatcher(WriteFun out) : write_fun_(std::move(out)) {} | |
| JsonRpcDispatcher(const JsonRpcDispatcher &) = delete; | |
| // Add a request handler for RPC calls that receive data and send a response. | |
| // Returns successful registration, false if that name is already registered. | |
| bool AddRequestHandler(const std::string &method_name, | |
| const RPCCallHandler &fun) { | |
| return handlers_.insert({method_name, fun}).second; | |
| } | |
| // Add a request handler for RPC Notifications, that are receive-only events. | |
| // Returns successful registration, false if that name is already registered. | |
| bool AddNotificationHandler(const std::string &method_name, | |
| const RPCNotification &fun) { | |
| return notifications_.insert({method_name, fun}).second; | |
| } | |
| // Dispatch incoming message, a string view with json data. | |
| // Call this with the content of exactly one message. | |
| // If this is an RPC call, response will call WriteFun. | |
| void DispatchMessage(absl::string_view data); | |
| // Send a notification to the client side. Parameters will be wrapped | |
| // in a JSON-RPC message and pushed out to the WriteFun | |
| void SendNotification(const std::string &method, | |
| const nlohmann::json ¬ification_params); | |
| // Get some human-readable statistical counters of methods called | |
| // and exception messages encountered. | |
| const StatsMap &GetStatCounters() const { return statistic_counters_; } | |
| // Number of exceptions that have been dealt with and turned into error | |
| // messages or ignored depending on the context. | |
| // The counters returned by GetStatsCounters() will report counts by | |
| // exception message. | |
| int exception_count() const { return exception_count_; } | |
| private: | |
| bool CallNotification(const nlohmann::json &req, const std::string &method); | |
| bool CallRequestHandler(const nlohmann::json &req, const std::string &method); | |
| void SendReply(const nlohmann::json &response); | |
| static nlohmann::json CreateError(const nlohmann::json &request, int code, | |
| absl::string_view message); | |
| static nlohmann::json MakeResponse(const nlohmann::json &request, | |
| const nlohmann::json &call_result); | |
| const WriteFun write_fun_; | |
| std::unordered_map<std::string, RPCCallHandler> handlers_; | |
| std::unordered_map<std::string, RPCNotification> notifications_; | |
| int exception_count_ = 0; | |
| StatsMap statistic_counters_; | |
| }; | |
| } // namespace lsp | |
| } // namespace verible | |
| #endif // VERIBLE_COMMON_LSP_JSON_RPC_DISPATCHER_H |