packio
rpc.h
1 // This Source Code Form is subject to the terms of the Mozilla Public
2 // License, v. 2.0. If a copy of the MPL was not distributed with this
3 // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4 
5 #ifndef PACKIO_JSON_RPC_RPC_H
6 #define PACKIO_JSON_RPC_RPC_H
7 
8 #include <array>
9 #include <queue>
10 
11 #include <boost/json.hpp>
12 
13 #include "../arg.h"
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"
20 #include "hash.h"
21 
22 namespace packio {
23 namespace json_rpc {
24 namespace internal {
25 
26 template <typename... Args>
27 constexpr bool positional_args_v = (!is_arg_v<Args> && ...);
28 
29 template <typename... Args>
30 constexpr bool named_args_v = sizeof...(Args) > 0 && (is_arg_v<Args> && ...);
31 
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;
37 
39 struct request {
40  call_type type;
41  internal::id_type id;
42  std::string method;
43  native_type args;
44 };
45 
47 struct response {
48  id_type id;
49  native_type result;
50  native_type error;
51 };
52 
55 public:
57  : parser_{std::make_unique<boost::json::stream_parser>()}
58  { //
59  parser_->reset();
60  }
61 
62  expected<request, std::string> get_request()
63  {
64  if (parsed_.empty()) {
65  return unexpected{"no request parsed"};
66  }
67  auto object = std::move(parsed_.front());
68  parsed_.pop();
69  return parse_request(std::move(object.as_object()));
70  }
71 
72  expected<response, std::string> get_response()
73  {
74  if (parsed_.empty()) {
75  return unexpected{"no response parsed"};
76  }
77  auto object = std::move(parsed_.front());
78  parsed_.pop();
79  return parse_response(std::move(object.as_object()));
80  }
81 
82  char* buffer()
83  { //
84  return buffer_.data();
85  }
86 
87  std::size_t buffer_capacity() const
88  { //
89  return buffer_.size();
90  }
91 
92  void buffer_consumed(std::size_t bytes)
93  { //
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());
100  parser_->reset();
101  }
102  }
103  }
104 
105  void reserve_buffer(std::size_t bytes)
106  { //
107  buffer_.resize(bytes);
108  }
109 
110 private:
111  static expected<response, std::string> parse_response(boost::json::object&& res)
112  {
113  auto id_it = res.find("id");
114  auto result_it = res.find("result");
115  auto error_it = res.find("error");
116 
117  if (id_it == res.end()) {
118  return unexpected{"missing id field"};
119  }
120  if (result_it == res.end() && error_it == res.end()) {
121  return unexpected{"missing error and result field"};
122  }
123 
124  response parsed;
125  parsed.id = std::move(id_it->value());
126  if (error_it != res.end()) {
127  parsed.error = std::move(error_it->value());
128  }
129  if (result_it != res.end()) {
130  parsed.result = std::move(result_it->value());
131  }
132  return {std::move(parsed)};
133  }
134 
135  static expected<request, std::string> parse_request(boost::json::object&& req)
136  {
137  auto id_it = req.find("id");
138  auto method_it = req.find("method");
139  auto params_it = req.find("params");
140 
141  if (method_it == req.end()) {
142  return unexpected{"missing method field"};
143  }
144  if (!method_it->value().is_string()) {
145  return unexpected{"method field is not a string"};
146  }
147 
148  request parsed;
149  parsed.method = std::string{
150  method_it->value().get_string().data(),
151  method_it->value().get_string().size(),
152  };
153  if (params_it == req.end() || params_it->value().is_null()) {
154  parsed.args = boost::json::array{};
155  }
156  else if (!params_it->value().is_array() && !params_it->value().is_object()) {
157  return unexpected{"non-structured arguments are not supported"};
158  }
159  else {
160  parsed.args = std::move(params_it->value());
161  }
162 
163  if (id_it == req.end() || id_it->value().is_null()) {
164  parsed.type = call_type::notification;
165  }
166  else {
167  parsed.type = call_type::request;
168  parsed.id = std::move(id_it->value());
169  }
170  return {std::move(parsed)};
171  }
172 
173  std::vector<char> buffer_;
174  std::queue<boost::json::value> parsed_;
175  std::unique_ptr<boost::json::stream_parser> parser_;
176 };
177 
178 } // internal
179 
181 class rpc {
182 public:
184  using id_type = internal::id_type;
185 
187  using native_type = internal::native_type;
188 
191 
194 
197 
198  static std::string format_id(const id_type& id)
199  { //
200  return boost::json::serialize(id);
201  }
202 
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>
206  {
207  auto res = boost::json::serialize(boost::json::object({
208  {"jsonrpc", "2.0"},
209  {"method", method},
210  {"params", {boost::json::value_from(std::forward<Args>(args))...}},
211  }));
212  PACKIO_TRACE("notification: " + res);
213  return res;
214  }
215 
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>
219  {
220  auto res = boost::json::serialize(boost::json::object({
221  {"jsonrpc", "2.0"},
222  {"method", method},
223  {"params", {{args.name, boost::json::value_from(args.value)}...}},
224  }));
225  PACKIO_TRACE("notification: " + res);
226  return res;
227  }
228 
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...>,
232  std::string>
233  {
234  static_assert(
235  internal::positional_args_v<Args...> || internal::named_args_v<Args...>,
236  "JSON-RPC does not support mixed named and unnamed arguments");
237  }
238 
239  template <typename... Args>
240  static auto serialize_request(
241  const id_type& id,
242  std::string_view method,
243  Args&&... args)
244  -> std::enable_if_t<internal::positional_args_v<Args...>, std::string>
245  {
246  auto res = boost::json::serialize(boost::json::object({
247  {"jsonrpc", "2.0"},
248  {"method", method},
249  {"params", {boost::json::value_from(std::forward<Args>(args))...}},
250  {"id", id},
251  }));
252  PACKIO_TRACE("request: " + res);
253  return res;
254  }
255 
256  template <typename... Args>
257  static auto serialize_request(
258  const id_type& id,
259  std::string_view method,
260  Args&&... args)
261  -> std::enable_if_t<internal::named_args_v<Args...>, std::string>
262  {
263  auto res = boost::json::serialize(boost::json::object({
264  {"jsonrpc", "2.0"},
265  {"method", method},
266  {"params", {{args.name, boost::json::value_from(args.value)}...}},
267  {"id", id},
268  }));
269  PACKIO_TRACE("request: " + res);
270  return res;
271  }
272 
273  template <typename... Args>
274  static auto serialize_request(const id_type&, std::string_view, Args&&...)
275  -> std::enable_if_t<
276  !internal::positional_args_v<Args...> && !internal::named_args_v<Args...>,
277  std::string>
278  {
279  static_assert(
280  internal::positional_args_v<Args...> || internal::named_args_v<Args...>,
281  "JSON-RPC does not support mixed named and unnamed arguments");
282  }
283 
284  static std::string serialize_response(const id_type& id)
285  {
286  return serialize_response(id, boost::json::value{});
287  }
288 
289  template <typename T>
290  static std::string serialize_response(const id_type& id, T&& value)
291  {
292  auto res = boost::json::serialize(boost::json::object({
293  {"jsonrpc", "2.0"},
294  {"id", id},
295  {"result", boost::json::value_from(std::forward<T>(value))},
296  }));
297  PACKIO_TRACE("response: " + res);
298  return res;
299  }
300 
301  template <typename T>
302  static std::string serialize_error_response(const id_type& id, T&& value)
303  {
304  auto res = boost::json::serialize(boost::json::object({
305  {"jsonrpc", "2.0"},
306  {"id", id},
307  {"error",
308  [&]() {
309  boost::json::object error = {
310  {"code", -32000}, // -32000 is an implementation-defined error
311  {"data", std::forward<T>(value)},
312  };
313  if (error["data"].is_string()) {
314  error["message"] = error["data"];
315  }
316  else {
317  error["message"] = "unknown error";
318  }
319  return error;
320  }()},
321  }));
322  PACKIO_TRACE("response: " + res);
323  return res;
324  }
325 
326  static net::const_buffer buffer(const std::string& buf)
327  {
328  return net::const_buffer(buf.data(), buf.size());
329  }
330 
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)
335  {
336  try {
337  if (args.is_array()) {
338  return convert_positional_args<T>(args.get_array(), specs);
339  }
340  else if (args.is_object()) {
341  return convert_named_args<T>(args.get_object(), specs);
342  }
343  else {
344  throw std::runtime_error{"arguments are not a structured type"};
345  }
346  }
347  catch (const std::exception& exc) {
348  return internal::unexpected{
349  std::string{"cannot convert arguments: "} + exc.what()};
350  }
351  }
352 
353 private:
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)
358  {
359  return convert_positional_args<T>(
360  array, specs, std::make_index_sequence<args_specs<F>::size()>());
361  }
362 
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...>)
368  {
369  if (!specs.options().allow_extra_arguments
370  && array.size() > std::tuple_size_v<T>) {
371  throw std::runtime_error{"too many arguments"};
372  }
373  return {[&]() {
374  if (Idxs < array.size()) {
375  try {
376  return boost::json::value_to<std::tuple_element_t<Idxs, T>>(
377  array.at(Idxs));
378  }
379  catch (const boost::json::system_error&) {
380  throw std::runtime_error{
381  "invalid type for argument "
382  + specs.template get<Idxs>().name()};
383  }
384  }
385  if (const auto& value = specs.template get<Idxs>().default_value()) {
386  return *value;
387  }
388  throw std::runtime_error{
389  "no value for argument " + specs.template get<Idxs>().name()};
390  }()...};
391  }
392 
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)
397  {
398  return convert_named_args<T>(
399  args, specs, std::make_index_sequence<args_specs<F>::size()>());
400  }
401 
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...>)
407  {
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())};
419  }
420  }
421  }
422 
423  return T{[&]() {
424  auto it = args.find(specs.template get<Idxs>().name());
425  if (it != args.end()) {
426  try {
427  return boost::json::value_to<std::tuple_element_t<Idxs, T>>(
428  it->value());
429  }
430  catch (const boost::json::system_error&) {
431  throw std::runtime_error{
432  "invalid type for argument "
433  + specs.template get<Idxs>().name()};
434  }
435  }
436  if (const auto& value = specs.template get<Idxs>().default_value()) {
437  return *value;
438  }
439  throw std::runtime_error{
440  "no value for argument " + specs.template get<Idxs>().name()};
441  }()...};
442  }
443 };
444 
445 } // json_rpc
446 } // packio
447 
448 #endif // PACKIO_JSON_RPC_RPC_H
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