JSON-RPC 2.0
JSON-RPC 2.0 Modern C++ Library
Loading...
Searching...
No Matches
client.cpp
Go to the documentation of this file.
2
3#include <spdlog/spdlog.h>
4
5namespace jsonrpc::client {
6
7Client::Client(std::unique_ptr<transport::Transport> transport)
8 : transport_(std::move(transport)) {
9 spdlog::info("Initializing JSON-RPC client");
10}
11
13 spdlog::info("Starting JSON-RPC client");
14 is_running_.store(true);
15 listener_ = std::thread(&Client::Listener, this);
16}
17
19 spdlog::info("Stopping JSON-RPC client");
20 is_running_.store(false);
21 if (listener_.joinable()) {
22 listener_.join();
23 }
24}
25
26auto Client::IsRunning() const -> bool {
27 return is_running_.load();
28}
29
30auto Client::HasPendingRequests() const -> bool {
31 std::lock_guard<std::mutex> lock(requests_mutex_);
32 return !requests_map_.empty();
33}
34
35void Client::Listener() {
36 spdlog::info("Starting JSON-RPC client listener thread");
37 while (is_running_) {
38 if (expected_count_ > 0) {
39 std::string response = transport_->ReceiveMessage();
40 HandleResponse(response);
41 expected_count_--;
42 }
43 }
44}
45
47 const std::string &method,
48 std::optional<nlohmann::json> params) -> nlohmann::json {
49 Request request(method, std::move(params), false, [this]() {
50 return GetNextRequestId();
51 });
52 return SendRequest(request);
53}
54
56 const std::string &method,
57 std::optional<nlohmann::json> params) -> std::future<nlohmann::json> {
58 Request request(method, std::move(params), false, [this]() {
59 return GetNextRequestId();
60 });
61 return SendRequestAsync(request);
62}
63
65 const std::string &method, std::optional<nlohmann::json> params) {
66 Request request(
67 method, std::move(params), true, [this]() { return GetNextRequestId(); });
68 transport_->SendMessage(request.Dump());
69}
70
71auto Client::SendRequest(const Request &request) -> nlohmann::json {
72 auto future_response = SendRequestAsync(request);
73 return future_response.get();
74}
75
76auto Client::SendRequestAsync(const Request &request)
77 -> std::future<nlohmann::json> {
78 assert(
79 request.RequiresResponse() &&
80 "SendRequestAsync called for a request "
81 "that does not require a response.");
82
83 std::promise<nlohmann::json> response_promise;
84 auto future_response = response_promise.get_future();
85
86 {
87 std::lock_guard<std::mutex> lock(requests_mutex_);
88 requests_map_[request.GetKey()] = std::move(response_promise);
89 }
90 expected_count_++;
91
92 transport_->SendMessage(request.Dump());
93
94 return future_response;
95}
96
97auto Client::GetNextRequestId() -> int {
98 return req_id_counter_++;
99}
100
101void Client::HandleResponse(const std::string &response) {
102 nlohmann::json json_response;
103 try {
104 json_response = nlohmann::json::parse(response);
105 } catch (const std::exception &e) {
106 spdlog::error("Failed to parse JSON response: {}", e.what());
107 throw std::runtime_error(
108 "Failed to parse JSON response: " + std::string(e.what()));
109 }
110
111 if (ValidateResponse(json_response)) {
112 int request_id = json_response["id"].get<int>();
113
114 std::lock_guard<std::mutex> lock(requests_mutex_);
115 auto req_iter = requests_map_.find(request_id);
116 if (req_iter != requests_map_.end()) {
117 req_iter->second.set_value(json_response);
118 requests_map_.erase(req_iter);
119 } else {
120 spdlog::error("Received response for unknown request ID: {}", request_id);
121 throw std::runtime_error(
122 "Received response for unknown request ID: " +
123 std::to_string(request_id));
124 }
125 } else {
126 spdlog::error("Invalid JSON-RPC response: {}", json_response.dump());
127 throw std::runtime_error(
128 "Invalid JSON-RPC response: " + json_response.dump());
129 }
130}
131
132auto Client::ValidateResponse(const nlohmann::json &response) -> bool {
133 if (!response.contains("jsonrpc") || response["jsonrpc"] != "2.0") {
134 return false;
135 }
136
137 if (!response.contains("id")) {
138 return false;
139 }
140
141 bool has_result = response.contains("result");
142 bool has_error = response.contains("error");
143
144 if (has_result == has_error) { // Mutually exclusive
145 return false;
146 }
147
148 if (has_error) {
149 const nlohmann::json &error = response["error"];
150 if (!error.contains("code") || !error["code"].is_number() ||
151 !error.contains("message") || !error["message"].is_string()) {
152 return false;
153 }
154 }
155
156 return true;
157}
158
159} // namespace jsonrpc::client
void SendNotification(const std::string &method, std::optional< nlohmann::json > params=std::nullopt)
Sends a JSON-RPC notification.
Definition client.cpp:64
void Start()
Starts the JSON-RPC client listener thread.
Definition client.cpp:12
Client(std::unique_ptr< transport::Transport > transport)
Constructs a JSON-RPC client with a specified transport layer.
Definition client.cpp:7
auto IsRunning() const -> bool
Checks if the client listener is running.
Definition client.cpp:26
auto SendMethodCall(const std::string &method, std::optional< nlohmann::json > params=std::nullopt) -> nlohmann::json
Sends a JSON-RPC method call and waits for the response.
Definition client.cpp:46
auto SendMethodCallAsync(const std::string &method, std::optional< nlohmann::json > params=std::nullopt) -> std::future< nlohmann::json >
Sends a JSON-RPC method call asynchronously.
Definition client.cpp:55
void Stop()
Stops the JSON-RPC client listener thread.
Definition client.cpp:18
auto HasPendingRequests() const -> bool
Checks if there are any pending requests.
Definition client.cpp:30
Represents a JSON-RPC request.
Definition request.hpp:17