Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 17 additions & 5 deletions include/jsonrpccxx/batchclient.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,19 +83,31 @@ namespace jsonrpccxx {
std::vector<size_t> nullIds;
};

class BatchClient : public JsonRpcClient {
template <class TBase, class TConnector>
class BatchClientBase : public TBase {
public:
explicit BatchClient(IClientConnector &connector) : JsonRpcClient(connector, version::v2) {}
explicit BatchClientBase(TConnector &connector) : TBase(connector, version::v2) {}

BatchResponse BatchCall(const BatchRequest &request) {
try {
json response = json::parse(connector.Send(request.Build().dump()));
json response = this->GetConnector().Send(request.Build());
if (!response.is_array()) {
throw JsonRpcException(parse_error, std::string("invalid JSON response from server: expected array"));
}
return BatchResponse(std::move(response));
} catch (json::parse_error &e) {
throw JsonRpcException(parse_error, std::string("invalid JSON response from server: ") + e.what());
} catch(const json::parse_error & e) {
throw JsonRpcException::fromJsonParseError(e);
}
}
};

class BatchClientJson : public BatchClientBase<JsonRpcClientJson,IClientJsonConnector> {
public:
explicit BatchClientJson(IClientJsonConnector &connector) : BatchClientBase<JsonRpcClientJson,IClientJsonConnector>(connector) {}
};

class BatchClient : public BatchClientBase<JsonRpcClient,IClientConnector> {
public:
explicit BatchClient(IClientConnector &connector) : BatchClientBase<JsonRpcClient,IClientConnector>(connector) {}
};
}
47 changes: 39 additions & 8 deletions include/jsonrpccxx/client.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#pragma once
#include "common.hpp"
#include "iclientjsonconnector.hpp"
#include "iclientconnector.hpp"
#include <exception>
#include <nlohmann/json.hpp>
Expand All @@ -17,10 +18,10 @@ namespace jsonrpccxx {
json result;
};

class JsonRpcClient {
class JsonRpcClientBase {
public:
JsonRpcClient(IClientConnector &connector, version v) : connector(connector), v(v) {}
virtual ~JsonRpcClient() = default;
JsonRpcClientBase(version v) : v(v) {}
virtual ~JsonRpcClientBase() = default;

template <typename T>
T CallMethod(const id_type &id, const std::string &name) { return call_method(id, name, json::object()).result.get<T>(); }
Expand All @@ -33,7 +34,7 @@ namespace jsonrpccxx {
void CallNotificationNamed(const std::string &name, const named_parameter &params = {}) { call_notification(name, params); }

protected:
IClientConnector &connector;
virtual IClientJsonConnector &GetConnector() = 0;

private:
version v;
Expand All @@ -56,7 +57,7 @@ namespace jsonrpccxx {
j["params"] = nullptr;
}
try {
json response = json::parse(connector.Send(j.dump()));
json response = GetConnector().Send(j);
if (has_key_type(response, "error", json::value_t::object)) {
throw JsonRpcException::fromJson(response["error"]);
} else if (has_key_type(response, "error", json::value_t::string)) {
Expand All @@ -69,8 +70,8 @@ namespace jsonrpccxx {
return JsonRpcResponse{response["id"].get<int>(), response["result"].get<json>()};
}
throw JsonRpcException(internal_error, R"(invalid server response: neither "result" nor "error" fields found)");
} catch (json::parse_error &e) {
throw JsonRpcException(parse_error, std::string("invalid JSON response from server: ") + e.what());
} catch (const json::parse_error &e) {
throw JsonRpcException::fromJsonParseError(e);
}
}

Expand All @@ -86,7 +87,37 @@ namespace jsonrpccxx {
} else if (v == version::v1) {
j["params"] = nullptr;
}
connector.Send(j.dump());
GetConnector().Send(j);
}
};

class JsonRpcClientJson
: public JsonRpcClientBase {
public:
JsonRpcClientJson(IClientJsonConnector &connector, version v) : JsonRpcClientBase(v), connector(connector) {}

protected:
virtual IClientJsonConnector &GetConnector() override { return connector; }

private:
IClientJsonConnector &connector;
};

class JsonRpcClient
: public JsonRpcClientBase
, private IClientJsonConnector {
public:
JsonRpcClient(IClientConnector &connector, version v) : JsonRpcClientBase(v), connector(connector) {}

protected:
IClientConnector &connector;
virtual IClientJsonConnector &GetConnector() override { return *this; }

private:

virtual json Send(const json &request) {
return json::parse(connector.Send(request.dump()));
}
};

} // namespace jsonrpccxx
8 changes: 8 additions & 0 deletions include/jsonrpccxx/common.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,18 @@ namespace jsonrpccxx {
return JsonRpcException(internal_error, R"(invalid error response: "code" (negative number) and "message" (string) are required)");
}

static inline JsonRpcException fromJsonParseError(const json::parse_error &value) {
return JsonRpcException(parse_error, std::string("invalid JSON response from server: ") + value.what());
}

private:
int code;
std::string message;
json data;
std::string err;
};

static inline json create_parse_error_response_v2(const json::parse_error &e) {
return json{{"id", nullptr}, {"error", {{"code", parse_error}, {"message", std::string("parse error: ") + e.what()}}}, {"jsonrpc", "2.0"}};
}
} // namespace jsonrpccxx
14 changes: 14 additions & 0 deletions include/jsonrpccxx/iclientjsonconnector.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#pragma once
#include "common.hpp"

namespace jsonrpccxx {
class IClientJsonConnector {
public:
virtual ~IClientJsonConnector() = default;

/***
* @throws json::parse_error In case repsonse can not be parsed.
*/
virtual json Send(const json &request) = 0;
};
}
59 changes: 33 additions & 26 deletions include/jsonrpccxx/server.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,35 +31,41 @@ namespace jsonrpccxx {
JsonRpc2Server() = default;
~JsonRpc2Server() override = default;

std::string HandleRequest(const std::string &requestString) override {
try {
json request = json::parse(requestString);
if (request.is_array()) {
json result = json::array();
for (json &r : request) {
json res = this->HandleSingleRequest(r);
if (!res.is_null()) {
result.push_back(std::move(res));
}
}
return result.dump();
} else if (request.is_object()) {
json res = HandleSingleRequest(request);
/***
* @remark If you encounter a json::parse_error, you can reply with create_parse_error_response_v2.
*/
json HandleRequest(const json &request) {
if (request.is_array()) {
json result = json::array();
for (const json &r : request) {
json res = this->HandleSingleRequest(r);
if (!res.is_null()) {
return res.dump();
} else {
return "";
result.push_back(std::move(res));
}
}
return result.dump();
} else if (request.is_object()) {
json res = HandleSingleRequest(request);
if (!res.is_null()) {
return res.dump();
} else {
return json{{"id", nullptr}, {"error", {{"code", invalid_request}, {"message", "invalid request: expected array or object"}}}, {"jsonrpc", "2.0"}}.dump();
return "";
}
} else {
return json{{"id", nullptr}, {"error", {{"code", invalid_request}, {"message", "invalid request: expected array or object"}}}, {"jsonrpc", "2.0"}}.dump();
}
}

std::string HandleRequest(const std::string &requestString) override {
try {
return HandleRequest(json::parse(requestString));
} catch (json::parse_error &e) {
return json{{"id", nullptr}, {"error", {{"code", parse_error}, {"message", std::string("parse error: ") + e.what()}}}, {"jsonrpc", "2.0"}}.dump();
return create_parse_error_response_v2(e).dump();
}
}

private:
json HandleSingleRequest(json &request) {
json HandleSingleRequest(const json &request) {
json id = nullptr;
if (valid_id(request)) {
id = request["id"];
Expand All @@ -79,7 +85,7 @@ namespace jsonrpccxx {
}
}

json ProcessSingleRequest(json &request) {
json ProcessSingleRequest(const json &request) {
if (!has_key_type(request, "jsonrpc", json::value_t::string) || request["jsonrpc"] != "2.0") {
throw JsonRpcException(invalid_request, R"(invalid request: missing jsonrpc field set to "2.0")");
}
Expand All @@ -92,18 +98,19 @@ namespace jsonrpccxx {
if (has_key(request, "params") && !(request["params"].is_array() || request["params"].is_object() || request["params"].is_null())) {
throw JsonRpcException(invalid_request, "invalid request: params field must be an array, object or null");
}
if (!has_key(request, "params") || has_key_type(request, "params", json::value_t::null)) {
request["params"] = json::array();
}
const json params = (!has_key(request, "params") || has_key_type(request, "params", json::value_t::null))
? json::array()
: request["params"]
;
if (!has_key(request, "id")) {
try {
dispatcher.InvokeNotification(request["method"], request["params"]);
dispatcher.InvokeNotification(request["method"], params);
return json();
} catch (std::exception &) {
return json();
}
} else {
return {{"jsonrpc", "2.0"}, {"id", request["id"]}, {"result", dispatcher.InvokeMethod(request["method"], request["params"])}};
return {{"jsonrpc", "2.0"}, {"id", request["id"]}, {"result", dispatcher.InvokeMethod(request["method"], params)}};
}
}
};
Expand Down
6 changes: 6 additions & 0 deletions test/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,12 @@ TEST_CASE_FIXTURE(F, "v2_method_result_empty") {
c.VerifyMethodRequest(version::v2, "some.method_1", "1");
}

TEST_CASE_FIXTURE(F, "v2_method_result_parse_error") {
c.raw_response = "abcdef";
REQUIRE_THROWS_WITH(clientV2.CallMethod<json>("1", "some.method_1", {}), "-32700: invalid JSON response from server: [json.exception.parse_error.101] parse error at line 1, column 1: syntax error while parsing value - invalid literal; last read: 'a'");
c.VerifyMethodRequest(version::v2, "some.method_1", "1");
}

/*
TEST_CASE_FIXTURE(F, "v1_method_result_empty") {
c.raw_response = "{}";
Expand Down