JSON RPC
JSON-RPC 2.0 library for C++
wwa::json_rpc::dispatcher Class Reference

A class that manages JSON RPC method handlers and processes JSON RPC requests. More...

#include <dispatcher.h>

+ Collaboration diagram for wwa::json_rpc::dispatcher:

Public Member Functions

 dispatcher ()
 Class constructor.
 
 dispatcher (const dispatcher &)=delete
 
 dispatcher (dispatcher &&rhs)=default
 Move constructor.
 
virtual ~dispatcher ()
 Class destructor.
 
template<typename F >
void add (std::string_view method, F &&f)
 Adds a method handler f for the method method.
 
template<typename C , typename F >
void add (std::string_view method, F &&f, C instance)
 Adds a method to the dispatcher with the specified instance and function.
 
template<typename F >
void add_ex (std::string_view method, F &&f)
 Adds a method handler with an extra parameter.
 
template<typename C , typename F >
void add_ex (std::string_view method, F &&f, C instance)
 Adds a method handler with an extra parameter and a class instance.
 
virtual void on_method (const std::string &method, const nlohmann::json &extra)
 Invoked right before the method handler is called.
 
virtual void on_request (const nlohmann::json &extra)
 Invoked when a request is received.
 
virtual void on_request_processed (const std::string &method, int code, const nlohmann::json &extra)
 Invoked after the method handler is called.
 
dispatcheroperator= (const dispatcher &)=delete
 
dispatcheroperator= (dispatcher &&rhs)=default
 Move assignment operator.
 
std::string parse_and_process_request (const std::string &request, const nlohmann::json &extra=nlohmann::json::object())
 Parses and processes a JSON RPC request.
 
std::string process_request (const nlohmann::json &request, const nlohmann::json &extra=nlohmann::json::object())
 Processes a JSON RPC request.
 

Private Types

using handler_t = std::function<nlohmann::json(const nlohmann::json& extra, const nlohmann::json& params)>
 Method handler type.
 

Private Member Functions

void add_internal_method (std::string_view method, handler_t &&handler)
 Adds a method handler for the specified method.
 
template<typename C , typename F , typename Extra , typename Args >
constexpr auto create_closure (C inst, F &&f) const
 Creates a closure for invoking a member function with JSON parameters.
 

Private Attributes

std::unique_ptr< dispatcher_privated_ptr
 Pointer to the implementation (Pimpl idiom).
 

Friends

class dispatcher_private
 

Detailed Description

A class that manages JSON RPC method handlers and processes JSON RPC requests.

The dispatcher class allows adding method handlers for JSON RPC methods and processes JSON RPC requests. It supports adding plain functions, static class methods, lambda functions, and member functions as handlers. The handlers can accept and return values that can be converted to and from nlohmann::json values.

Note
The dispatcher class is non-copyable but movable.

The dispatcher class provides the following functionalities:

  • Adding method handlers for JSON RPC methods.
  • Parsing and processing JSON RPC requests.
  • Invoking method handlers with the appropriate arguments.
  • Handling exceptions thrown by method handlers and returning appropriate JSON RPC error responses.

The dispatcher class also provides virtual methods that can be overridden in derived classes to customize the behavior when a request is received, before a method handler is called, and after a method handler is called.

Example Usage:
d.add("subtract", [](const nlohmann::json& params) {
int minuend = params["minuend"];
int subtrahend = params["subtrahend"];
return minuend - subtrahend;
});
std::string request = R"({"jsonrpc": "2.0", "method": "subtract", "params": {"minuend": 42, "subtrahend": 23}, "id": 1})";
std::string response = d.parse_and_process_request(request);
A class that manages JSON RPC method handlers and processes JSON RPC requests.
Definition dispatcher.h:77
std::string parse_and_process_request(const std::string &request, const nlohmann::json &extra=nlohmann::json::object())
Parses and processes a JSON RPC request.
void add(std::string_view method, F &&f)
Adds a method handler f for the method method.
Definition dispatcher.h:192
Adding Method Handlers:
Method handlers can be added using the add method. The handler function can accept any number of arguments as long as they can be converted from a nlohmann::json value. The handler function can also return any type that can be converted to a nlohmann::json value.
Handling Exceptions:
If a handler function throws an exception derived from std::exception, the exception will be caught and an appropriate JSON RPC error response will be returned.

Definition at line 77 of file dispatcher.h.

Member Typedef Documentation

◆ handler_t

using wwa::json_rpc::dispatcher::handler_t = std::function<nlohmann::json(const nlohmann::json& extra, const nlohmann::json& params)>
private

Method handler type.

This type alias defines a method handler function that takes two JSON parameters:

  • extra: An additional JSON object that can be used to pass extra information to the handler.
  • params: A JSON object containing the parameters for the method.

The handler function returns a JSON object as a result.

This type alias is used to define the signature of functions that handle method calls in the dispatcher.

Definition at line 92 of file dispatcher.h.

Constructor & Destructor Documentation

◆ dispatcher() [1/3]

wwa::json_rpc::dispatcher::dispatcher ( )

Class constructor.

Definition at line 12 of file dispatcher.cpp.

12: d_ptr(std::make_unique<dispatcher_private>(this)) {}
std::unique_ptr< dispatcher_private > d_ptr
Pointer to the implementation (Pimpl idiom).
Definition dispatcher.h:413

◆ ~dispatcher()

wwa::json_rpc::dispatcher::~dispatcher ( )
virtualdefault

Class destructor.

◆ dispatcher() [2/3]

wwa::json_rpc::dispatcher::dispatcher ( const dispatcher & )
delete

◆ dispatcher() [3/3]

wwa::json_rpc::dispatcher::dispatcher ( dispatcher && rhs)
default

Move constructor.

Parameters
rhsRight-hand side object.

Member Function Documentation

◆ add() [1/2]

template<typename F >
void wwa::json_rpc::dispatcher::add ( std::string_view method,
F && f )
inline

Adds a method handler f for the method method.

Template Parameters
FType of the handler function (f).
Parameters
methodThe name of the method to add the handler for.
fThe handler function.

This overload is used to add a plain function, a static class method, or a lambda function as a handler.

Internally, the handler is a function that accepts a nlohmann::json as its argument and returns a nlohmann::json value. However, the handler function can accept any number of arguments, as long as they can be converted from a nlohmann::json value. The same is true for the return value: it can be any type that can be converted to a nlohmann::json value (or void). Of course, the handler can accept and/or return nlohmann::json values directly.

Accepting Arguments in a Handler:
The Specification defines two types of parameters: named and positional. Since there is no easy way to match named parameters to the handler function arguments, the named parameters are treated as a single structured value.
For example, if the parameters are passed like this: "params": {"subtrahend": 23, "minuend": 42}, the handler function must accept a single argument of a struct type:
struct subtract_params {
int minuend;
int subtrahend;
};
int subtract(const subtract_params& params)
{
return params.minuend - params.subtrahend;
}
Because there is no automatic conversion from a JSON object to subtract_params, you must define the conversion function yourself. The easiest way is to use a macro:
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(subtract_params, minuend, subtrahend);
In the case of positional parameters, the handler function must accept the same number of arguments as the number of parameters passed in the request.
If the handler needs to accept a variable number of arguments, it must accept a single nlohmann::json argument and parse it as needed, like this:
dispatcher.add("sum", [](const nlohmann::json& params) {
std::vector<int> v;
params.get_to(v);
return std::accumulate(v.begin(), v.end(), 0);
});
Note
If the handler accepts a single nlohmann::json argument, it will accept any parameters. For example:
void handler(const nlohmann::json& params)
{
if (params.is_null()) {
// No parameters
}
else if (params.is_array()) {
// Array of positional parameters
}
else if (params.is_object()) {
// Named parameters
}
}
Returning Values from a Handler:
  1. The handler can return any value as long as it can be converted to a nlohmann::json value. If there is no default conversion available, the handler can either return a nlohmann::json value directly, or use a custom to_json() function.
  2. If the handler function returns void, it will be automatically converted to null in the JSON response.
Exception Handling:
If the hander function throws an exception (derived from std::exception), the exception will be caught, and the error will be returned in the JSON response:
  1. json_rpc::exception will be converted to a JSON RPC error object using json_rpc::exception::to_json();
  2. other exceptions derived from std::exception will be converted to a JSON RPC error object with code -32603 (exception::INTERNAL_ERROR) and the exception message (what()) as the error message.

Definition at line 192 of file dispatcher.h.

193 {
194 this->add(method, std::forward<F>(f), nullptr);
195 }

◆ add() [2/2]

template<typename C , typename F >
void wwa::json_rpc::dispatcher::add ( std::string_view method,
F && f,
C instance )
inline

Adds a method to the dispatcher with the specified instance and function.

Template Parameters
CThe type of the class instance.
FThe type of the function to be added.
Parameters
methodThe name of the method to be added.
fThe function to be added, which will be bound to the instance.
instanceThe instance of the class to which the function belongs. This is an overloaded member function, provided for convenience. It differs from the above function only in what argument(s) it accepts.

This template method allows adding a method to the dispatcher by binding a member function of a class instance. It uses function traits to deduce the argument types and creates a closure that is then added to the internal method map.

Definition at line 210 of file dispatcher.h.

211 {
212 using traits = details::function_traits<std::decay_t<F>>;
213 using ArgsTuple = typename traits::args_tuple;
214
215 auto&& closure = this->create_closure<C, F, void, ArgsTuple>(instance, std::forward<F>(f));
216 this->add_internal_method(method, std::forward<decltype(closure)>(closure));
217 }
constexpr auto create_closure(C inst, F &&f) const
Creates a closure for invoking a member function with JSON parameters.
Definition dispatcher.h:455
void add_internal_method(std::string_view method, handler_t &&handler)
Adds a method handler for the specified method.

◆ add_ex() [1/2]

template<typename F >
void wwa::json_rpc::dispatcher::add_ex ( std::string_view method,
F && f )
inline

Adds a method handler with an extra parameter.

Template Parameters
FThe type of the handler function.
Parameters
methodThe name of the method to add the handler for.
fThe handler function.

This method allows adding a handler function with an additional extra parameter. The extra parameter can be used to pass additional information to the handler function. The handler function can accept any number of arguments as long as they can be converted from a nlohmann::json value. The same is true for the return value: it can be any type that can be converted to a nlohmann::json value (or void).

This overload is used to add a plain function, a static class method, or a lambda function as a handler.

Sample Usage:
struct extra_params {
std::string ip;
};
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(extra_params, ip);
dispatcher.add_ex("sum", [](const extra_params& extra, const nlohmann::json& params) {
std::cout << "Invoking sum() method for " << extra.ip << "\n";
std::vector<int> v;
params.get_to(v);
return std::accumulate(v.begin(), v.end(), 0);
});
void add_ex(std::string_view method, F &&f)
Adds a method handler with an extra parameter.
Definition dispatcher.h:254

See process_request() for more details on the extra parameter.

See also
add()

Definition at line 254 of file dispatcher.h.

255 {
256 this->add_ex(method, std::forward<F>(f), nullptr);
257 }

◆ add_ex() [2/2]

template<typename C , typename F >
void wwa::json_rpc::dispatcher::add_ex ( std::string_view method,
F && f,
C instance )
inline

Adds a method handler with an extra parameter and a class instance.

Template Parameters
CThe type of the class instance.
FThe type of the handler function.
Parameters
methodThe name of the method to add the handler for.
fThe handler function.
instanceThe instance of the class to which the function belongs.

This is an overloaded member function, provided for convenience. It differs from the above function only in what argument(s) it accepts.

This method allows adding a class method handler with an additional extra parameter. The extra parameter can be used to pass additional information to the handler function. The handler function can accept any number of arguments as long as they can be converted from a nlohmann::json value. The same is true for the return value: it can be any type that can be converted to a nlohmann::json value (or void).

See also
add()

Definition at line 277 of file dispatcher.h.

278 {
279 using traits = details::function_traits<std::decay_t<F>>;
280 using ArgsTuple = typename traits::args_tuple;
281
282 static_assert(
283 std::tuple_size<std::decay_t<ArgsTuple>>::value > 0,
284 "Handler function must accept the `extra` argument. Use `add()` for handlers without an extra parameter."
285 );
286
287 using ExtraType = std::decay_t<std::tuple_element_t<0, ArgsTuple>>;
288 auto&& closure = this->create_closure<C, F, ExtraType, ArgsTuple>(instance, std::forward<F>(f));
289 this->add_internal_method(method, std::forward<decltype(closure)>(closure));
290 }

◆ add_internal_method()

void wwa::json_rpc::dispatcher::add_internal_method ( std::string_view method,
handler_t && handler )
private

Adds a method handler for the specified method.

Parameters
methodThe name of the method.
handlerThe handler function.

This method registers a handler function for a given method name. The handler function will be invoked when a request for the specified method is received.

Definition at line 16 of file dispatcher.cpp.

17{
18 this->d_ptr->add_handler(std::string(method), std::move(handler));
19}

◆ create_closure()

template<typename C , typename F , typename Extra , typename Args >
auto wwa::json_rpc::dispatcher::create_closure ( C inst,
F && f ) const
inlineconstexprprivate

Creates a closure for invoking a member function with JSON parameters.

Template Parameters
CThe type of the class instance (can be a pointer or null pointer).
FThe type of the member function (if C is not std::nullptr_t) or the function.
ExtraThe type of the extra parameter that can be passed to the member function (can be void, nlohmann::json, or convertible from nlohmann::json).
ArgsThe type of the arguments tuple.
Parameters
instThe instance of the class (can be a pointer or null pointer).
fThe member function to be invoked.
Returns
A lambda function that takes a JSON object as a parameter and invokes the member function with the appropriate arguments.

This method creates a closure (lambda function) that can be used to invoke a member function with arguments extracted from a JSON object.

The closure performs the following steps:

  1. Checks if the JSON object is an array.
  2. If the JSON object is an array and the member function takes a single argument of type nlohmann::json, it directly passes the JSON object to the member function.
  3. If the JSON object is an array and the number of elements matches the number of arguments expected by the member function, it extracts the arguments from the JSON array and invokes the member function.
  4. If the JSON object is not an array or the number of elements does not match the number of arguments, it throws a json_rpc::exception with the exception::INVALID_PARAMS code.

The invoke_function method is used to invoke the member function with the extracted arguments.

The std::apply function is used to unpack the tuple and pass the arguments to the member function.

Compile-time checks ensure that the code is type-safe and that certain conditions are met before the code is compiled. This helps catch potential errors early in the development process and improves the overall robustness of the code.

Definition at line 455 of file dispatcher.h.

456 {
457 static_assert((std::is_pointer_v<C> && std::is_class_v<std::remove_pointer_t<C>>) || std::is_null_pointer_v<C>);
458 return [func = std::forward<F>(f), inst](const nlohmann::json& extra, const nlohmann::json& params) {
459 assert(params.is_array());
460 constexpr auto args_size = std::tuple_size<std::decay_t<Args>>::value;
461 constexpr auto arg_pos = std::is_void_v<Extra> ? 0 : 1;
462
463 if constexpr (args_size == arg_pos + 1) {
464 if constexpr (std::is_same_v<std::decay_t<std::tuple_element_t<arg_pos, Args>>, nlohmann::json>) {
465 auto&& tuple_args = std::tuple_cat(
466 details::make_inst_tuple(inst), details::make_extra_tuple<Extra>(extra), std::make_tuple(params)
467 );
468
469 return details::invoke_function(func, std::forward<decltype(tuple_args)>(tuple_args));
470 }
471 }
472
473 if (params.size() + arg_pos == args_size) {
474 constexpr auto offset = std::is_void_v<Extra> ? 0U : 1U;
475 auto&& tuple_args = std::tuple_cat(
478 params, details::offset_sequence_t<offset, std::make_index_sequence<args_size - offset>>{}
479 )
480 );
481
482 return details::invoke_function(func, std::forward<decltype(tuple_args)>(tuple_args));
483 }
484
486 };
487 }
static constexpr int INVALID_PARAMS
Invalid method parameter(s).
Definition exception.h:108
static constexpr std::string_view err_invalid_params_passed_to_method
Error message for when the parameters passed to the method are not correct.
Definition exception.h:37
typename offset_sequence< N, Seq >::type offset_sequence_t
Alias template for creating an offset sequence.
Definition details.h:217
constexpr auto make_extra_tuple(const nlohmann::json &extra)
Creates a tuple from the provided JSON object based on the type of Extra.
Definition details.h:263
constexpr auto convert_args(const nlohmann::json &params, std::index_sequence< Indices... >)
Converts JSON parameters to a tuple of arguments based on the specified types.
Definition details.h:346
constexpr auto make_inst_tuple(C inst)
Creates a tuple containing the instance if it is a class pointer, or an empty tuple if it is a null p...
Definition details.h:239
nlohmann::json invoke_function(F &&f, Tuple &&tuple)
Invokes a function with the provided arguments handling void return type.
Definition details.h:299

◆ on_method()

void wwa::json_rpc::dispatcher::on_method ( const std::string & method,
const nlohmann::json & extra )
virtual

Invoked right before the method handler is called.

This method does nothing by default. It is intended to be overridden in a derived class. For example, it can start a timer to measure the method execution time.

Parameters
extraAdditional information that was passed to process_request(). If extra is a JSON object, it will contain all extra fields from the JSON RPC request (in extra['extra'], see process_request()).
methodThe name of the method to be called.

Definition at line 51 of file dispatcher.cpp.

52{
53 // Do nothing
54}

◆ on_request()

void wwa::json_rpc::dispatcher::on_request ( const nlohmann::json & extra)
virtual

Invoked when a request is received.

This method does nothing by default. It is intended to be overridden in a derived class. For example, it can be used to log requests or increment a counter.

Parameters
extraAdditional information that was passed to process_request(). Since on_request() is called before the request is parsed, the extra` parameter will not contain fields from the request itself (i.e., `extra['extra']` will not be set).
Note
In the case of a valid batch request, this method is invoked for every request in the batch but not for the batch itself. However, if the batch request is invalid (e.g., is empty), this method is invoked once with an empty method name.

Definition at line 46 of file dispatcher.cpp.

47{
48 // Do nothing
49}

◆ on_request_processed()

void wwa::json_rpc::dispatcher::on_request_processed ( const std::string & method,
int code,
const nlohmann::json & extra )
virtual

Invoked after the method handler is called.

This method does nothing by default. It is intended to be overridden in a derived class. For example, it can be used to stop the timer started in on_method().

Parameters
methodThe name of the called method. It can be empty for an invalid batch request.
codeThe result code: 0 if the method was processed successfully, or an error code if an exception was thrown (e.g., exception::INTERNAL_ERROR) or the request could not be processed (e.g., exception::INVALID_PARAMS).
extraAdditional information that was passed to process_request(). If the request had already been successfully parsed before on_request_processed() was called, the extra parameter will contain all extra fields from the JSON RPC request (in extra['extra'], see process_request()).

Definition at line 56 of file dispatcher.cpp.

57{
58 // Do nothing
59}

◆ operator=() [1/2]

dispatcher & wwa::json_rpc::dispatcher::operator= ( const dispatcher & )
delete

◆ operator=() [2/2]

dispatcher & wwa::json_rpc::dispatcher::operator= ( dispatcher && rhs)
default

Move assignment operator.

Parameters
rhsRight-hand side object.
Returns
Reference to this object.

◆ parse_and_process_request()

std::string wwa::json_rpc::dispatcher::parse_and_process_request ( const std::string & request,
const nlohmann::json & extra = nlohmann::json::object() )

Parses and processes a JSON RPC request.

Parameters
requestThe JSON RPC request as a string.
extraOptional data that can be passed to the handler function (only for handlers added with add_ex()).
Returns
The response serialized into a JSON string.
Return values
""If the request is a Notification, the method returns an empty string.

This method performs the following steps:

  • Parses the JSON RPC request.
  • Passes the parsed JSON to the process_request() method.
  • If the request is invalid, returns an appropriate error response.

If the request cannot be parsed, the method returns a JSON RPC error response with code -32700 (exception::PARSE_ERROR).

Exceptions derived from std::exception thrown by the handler function are caught and returned as JSON RPC error responses.

See also
process_request()

Definition at line 21 of file dispatcher.cpp.

22{
23 nlohmann::json req;
24 try {
25 req = nlohmann::json::parse(request);
26 }
27 catch (const nlohmann::json::exception& e) {
28 this->on_request(extra);
30 exception(exception::PARSE_ERROR, e.what()), nlohmann::json(nullptr)
31 );
32
34 return json.dump();
35 }
36
37 return this->process_request(req, extra);
38}
static nlohmann::json generate_error_response(const exception &e, const nlohmann::json &id)
Generates an error response.
virtual void on_request(const nlohmann::json &extra)
Invoked when a request is received.
std::string process_request(const nlohmann::json &request, const nlohmann::json &extra=nlohmann::json::object())
Processes a JSON RPC request.
virtual void on_request_processed(const std::string &method, int code, const nlohmann::json &extra)
Invoked after the method handler is called.
static constexpr int PARSE_ERROR
Invalid JSON was received by the server.
Definition exception.h:93

◆ process_request()

std::string wwa::json_rpc::dispatcher::process_request ( const nlohmann::json & request,
const nlohmann::json & extra = nlohmann::json::object() )

Processes a JSON RPC request.

Parameters
requestThe JSON RPC request as a nlohmann::json object.
extraOptional data that can be passed to the handler function (only for handlers added with add_ex()).
Returns
The response serialized into a JSON string.
Return values
""If the request is a Notification, the method returns an empty string.

This method performs the following steps:

  • Parses the JSON RPC request.
  • Passes the parsed JSON to the appropriate handler function.
  • If the request is invalid, returns an appropriate error response.

If the request cannot be parsed, the method returns a JSON RPC error response with code -32700 (exception::PARSE_ERROR).

Exceptions derived from std::exception thrown by the handler function are caught and returned as JSON RPC error responses:

If extra is an object, all extra fields from the JSON RPC request will be passed in the extra property of extra. For example, given this request:

{
"jsonrpc": "2.0",
"method": "subtract",
"params": {"minuend": 42, "subtrahend": 23},
"id": 1,
"auth": "secret",
"user": "admin"
}

and extra set to {"ip": "1.2.3.4"}, the extra parameter passed to the handler will be:

{
"ip": "1.2.3.4",
"extra": {
"auth": "secret",
"user": "admin"
}
}

Definition at line 40 of file dispatcher.cpp.

41{
42 const auto json = this->d_ptr->process_request(request, extra);
43 return json.is_discarded() ? std::string{} : json.dump();
44}

Friends And Related Symbol Documentation

◆ dispatcher_private

friend class dispatcher_private
friend

Definition at line 79 of file dispatcher.h.

Member Data Documentation

◆ d_ptr

std::unique_ptr<dispatcher_private> wwa::json_rpc::dispatcher::d_ptr
private

Pointer to the implementation (Pimpl idiom).

This unique pointer holds the private implementation details of the dispatcher class. It is used to hide the implementation details and reduce compilation dependencies.

Definition at line 413 of file dispatcher.h.


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