5 #ifndef PACKIO_NL_JSON_RPC_RPC_H
6 #define PACKIO_NL_JSON_RPC_RPC_H
11 #include <nlohmann/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 "incremental_buffers.h"
22 namespace nl_json_rpc {
25 template <
typename... Args>
26 constexpr
bool positional_args_v = (!is_arg_v<Args> && ...);
28 template <
typename... Args>
29 constexpr
bool named_args_v =
sizeof...(Args) > 0 && (is_arg_v<Args> && ...);
31 using id_type = nlohmann::json;
32 using native_type = nlohmann::json;
33 using packio::internal::expected;
34 using packio::internal::unexpected;
54 expected<request, std::string> get_request()
58 return unexpected{
"no request parsed"};
60 auto object = std::move(*parsed_);
62 return parse_request(std::move(
object));
65 expected<response, std::string> get_response()
69 return unexpected{
"no response parsed"};
71 auto object = std::move(*parsed_);
73 return parse_response(std::move(
object));
78 return incremental_buffers_.in_place_buffer();
81 std::size_t buffer_capacity()
const
83 return incremental_buffers_.in_place_buffer_capacity();
86 void buffer_consumed(std::size_t bytes)
88 incremental_buffers_.in_place_buffer_consumed(bytes);
91 void reserve_buffer(std::size_t bytes)
93 incremental_buffers_.reserve_in_place_buffer(bytes);
97 void try_parse_object()
102 auto buffer = incremental_buffers_.get_parsed_buffer();
104 parsed_ = nlohmann::json::parse(*buffer);
108 static expected<response, std::string> parse_response(nlohmann::json&& res)
110 auto id_it = res.find(
"id");
111 auto result_it = res.find(
"result");
112 auto error_it = res.find(
"error");
114 if (id_it == end(res)) {
115 return unexpected{
"missing id field"};
117 if (result_it == end(res) && error_it == end(res)) {
118 return unexpected{
"missing error and result field"};
122 parsed.id = std::move(*id_it);
123 if (error_it != end(res)) {
124 parsed.error = std::move(*error_it);
126 if (result_it != end(res)) {
127 parsed.result = std::move(*result_it);
129 return {std::move(parsed)};
132 static expected<request, std::string> parse_request(nlohmann::json&& req)
134 auto id_it = req.find(
"id");
135 auto method_it = req.find(
"method");
136 auto params_it = req.find(
"params");
138 if (method_it == end(req)) {
139 return unexpected{
"missing method field"};
141 if (!method_it->is_string()) {
142 return unexpected{
"method field is not a string"};
146 parsed.method = method_it->get<std::string>();
147 if (params_it == end(req) || params_it->is_null()) {
148 parsed.args = nlohmann::json::array();
150 else if (!params_it->is_array() && !params_it->is_object()) {
151 return unexpected{
"non-structured arguments are not supported"};
154 parsed.args = std::move(*params_it);
157 if (id_it == end(req) || id_it->is_null()) {
158 parsed.type = call_type::notification;
161 parsed.type = call_type::request;
162 parsed.id = std::move(*id_it);
164 return {std::move(parsed)};
167 std::optional<nlohmann::json> parsed_;
168 incremental_buffers incremental_buffers_;
191 static std::string format_id(
const id_type&
id)
196 template <
typename... Args>
197 static auto serialize_notification(std::string_view method, Args&&... args)
198 -> std::enable_if_t<internal::positional_args_v<Args...>, std::string>
200 return nlohmann::json({
204 nlohmann::json::array({nlohmann::json(
205 std::forward<Args>(args))...})},
210 template <
typename... Args>
211 static auto serialize_notification(std::string_view method, Args&&... args)
212 -> std::enable_if_t<internal::named_args_v<Args...>, std::string>
214 return nlohmann::json({
217 {
"params", {{args.name, args.value}...}},
222 template <
typename... Args>
223 static auto serialize_notification(std::string_view, Args&&...) -> std::enable_if_t<
224 !internal::positional_args_v<Args...> && !internal::named_args_v<Args...>,
228 internal::positional_args_v<Args...> || internal::named_args_v<Args...>,
229 "JSON-RPC does not support mixed named and unnamed arguments");
232 template <
typename... Args>
233 static auto serialize_request(
235 std::string_view method,
237 -> std::enable_if_t<internal::positional_args_v<Args...>, std::string>
239 return nlohmann::json({
243 nlohmann::json::array({nlohmann::json(
244 std::forward<Args>(args))...})},
250 template <
typename... Args>
251 static auto serialize_request(
253 std::string_view method,
255 -> std::enable_if_t<internal::named_args_v<Args...>, std::string>
257 return nlohmann::json({
260 {
"params", {{args.name, args.value}...}},
266 template <
typename... Args>
267 static auto serialize_request(
const id_type&, std::string_view, Args&&...)
269 !internal::positional_args_v<Args...> && !internal::named_args_v<Args...>,
273 internal::positional_args_v<Args...> || internal::named_args_v<Args...>,
274 "JSON-RPC does not support mixed named and unnamed arguments");
277 static std::string serialize_response(
const id_type&
id)
279 return serialize_response(
id, nlohmann::json{});
282 template <
typename T>
283 static std::string serialize_response(
const id_type&
id, T&& value)
285 return nlohmann::json{
288 {
"result", std::forward<T>(value)},
293 template <
typename T>
294 static std::string serialize_error_response(
const id_type&
id, T&& value)
296 return nlohmann::json{
301 nlohmann::json error = {
303 {
"data", std::forward<T>(value)},
305 if (error[
"data"].is_string()) {
306 error[
"message"] = error[
"data"];
309 error[
"message"] =
"unknown error";
317 static net::const_buffer buffer(
const std::string& buf)
319 return net::const_buffer(buf.data(), buf.size());
322 template <
typename T,
typename F>
323 static internal::expected<T, std::string> extract_args(
324 const nlohmann::json& args,
325 const args_specs<F>& specs)
328 if (args.is_array()) {
329 return convert_positional_args<T>(args, specs);
331 else if (args.is_object()) {
332 return convert_named_args<T>(args, specs);
335 throw std::runtime_error{
"arguments are not a structured type"};
338 catch (
const std::exception& exc) {
339 return internal::unexpected{
340 std::string{
"cannot convert arguments: "} + exc.what()};
345 template <
typename T,
typename F>
346 static constexpr T convert_positional_args(
347 const nlohmann::json& array,
348 const args_specs<F>& specs)
350 return convert_positional_args<T>(
351 array, specs, std::make_index_sequence<args_specs<F>::size()>());
354 template <
typename T,
typename F, std::size_t... Idxs>
355 static constexpr T convert_positional_args(
356 const nlohmann::json& array,
357 const args_specs<F>& specs,
358 std::index_sequence<Idxs...>)
360 if (!specs.options().allow_extra_arguments
361 && array.size() > std::tuple_size_v<T>) {
362 throw std::runtime_error{
"too many arguments"};
365 if (Idxs < array.size()) {
367 return array.at(Idxs).get<std::tuple_element_t<Idxs, T>>();
369 catch (const ::nlohmann::json::type_error&) {
370 throw std::runtime_error{
371 "invalid type for argument "
372 + specs.template get<Idxs>().name()};
375 if (
const auto& value = specs.template get<Idxs>().default_value()) {
378 throw std::runtime_error{
379 "no value for argument " + specs.template get<Idxs>().name()};
383 template <
typename T,
typename F>
384 static T convert_named_args(
const nlohmann::json& args,
const args_specs<F>& specs)
386 return convert_named_args<T>(
387 args, specs, std::make_index_sequence<args_specs<F>::size()>{});
390 template <
typename T,
typename F, std::size_t... Idxs>
391 static T convert_named_args(
392 const nlohmann::json& args,
393 const args_specs<F>& specs,
394 std::index_sequence<Idxs...>)
396 if (!specs.options().allow_extra_arguments) {
397 const std::array<
const std::string*,
sizeof...(Idxs)>
398 available_arguments = {&specs.template get<Idxs>().name()...};
399 for (
auto it = args.begin(); it != args.end(); ++it) {
400 auto arg_it = std::find_if(
401 available_arguments.begin(),
402 available_arguments.end(),
403 [&](
const std::string* arg) { return *arg == it.key(); });
404 if (arg_it == available_arguments.end()) {
405 throw std::runtime_error{
"unexpected argument " + it.key()};
411 auto it = args.find(specs.template get<Idxs>().name());
412 if (it != args.end()) {
414 return it->template get<std::tuple_element_t<Idxs, T>>();
416 catch (const ::nlohmann::json::type_error&) {
417 throw std::runtime_error{
418 "invalid type for argument "
419 + specs.template get<Idxs>().name()};
422 if (
const auto& value = specs.template get<Idxs>().default_value()) {
425 throw std::runtime_error{
426 "no value for argument " + specs.template get<Idxs>().name()};
The incremental parser for JSON-RPC objects.
Definition: rpc.h:52
The JSON-RPC protocol implementation.
Definition: rpc.h:174
internal::native_type native_type
The native type of the serialization library.
Definition: rpc.h:180
internal::id_type id_type
Type of the call ID.
Definition: rpc.h:177
The packio namespace.
Definition: arg.h:14
The object representing a client request.
Definition: rpc.h:37
The object representing the response to a call.
Definition: rpc.h:45