blob: 4ea363df4e2aada5dd51a831dfa3d4601e10f1de [file] [log] [blame]
// 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 &notification_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