5 #ifndef PACKIO_NLOHMANN_JSON_RPC_H
6 #define PACKIO_NLOHMANN_JSON_RPC_H
10 #include <nlohmann/json.hpp>
13 #include "../internal/config.h"
14 #include "../internal/log.h"
15 #include "../internal/rpc.h"
16 #include "incremental_buffers.h"
20 template <
typename... Args>
21 constexpr
bool positional_args_v = (!is_arg_v<Args> && ...);
23 template <
typename... Args>
24 constexpr
bool named_args_v =
sizeof...(Args) > 0 && (is_arg_v<Args> && ...);
28 using buffer_type = std::string;
45 nlohmann::json result;
49 class incremental_parser {
51 std::optional<request> get_request()
57 auto object = std::move(*parsed_);
59 return parse_request(std::move(
object));
62 std::optional<response> get_response()
68 auto object = std::move(*parsed_);
70 return parse_response(std::move(
object));
75 return buffer_.data();
78 std::size_t buffer_capacity()
const
80 return buffer_.size();
83 void buffer_consumed(std::size_t bytes)
88 incremental_buffers_.feed(std::string_view(buffer_.data(), bytes));
89 std::copy(begin(buffer_) + bytes, end(buffer_), begin(buffer_));
90 buffer_.resize(buffer_.size() - bytes);
93 void reserve_buffer(std::size_t bytes)
95 if (buffer_.size() >= bytes) {
98 buffer_.resize(bytes);
102 void try_parse_object()
107 auto buffer = incremental_buffers_.get_buffer();
109 parsed_ = nlohmann::json::parse(*buffer);
113 std::vector<char> buffer_;
114 std::optional<nlohmann::json> parsed_;
115 incremental_buffers incremental_buffers_;
118 static std::string format_id(
const id_type&
id)
123 template <
typename... Args>
124 static auto serialize_notification(std::string_view method, Args&&... args)
125 -> std::enable_if_t<positional_args_v<Args...>, buffer_type>
127 return nlohmann::json({
131 nlohmann::json::array({nlohmann::json(
132 std::forward<Args>(args))...})},
137 template <
typename... Args>
138 static auto serialize_notification(std::string_view method, Args&&... args)
139 -> std::enable_if_t<named_args_v<Args...>, buffer_type>
141 return nlohmann::json({
144 {
"params", {{args.name, args.value}...}},
149 template <
typename... Args>
150 static auto serialize_notification(std::string_view, Args&&...)
151 -> std::enable_if_t<!positional_args_v<Args...> && !named_args_v<Args...>, buffer_type>
154 positional_args_v<Args...> || named_args_v<Args...>,
155 "JSON-RPC does not support mixed named and unnamed arguments");
158 template <
typename... Args>
159 static auto serialize_request(
id_type id, std::string_view method, Args&&... args)
160 -> std::enable_if_t<positional_args_v<Args...>, buffer_type>
162 return nlohmann::json({
166 nlohmann::json::array({nlohmann::json(
167 std::forward<Args>(args))...})},
173 template <
typename... Args>
174 static auto serialize_request(
id_type id, std::string_view method, Args&&... args)
175 -> std::enable_if_t<named_args_v<Args...>, buffer_type>
177 return nlohmann::json({
180 {
"params", {{args.name, args.value}...}},
186 template <
typename... Args>
187 static auto serialize_request(
id_type, std::string_view, Args&&...)
188 -> std::enable_if_t<!positional_args_v<Args...> && !named_args_v<Args...>, buffer_type>
191 positional_args_v<Args...> || named_args_v<Args...>,
192 "JSON-RPC does not support mixed named and unnamed arguments");
195 template <
typename... Args>
196 static auto serialize_response(response response)
198 nlohmann::json resp = {
202 if (!response.error.is_null()) {
203 resp[
"error"] = std::move(response.error);
206 resp[
"result"] = std::move(response.result);
211 static net::const_buffer buffer(
const buffer_type& buf)
213 return net::const_buffer(buf.data(), buf.size());
216 template <
typename T,
typename NamesContainer>
217 static std::optional<T> extract_args(
218 const nlohmann::json& args,
219 const NamesContainer& names)
222 if (args.is_array()) {
223 if (args.size() != std::tuple_size_v<T>) {
227 "cannot convert args: wrong number of arguments");
231 return args.get<T>();
233 else if (args.is_object()) {
234 return convert_named_args<T>(
235 args, names, std::make_index_sequence<std::tuple_size_v<T>>{});
238 PACKIO_ERROR(
"arguments are not a structured type");
242 catch (
const std::exception& exc) {
243 PACKIO_WARN(
"cannot convert args: {}", exc.what());
248 static response make_response(
id_type id)
250 return make_response(
id, nlohmann::json{});
253 template <
typename T>
254 static response make_response(
id_type id, T&& value)
258 resp.result = std::forward<T>(value);
262 template <
typename T>
263 static response make_error_response(
id_type id, T&& value)
268 nlohmann::json error = {
270 {
"data", std::forward<T>(value)},
272 if (error[
"data"].is_string()) {
273 error[
"message"] = error[
"data"];
276 error[
"message"] =
"Unknown error";
284 template <
typename T,
typename NamesContainer, std::size_t... Idxs>
285 static T convert_named_args(
286 const nlohmann::json& args,
287 const NamesContainer& names,
288 std::index_sequence<Idxs...>)
291 args[names.at(Idxs)].template get<std::tuple_element_t<Idxs, T>>())...};
294 static std::optional<response> parse_response(nlohmann::json&& res)
296 auto id_it = res.find(
"id");
297 auto result_it = res.find(
"result");
298 auto error_it = res.find(
"error");
300 if (id_it == end(res)) {
301 PACKIO_ERROR(
"missing id field");
304 if (result_it == end(res) && error_it == end(res)) {
305 PACKIO_ERROR(
"missing error and result field");
310 parsed.id = std::move(*id_it);
311 if (error_it != end(res)) {
312 parsed.error = std::move(*error_it);
314 if (result_it != end(res)) {
315 parsed.result = std::move(*result_it);
320 static std::optional<request> parse_request(nlohmann::json&& req)
322 auto id_it = req.find(
"id");
323 auto method_it = req.find(
"method");
324 auto params_it = req.find(
"params");
326 if (method_it == end(req)) {
327 PACKIO_ERROR(
"missing method field");
330 if (!method_it->is_string()) {
331 PACKIO_ERROR(
"method field is not a string");
336 parsed.method = method_it->get<std::string>();
337 if (params_it == end(req) || params_it->is_null()) {
338 parsed.args = nlohmann::json::array();
340 else if (!params_it->is_array() && !params_it->is_object()) {
341 PACKIO_ERROR(
"non-structured arguments are not supported");
345 parsed.args = std::move(*params_it);
348 if (id_it == end(req) || id_it->is_null()) {
349 parsed.type = call_type::notification;
352 parsed.type = call_type::request;
353 parsed.id = std::move(*id_it);
361 #endif // PACKIO_NLOHMAANN_JSON_RPC_H