JSON-RPC 2.0
JSON-RPC 2.0 Modern C++ Library
Loading...
Searching...
No Matches
jsonrpc::endpoint::RpcEndpoint Class Reference

#include <endpoint.hpp>

Collaboration diagram for jsonrpc::endpoint::RpcEndpoint:

Public Member Functions

 RpcEndpoint (asio::any_io_executor executor, std::unique_ptr< transport::Transport > transport, std::shared_ptr< spdlog::logger > logger=nullptr)
 
 RpcEndpoint (const RpcEndpoint &)=delete
 
 RpcEndpoint (RpcEndpoint &&)=delete
 
auto operator= (const RpcEndpoint &) -> RpcEndpoint &=delete
 
auto operator= (RpcEndpoint &&) -> RpcEndpoint &=delete
 
 ~RpcEndpoint ()=default
 
auto Start () -> asio::awaitable< std::expected< void, RpcError > >
 
auto WaitForShutdown () -> asio::awaitable< std::expected< void, RpcError > >
 
auto Shutdown () -> asio::awaitable< std::expected< void, RpcError > >
 
auto IsRunning () const -> bool
 
auto Logger () -> std::shared_ptr< spdlog::logger >
 
auto SendMethodCall (std::string method, std::optional< nlohmann::json > params=std::nullopt) -> asio::awaitable< std::expected< nlohmann::json, RpcError > >
 
template<typename ParamsType , typename ResultType >
auto SendMethodCall (std::string method, ParamsType params) -> asio::awaitable< std::expected< ResultType, RpcError > > requires(ToJson< ParamsType > &&NotJsonLike< ParamsType > &&FromJson< ResultType >)
 
auto SendNotification (std::string method, std::optional< nlohmann::json > params=std::nullopt) -> asio::awaitable< std::expected< void, RpcError > >
 
template<typename ParamsType >
auto SendNotification (std::string method, ParamsType params) -> asio::awaitable< std::expected< void, RpcError > > requires(ToJson< ParamsType > &&NotJsonLike< ParamsType >)
 
void RegisterMethodCall (std::string method, typename Dispatcher::MethodCallHandler handler)
 
template<typename ParamsType , typename ResultType >
requires (FromJson<ParamsType> && ToJson<ResultType>)
void RegisterMethodCall (std::string method, std::function< asio::awaitable< ResultType >(ParamsType)> handler)
 
template<typename ParamsType , typename ResultType , typename ErrorType >
requires (FromJson<ParamsType> && ToJson<ResultType> && ToJson<ErrorType>)
void RegisterMethodCall (std::string method, std::function< asio::awaitable< std::expected< ResultType, ErrorType > >(ParamsType)> handler)
 
void RegisterNotification (std::string method, typename Dispatcher::NotificationHandler handler)
 
template<typename ParamsType , typename ErrorType >
requires (FromJson<ParamsType> && HasMessageMethod<ErrorType>)
void RegisterNotification (std::string method, std::function< asio::awaitable< std::expected< void, ErrorType > >(ParamsType)> handler)
 
auto HasPendingRequests () const -> bool
 

Static Public Member Functions

static auto CreateClient (asio::any_io_executor executor, std::unique_ptr< transport::Transport > transport) -> asio::awaitable< std::expected< std::unique_ptr< RpcEndpoint >, RpcError > >
 

Detailed Description

Definition at line 26 of file endpoint.hpp.

Constructor & Destructor Documentation

◆ RpcEndpoint() [1/3]

jsonrpc::endpoint::RpcEndpoint::RpcEndpoint ( asio::any_io_executor executor,
std::unique_ptr< transport::Transport > transport,
std::shared_ptr< spdlog::logger > logger = nullptr )
explicit

Definition at line 16 of file endpoint.cpp.

20 : logger_(logger ? logger : spdlog::default_logger()),
21 executor_(std::move(executor)),
22 transport_(std::move(transport)),
23 dispatcher_(executor_, logger_),
24 endpoint_strand_(asio::make_strand(executor_)) {
25}

◆ RpcEndpoint() [2/3]

jsonrpc::endpoint::RpcEndpoint::RpcEndpoint ( const RpcEndpoint & )
delete

◆ RpcEndpoint() [3/3]

jsonrpc::endpoint::RpcEndpoint::RpcEndpoint ( RpcEndpoint && )
delete

◆ ~RpcEndpoint()

jsonrpc::endpoint::RpcEndpoint::~RpcEndpoint ( )
default

Member Function Documentation

◆ CreateClient()

auto jsonrpc::endpoint::RpcEndpoint::CreateClient ( asio::any_io_executor executor,
std::unique_ptr< transport::Transport > transport ) -> asio::awaitable<std::expected<std::unique_ptr<RpcEndpoint>, RpcError>>
static

Definition at line 27 of file endpoint.cpp.

30 {
31 auto endpoint = std::make_unique<RpcEndpoint>(executor, std::move(transport));
32
33 auto start_result = co_await endpoint->Start();
34 if (!start_result) {
35 co_return std::unexpected(start_result.error());
36 }
37
38 endpoint->Logger()->debug("Client endpoint initialized");
39 co_return endpoint;
40}

◆ HasPendingRequests()

auto jsonrpc::endpoint::RpcEndpoint::HasPendingRequests ( ) const -> bool
nodiscard

Definition at line 174 of file endpoint.cpp.

174 {
175 // This is safe to call without a strand because we're just checking if empty
176 return !pending_requests_.empty();
177}

◆ IsRunning()

auto jsonrpc::endpoint::RpcEndpoint::IsRunning ( ) const -> bool
inlinenodiscard

Definition at line 51 of file endpoint.hpp.

51 {
52 return is_running_.load();
53 }

◆ Logger()

auto jsonrpc::endpoint::RpcEndpoint::Logger ( ) -> std::shared_ptr<spdlog::logger>
inline

Definition at line 55 of file endpoint.hpp.

55 {
56 return logger_;
57 }

◆ operator=() [1/2]

auto jsonrpc::endpoint::RpcEndpoint::operator= ( const RpcEndpoint & ) -> RpcEndpoint &=delete
delete

◆ operator=() [2/2]

auto jsonrpc::endpoint::RpcEndpoint::operator= ( RpcEndpoint && ) -> RpcEndpoint &=delete
delete

◆ RegisterMethodCall() [1/3]

template<typename ParamsType , typename ResultType >
requires (FromJson<ParamsType> && ToJson<ResultType>)
void jsonrpc::endpoint::RpcEndpoint::RegisterMethodCall ( std::string method,
std::function< asio::awaitable< ResultType >(ParamsType)> handler )

Definition at line 206 of file endpoint.hpp.

210{
211 // Create a handler object and store its function object
212 // NOTE: We use a shared_ptr to ensure the handler object stays alive
213 // throughout the entire lifetime of any coroutine that might use it. This
214 // prevents the use-after-free issues that can occur with lambda captures in
215 // coroutines.
216 auto typed_handler =
217 std::make_shared<TypedMethodHandler<ParamsType, ResultType>>(
218 std::move(handler));
219
220 // Register a lambda that calls the handler object
222 method,
223 [handler = std::move(typed_handler)](
224 std::optional<nlohmann::json> params) { return (*handler)(params); });
225}
void RegisterMethodCall(std::string method, typename Dispatcher::MethodCallHandler handler)
Definition endpoint.cpp:164

◆ RegisterMethodCall() [2/3]

template<typename ParamsType , typename ResultType , typename ErrorType >
requires (FromJson<ParamsType> && ToJson<ResultType> && ToJson<ErrorType>)
void jsonrpc::endpoint::RpcEndpoint::RegisterMethodCall ( std::string method,
std::function< asio::awaitable< std::expected< ResultType, ErrorType > >(ParamsType)> handler )

Definition at line 228 of file endpoint.hpp.

234{
235 auto typed_handler =
236 std::make_shared<TypedMethodHandler<ParamsType, ResultType, ErrorType>>(
237 std::move(handler));
238
240 method,
241 [handler = std::move(typed_handler)](
242 std::optional<nlohmann::json> params) { return (*handler)(params); });
243}

◆ RegisterMethodCall() [3/3]

void jsonrpc::endpoint::RpcEndpoint::RegisterMethodCall ( std::string method,
typename Dispatcher::MethodCallHandler handler )

Definition at line 164 of file endpoint.cpp.

165 {
166 dispatcher_.RegisterMethodCall(method, handler);
167}
void RegisterMethodCall(const std::string &method, const MethodCallHandler &handler)

References jsonrpc::endpoint::Dispatcher::RegisterMethodCall().

Here is the call graph for this function:

◆ RegisterNotification() [1/2]

template<typename ParamsType , typename ErrorType >
requires (FromJson<ParamsType> && HasMessageMethod<ErrorType>)
void jsonrpc::endpoint::RpcEndpoint::RegisterNotification ( std::string method,
std::function< asio::awaitable< std::expected< void, ErrorType > >(ParamsType)> handler )

Definition at line 246 of file endpoint.hpp.

251{
252 // Create a handler object and store its function object
253 // NOTE: Using a class-based approach with shared_ptr ownership guarantees
254 // the handler remains valid even when coroutines are suspended and resumed,
255 // which is safer than direct lambda captures that may go out of scope.
256 auto typed_handler =
257 std::make_shared<TypedNotificationHandler<ParamsType, ErrorType>>(
258 std::move(handler));
259
260 // Register a lambda that calls the handler object
262 method,
263 [handler = std::move(typed_handler)](
264 std::optional<nlohmann::json> params) { return (*handler)(params); });
265}
void RegisterNotification(std::string method, typename Dispatcher::NotificationHandler handler)
Definition endpoint.cpp:169

◆ RegisterNotification() [2/2]

void jsonrpc::endpoint::RpcEndpoint::RegisterNotification ( std::string method,
typename Dispatcher::NotificationHandler handler )

Definition at line 169 of file endpoint.cpp.

170 {
171 dispatcher_.RegisterNotification(method, handler);
172}
void RegisterNotification(const std::string &method, const NotificationHandler &handler)

References jsonrpc::endpoint::Dispatcher::RegisterNotification().

Here is the call graph for this function:

◆ SendMethodCall() [1/2]

template<typename ParamsType , typename ResultType >
auto jsonrpc::endpoint::RpcEndpoint::SendMethodCall ( std::string method,
ParamsType params ) -> asio::awaitable<std::expected<ResultType, RpcError>> requires( ToJson<ParamsType> && NotJsonLike<ParamsType> && FromJson<ResultType>)

Definition at line 146 of file endpoint.hpp.

150{
151 spdlog::debug("RpcEndpoint sending typed method call: {}", method);
152 nlohmann::json json_params;
153 try {
154 json_params = params;
155 } catch (const nlohmann::json::exception &ex) {
156 spdlog::error(
157 "RpcEndpoint failed to convert parameters to JSON: {}", ex.what());
159 RpcErrorCode::kClientSerializationError,
160 "RpcEndpoint failed to convert parameters to JSON: " +
161 std::string(ex.what()));
162 }
163
164 auto result = co_await RpcEndpoint::SendMethodCall(method, json_params);
165 if (!result) {
166 co_return result;
167 }
168
169 try {
170 co_return result->template get<ResultType>();
171 } catch (const nlohmann::json::exception &ex) {
172 spdlog::error("RpcEndpoint failed to convert result: {}", ex.what());
174 RpcErrorCode::kClientDeserializationError,
175 "RpcEndpoint failed to convert result: " + std::string(ex.what()));
176 }
177}
auto SendMethodCall(std::string method, std::optional< nlohmann::json > params=std::nullopt) -> asio::awaitable< std::expected< nlohmann::json, RpcError > >
Definition endpoint.cpp:110
static auto UnexpectedFromCode(RpcErrorCode code, std::string message="") -> std::unexpected< RpcError >
Definition error.hpp:101

References SendMethodCall(), and jsonrpc::error::RpcError::UnexpectedFromCode().

Here is the call graph for this function:

◆ SendMethodCall() [2/2]

auto jsonrpc::endpoint::RpcEndpoint::SendMethodCall ( std::string method,
std::optional< nlohmann::json > params = std::nullopt ) -> asio::awaitable<std::expected<nlohmann::json, RpcError>>

Definition at line 110 of file endpoint.cpp.

112 {
113 if (!is_running_) {
115 RpcErrorCode::kClientError, "RPC endpoint is not running");
116 }
117
118 auto request_id = GetNextRequestId();
119 Request request(method, std::move(params), request_id);
120 std::string message = request.ToJson().dump();
121
122 Logger()->debug("RpcEndpoint sending message: {}", message.substr(0, 70));
123 auto pending_request = std::make_shared<PendingRequest>(endpoint_strand_);
124 asio::post(endpoint_strand_, [this, request_id, pending_request] {
125 pending_requests_[request_id] = pending_request;
126 });
127
128 auto send_result = co_await transport_->SendMessage(message);
129 if (!send_result) {
130 co_return std::unexpected(send_result.error());
131 }
132
133 auto result = co_await pending_request->GetResult();
134 if (result.contains("error")) {
135 auto err = result["error"];
137 RpcErrorCode::kClientError, err["message"].get<std::string>());
138 }
139
140 co_return result["result"];
141}
auto Logger() -> std::shared_ptr< spdlog::logger >
Definition endpoint.hpp:55

References jsonrpc::endpoint::Request::ToJson(), and jsonrpc::error::RpcError::UnexpectedFromCode().

Referenced by SendMethodCall().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ SendNotification() [1/2]

template<typename ParamsType >
auto jsonrpc::endpoint::RpcEndpoint::SendNotification ( std::string method,
ParamsType params ) -> asio::awaitable<std::expected<void, RpcError>> requires(ToJson<ParamsType> && NotJsonLike<ParamsType>)

Definition at line 180 of file endpoint.hpp.

183{
184 spdlog::debug("RpcEndpoint sending typed notification: {}", method);
185 nlohmann::json json_params;
186 try {
187 json_params = params;
188 } catch (const nlohmann::json::exception &ex) {
189 spdlog::error(
190 "RpcEndpoint failed to convert notification parameters: {}", ex.what());
192 RpcErrorCode::kClientSerializationError,
193 "RpcEndpoint failed to convert notification parameters: " +
194 std::string(ex.what()));
195 }
196
197 auto result = co_await RpcEndpoint::SendNotification(method, json_params);
198 if (!result) {
199 co_return result;
200 }
201
202 co_return Ok();
203}
auto SendNotification(std::string method, std::optional< nlohmann::json > params=std::nullopt) -> asio::awaitable< std::expected< void, RpcError > >
Definition endpoint.cpp:143

References SendNotification(), and jsonrpc::error::RpcError::UnexpectedFromCode().

Here is the call graph for this function:

◆ SendNotification() [2/2]

auto jsonrpc::endpoint::RpcEndpoint::SendNotification ( std::string method,
std::optional< nlohmann::json > params = std::nullopt ) -> asio::awaitable<std::expected<void, RpcError>>

Definition at line 143 of file endpoint.cpp.

145 {
146 Logger()->debug("RpcEndpoint sending notification: {}", method);
147 if (!is_running_) {
149 RpcErrorCode::kClientError, "RpcEndpoint is not running");
150 }
151
152 Request request(method, std::move(params));
153 std::string message = request.ToJson().dump();
154
155 Logger()->debug("RpcEndpoint sending message: {}", message.substr(0, 70));
156 auto send_result = co_await transport_->SendMessage(message);
157 if (!send_result) {
158 co_return send_result;
159 }
160
161 co_return std::expected<void, RpcError>{};
162}

References jsonrpc::endpoint::Request::ToJson(), and jsonrpc::error::RpcError::UnexpectedFromCode().

Referenced by SendNotification().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ Shutdown()

auto jsonrpc::endpoint::RpcEndpoint::Shutdown ( ) -> asio::awaitable<std::expected<void, RpcError>>

Definition at line 78 of file endpoint.cpp.

78 {
79 if (!is_running_.exchange(false)) {
80 co_return std::expected<void, RpcError>{};
81 }
82
83 co_await asio::post(endpoint_strand_, asio::use_awaitable);
84 cancel_signal_.emit(asio::cancellation_type::all);
85
86 Logger()->debug("Shutting down RPC endpoint");
87
88 // Ensure all operations on the strand complete, including message processing
89
90 // Cancel pending requests
91 for (auto &[id, request] : pending_requests_) {
92 request->Cancel(-32603, "RPC endpoint shutting down");
93 }
94 pending_requests_.clear();
95
96 // Wait for the message loop to complete
97 if (message_loop_.valid()) {
98 co_await std::move(message_loop_);
99 }
100
101 // Now close the transport
102 auto close_result = co_await transport_->Close();
103 if (!close_result) {
104 co_return close_result;
105 }
106
107 co_return Ok();
108}

◆ Start()

auto jsonrpc::endpoint::RpcEndpoint::Start ( ) -> asio::awaitable<std::expected<void, RpcError>>

Definition at line 42 of file endpoint.cpp.

42 {
43 Logger()->debug("RpcEndpoint starting");
44 if (is_running_.exchange(true)) {
46 RpcErrorCode::kClientError, "RPC endpoint is already running");
47 }
48
49 pending_requests_.clear();
50
51 // Start the transport
52 auto start_result = co_await transport_->Start();
53 if (!start_result) {
54 co_return start_result;
55 }
56
57 // Start message processing on the endpoint strand
58 StartMessageProcessing();
59
60 // Ensure Start completes before Wait checks is_running_
61 co_return std::expected<void, RpcError>{};
62}

References jsonrpc::error::RpcError::UnexpectedFromCode().

Here is the call graph for this function:

◆ WaitForShutdown()

auto jsonrpc::endpoint::RpcEndpoint::WaitForShutdown ( ) -> asio::awaitable<std::expected<void, RpcError>>

Definition at line 64 of file endpoint.cpp.

65 {
66 // If already shut down, return immediately
67 if (!is_running_) {
68 co_return std::expected<void, RpcError>{};
69 }
70
71 if (message_loop_.valid()) {
72 co_await std::move(message_loop_);
73 }
74
75 co_return std::expected<void, RpcError>{};
76}

The documentation for this class was generated from the following files: