5 #ifndef PACKIO_JSON_RPC_RPC_H
6 #define PACKIO_JSON_RPC_RPC_H
11 #include <boost/json.hpp>
14 #include "../args_specs.h"
15 #include "../internal/config.h"
16 #include "../internal/expected.h"
17 #include "../internal/log.h"
18 #include "../internal/rpc.h"
19 #include "converters.h"
26 template <
typename... Args>
27 constexpr
bool positional_args_v = (!is_arg_v<Args> && ...);
29 template <
typename... Args>
30 constexpr
bool named_args_v =
sizeof...(Args) > 0 && (is_arg_v<Args> && ...);
32 using id_type = boost::json::value;
33 using native_type = boost::json::value;
34 using string_type = boost::json::string;
35 using packio::internal::expected;
36 using packio::internal::unexpected;
57 : parser_{std::make_unique<boost::json::stream_parser>()}
62 expected<request, std::string> get_request()
64 if (parsed_.empty()) {
65 return unexpected{
"no request parsed"};
67 auto object = std::move(parsed_.front());
69 return parse_request(std::move(
object.as_object()));
72 expected<response, std::string> get_response()
74 if (parsed_.empty()) {
75 return unexpected{
"no response parsed"};
77 auto object = std::move(parsed_.front());
79 return parse_response(std::move(
object.as_object()));
84 return buffer_.data();
87 std::size_t buffer_capacity()
const
89 return buffer_.size();
92 void buffer_consumed(std::size_t bytes)
94 std::size_t parsed = 0;
95 while (parsed < bytes) {
96 parsed += parser_->write_some(
97 buffer_.data() + parsed, bytes - parsed);
98 if (parser_->done()) {
99 parsed_.push(parser_->release());
105 void reserve_buffer(std::size_t bytes)
107 buffer_.resize(bytes);
111 static expected<response, std::string> parse_response(boost::json::object&& res)
113 auto id_it = res.find(
"id");
114 auto result_it = res.find(
"result");
115 auto error_it = res.find(
"error");
117 if (id_it == res.end()) {
118 return unexpected{
"missing id field"};
120 if (result_it == res.end() && error_it == res.end()) {
121 return unexpected{
"missing error and result field"};
125 parsed.id = std::move(id_it->value());
126 if (error_it != res.end()) {
127 parsed.error = std::move(error_it->value());
129 if (result_it != res.end()) {
130 parsed.result = std::move(result_it->value());
132 return {std::move(parsed)};
135 static expected<request, std::string> parse_request(boost::json::object&& req)
137 auto id_it = req.find(
"id");
138 auto method_it = req.find(
"method");
139 auto params_it = req.find(
"params");
141 if (method_it == req.end()) {
142 return unexpected{
"missing method field"};
144 if (!method_it->value().is_string()) {
145 return unexpected{
"method field is not a string"};
149 parsed.method = std::string{
150 method_it->value().get_string().data(),
151 method_it->value().get_string().size(),
153 if (params_it == req.end() || params_it->value().is_null()) {
154 parsed.args = boost::json::array{};
156 else if (!params_it->value().is_array() && !params_it->value().is_object()) {
157 return unexpected{
"non-structured arguments are not supported"};
160 parsed.args = std::move(params_it->value());
163 if (id_it == req.end() || id_it->value().is_null()) {
164 parsed.type = call_type::notification;
167 parsed.type = call_type::request;
168 parsed.id = std::move(id_it->value());
170 return {std::move(parsed)};
173 std::vector<char> buffer_;
174 std::queue<boost::json::value> parsed_;
175 std::unique_ptr<boost::json::stream_parser> parser_;
198 static std::string format_id(
const id_type&
id)
200 return boost::json::serialize(
id);
203 template <
typename... Args>
204 static auto serialize_notification(std::string_view method, Args&&... args)
205 -> std::enable_if_t<internal::positional_args_v<Args...>, std::string>
207 auto res = boost::json::serialize(boost::json::object({
210 {
"params", {boost::json::value_from(std::forward<Args>(args))...}},
212 PACKIO_TRACE(
"notification: " + res);
216 template <
typename... Args>
217 static auto serialize_notification(std::string_view method, Args&&... args)
218 -> std::enable_if_t<internal::named_args_v<Args...>, std::string>
220 auto res = boost::json::serialize(boost::json::object({
223 {
"params", {{args.name, boost::json::value_from(args.value)}...}},
225 PACKIO_TRACE(
"notification: " + res);
229 template <
typename... Args>
230 static auto serialize_notification(std::string_view, Args&&...) -> std::enable_if_t<
231 !internal::positional_args_v<Args...> && !internal::named_args_v<Args...>,
235 internal::positional_args_v<Args...> || internal::named_args_v<Args...>,
236 "JSON-RPC does not support mixed named and unnamed arguments");
239 template <
typename... Args>
240 static auto serialize_request(
242 std::string_view method,
244 -> std::enable_if_t<internal::positional_args_v<Args...>, std::string>
246 auto res = boost::json::serialize(boost::json::object({
249 {
"params", {boost::json::value_from(std::forward<Args>(args))...}},
252 PACKIO_TRACE(
"request: " + res);
256 template <
typename... Args>
257 static auto serialize_request(
259 std::string_view method,
261 -> std::enable_if_t<internal::named_args_v<Args...>, std::string>
263 auto res = boost::json::serialize(boost::json::object({
266 {
"params", {{args.name, boost::json::value_from(args.value)}...}},
269 PACKIO_TRACE(
"request: " + res);
273 template <
typename... Args>
274 static auto serialize_request(
const id_type&, std::string_view, Args&&...)
276 !internal::positional_args_v<Args...> && !internal::named_args_v<Args...>,
280 internal::positional_args_v<Args...> || internal::named_args_v<Args...>,
281 "JSON-RPC does not support mixed named and unnamed arguments");
284 static std::string serialize_response(
const id_type&
id)
286 return serialize_response(
id, boost::json::value{});
289 template <
typename T>
290 static std::string serialize_response(
const id_type&
id, T&& value)
292 auto res = boost::json::serialize(boost::json::object({
295 {
"result", boost::json::value_from(std::forward<T>(value))},
297 PACKIO_TRACE(
"response: " + res);
301 template <
typename T>
302 static std::string serialize_error_response(
const id_type&
id, T&& value)
304 auto res = boost::json::serialize(boost::json::object({
309 boost::json::object error = {
311 {
"data", std::forward<T>(value)},
313 if (error[
"data"].is_string()) {
314 error[
"message"] = error[
"data"];
317 error[
"message"] =
"unknown error";
322 PACKIO_TRACE(
"response: " + res);
326 static net::const_buffer buffer(
const std::string& buf)
328 return net::const_buffer(buf.data(), buf.size());
331 template <
typename T,
typename F>
332 static internal::expected<T, std::string> extract_args(
333 boost::json::value&& args,
334 const args_specs<F>& specs)
337 if (args.is_array()) {
338 return convert_positional_args<T>(args.get_array(), specs);
340 else if (args.is_object()) {
341 return convert_named_args<T>(args.get_object(), specs);
344 throw std::runtime_error{
"arguments are not a structured type"};
347 catch (
const std::exception& exc) {
348 return internal::unexpected{
349 std::string{
"cannot convert arguments: "} + exc.what()};
354 template <
typename T,
typename F>
355 static constexpr T convert_positional_args(
356 const boost::json::array& array,
357 const args_specs<F>& specs)
359 return convert_positional_args<T>(
360 array, specs, std::make_index_sequence<args_specs<F>::size()>());
363 template <
typename T,
typename F, std::size_t... Idxs>
364 static constexpr T convert_positional_args(
365 const boost::json::array& array,
366 const args_specs<F>& specs,
367 std::index_sequence<Idxs...>)
369 if (!specs.options().allow_extra_arguments
370 && array.size() > std::tuple_size_v<T>) {
371 throw std::runtime_error{
"too many arguments"};
374 if (Idxs < array.size()) {
376 return boost::json::value_to<std::tuple_element_t<Idxs, T>>(
379 catch (
const boost::json::system_error&) {
380 throw std::runtime_error{
381 "invalid type for argument "
382 + specs.template get<Idxs>().name()};
385 if (
const auto& value = specs.template get<Idxs>().default_value()) {
388 throw std::runtime_error{
389 "no value for argument " + specs.template get<Idxs>().name()};
393 template <
typename T,
typename F>
394 static constexpr T convert_named_args(
395 const boost::json::object& args,
396 const args_specs<F>& specs)
398 return convert_named_args<T>(
399 args, specs, std::make_index_sequence<args_specs<F>::size()>());
402 template <
typename T,
typename F, std::size_t... Idxs>
403 static constexpr T convert_named_args(
404 const boost::json::object& args,
405 const args_specs<F>& specs,
406 std::index_sequence<Idxs...>)
408 if (!specs.options().allow_extra_arguments) {
409 const std::array<
const std::string*,
sizeof...(Idxs)>
410 available_arguments = {&specs.template get<Idxs>().name()...};
411 for (
const auto& item : args) {
412 auto it = std::find_if(
413 available_arguments.begin(),
414 available_arguments.end(),
415 [&](
const std::string* arg) { return *arg == item.key(); });
416 if (it == available_arguments.end()) {
417 throw std::runtime_error{
418 "unexpected argument " + std::string(item.key())};
424 auto it = args.find(specs.template get<Idxs>().name());
425 if (it != args.end()) {
427 return boost::json::value_to<std::tuple_element_t<Idxs, T>>(
430 catch (
const boost::json::system_error&) {
431 throw std::runtime_error{
432 "invalid type for argument "
433 + specs.template get<Idxs>().name()};
436 if (
const auto& value = specs.template get<Idxs>().default_value()) {
439 throw std::runtime_error{
440 "no value for argument " + specs.template get<Idxs>().name()};
The incremental parser for JSON-RPC objects.
Definition: rpc.h:54
The JSON-RPC protocol implementation.
Definition: rpc.h:181
internal::native_type native_type
The native type of the serialization library.
Definition: rpc.h:187
internal::id_type id_type
Type of the call ID.
Definition: rpc.h:184
The packio namespace.
Definition: arg.h:14
The object representing a client request.
Definition: rpc.h:39
The object representing the response to a call.
Definition: rpc.h:47