Skip to content

Commit

Permalink
Add enum for specified error types (Closes #17)
Browse files Browse the repository at this point in the history
  • Loading branch information
cinemast committed Mar 13, 2021
1 parent 3d8a8fe commit 4099778
Show file tree
Hide file tree
Showing 8 changed files with 76 additions and 32 deletions.
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ if (COMPILE_TESTS)
target_compile_options(coverage_config INTERFACE -O0 -g --coverage)
target_link_libraries(coverage_config INTERFACE --coverage)
endif ()
add_executable(jsonrpccpp-test test/main.cpp test/client.cpp test/typemapper.cpp test/dispatcher.cpp test/server.cpp test/batchclient.cpp test/testclientconnector.hpp examples/warehouse/warehouseapp.cpp test/warehouseapp.cpp)
add_executable(jsonrpccpp-test test/main.cpp test/client.cpp test/typemapper.cpp test/dispatcher.cpp test/server.cpp test/batchclient.cpp test/testclientconnector.hpp examples/warehouse/warehouseapp.cpp test/warehouseapp.cpp test/common.cpp)
target_compile_options(jsonrpccpp-test PUBLIC "${_warning_opts}")
target_include_directories(jsonrpccpp-test PRIVATE vendor examples)
target_link_libraries(jsonrpccpp-test coverage_config json-rpc-cxx)
Expand Down
8 changes: 4 additions & 4 deletions include/jsonrpccxx/batchclient.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,12 @@ namespace jsonrpccxx {
try {
return response[results[id]]["result"].get<T>();
} catch (json::type_error &e) {
throw JsonRpcException(-32700, "invalid return type: " + std::string(e.what()));
throw JsonRpcException(parse_error, "invalid return type: " + std::string(e.what()));
}
} else if (errors.find(id) != errors.end()) {
throw JsonRpcException::fromJson(response[errors[id]]["error"]);
}
throw JsonRpcException(-32700, std::string("no result found for id ") + id.dump());
throw JsonRpcException(parse_error, std::string("no result found for id ") + id.dump());
}

bool HasErrors() { return !errors.empty() || !nullIds.empty(); }
Expand All @@ -90,11 +90,11 @@ namespace jsonrpccxx {
try {
json response = json::parse(connector.Send(request.Build().dump()));
if (!response.is_array()) {
throw JsonRpcException(-32700, std::string("invalid JSON response from server: expected 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(-32700, std::string("invalid JSON response from server: ") + e.what());
throw JsonRpcException(parse_error, std::string("invalid JSON response from server: ") + e.what());
}
}
};
Expand Down
6 changes: 3 additions & 3 deletions include/jsonrpccxx/client.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,17 +60,17 @@ namespace jsonrpccxx {
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)) {
throw JsonRpcException(-32603, response["error"]);
throw JsonRpcException(internal_error, response["error"]);
}
if (has_key(response, "result") && has_key(response, "id")) {
if (response["id"].type() == json::value_t::string)
return JsonRpcResponse{response["id"].get<std::string>(), response["result"].get<json>()};
else
return JsonRpcResponse{response["id"].get<int>(), response["result"].get<json>()};
}
throw JsonRpcException(-32603, R"(invalid server response: neither "result" nor "error" fields found)");
throw JsonRpcException(internal_error, R"(invalid server response: neither "result" nor "error" fields found)");
} catch (json::parse_error &e) {
throw JsonRpcException(-32700, std::string("invalid JSON response from server: ") + e.what());
throw JsonRpcException(parse_error, std::string("invalid JSON response from server: ") + e.what());
}
}

Expand Down
22 changes: 21 additions & 1 deletion include/jsonrpccxx/common.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,32 @@ namespace jsonrpccxx {
}
static inline bool valid_id_not_null(const json &request) { return has_key(request, "id") && (request["id"].is_number() || request["id"].is_string()); }

enum error_type {
parse_error = -32700,
invalid_request = -32600,
method_not_found = -32601,
invalid_params = -32602,
internal_error = -32603,
server_error,
invalid
};

class JsonRpcException : public std::exception {
public:
JsonRpcException(int code, const std::string &message) noexcept : code(code), message(message), data(nullptr), err(std::to_string(code) + ": " + message) {}
JsonRpcException(int code, const std::string &message, const json &data) noexcept
: code(code), message(message), data(data), err(std::to_string(code) + ": " + message + ", data: " + data.dump()) {}

error_type Type() const {
if (code >= -32603 && code <= -32600)
return static_cast<error_type>(code);
if (code >= -32099 && code <= -32000)
return server_error;
if (code == -32700)
return parse_error;
return invalid;
}

int Code() const { return code; }
const std::string &Message() const { return message; }
const json &Data() const { return data; }
Expand All @@ -36,7 +56,7 @@ namespace jsonrpccxx {
return JsonRpcException(value["code"], value["message"]);
}
}
return JsonRpcException(-32603, R"(invalid error response: "code" (negative number) and "message" (string) are required)");
return JsonRpcException(internal_error, R"(invalid error response: "code" (negative number) and "message" (string) are required)");
}

private:
Expand Down
14 changes: 7 additions & 7 deletions include/jsonrpccxx/dispatcher.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,12 @@ namespace jsonrpccxx {
json InvokeMethod(const std::string &name, const json &params) {
auto method = methods.find(name);
if (method == methods.end()) {
throw JsonRpcException(-32601, "method not found: " + name);
throw JsonRpcException(method_not_found, "method not found: " + name);
}
try {
return method->second(normalize_parameter(name, params));
} catch (json::type_error &e) {
throw JsonRpcException(-32602, "invalid parameter: " + std::string(e.what()));
throw JsonRpcException(invalid_params, "invalid parameter: " + std::string(e.what()));
} catch (JsonRpcException &e) {
throw process_type_error(name, e);
}
Expand All @@ -67,12 +67,12 @@ namespace jsonrpccxx {
void InvokeNotification(const std::string &name, const json &params) {
auto notification = notifications.find(name);
if (notification == notifications.end()) {
throw JsonRpcException(-32601, "notification not found: " + name);
throw JsonRpcException(method_not_found, "notification not found: " + name);
}
try {
notification->second(normalize_parameter(name, params));
} catch (json::type_error &e) {
throw JsonRpcException(-32602, "invalid parameter: " + std::string(e.what()));
throw JsonRpcException(invalid_params, "invalid parameter: " + std::string(e.what()));
} catch (JsonRpcException &e) {
throw process_type_error(name, e);
}
Expand All @@ -89,18 +89,18 @@ namespace jsonrpccxx {
return params;
} else if (params.type() == json::value_t::object) {
if (mapping.find(name) == mapping.end()) {
throw JsonRpcException(-32602, "invalid parameter: procedure doesn't support named parameter");
throw JsonRpcException(invalid_params, "invalid parameter: procedure doesn't support named parameter");
}
json result;
for (auto const &p : mapping[name]) {
if (params.find(p) == params.end()) {
throw JsonRpcException(-32602, "invalid parameter: missing named parameter \"" + p + "\"");
throw JsonRpcException(invalid_params, "invalid parameter: missing named parameter \"" + p + "\"");
}
result.push_back(params[p]);
}
return result;
}
throw JsonRpcException(-32600, "invalid request: params field must be an array, object");
throw JsonRpcException(invalid_request, "invalid request: params field must be an array, object");
}
};
} // namespace jsonrpccxx
18 changes: 9 additions & 9 deletions include/jsonrpccxx/server.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,10 @@ namespace jsonrpccxx {
return "";
}
} else {
return json{{"id", nullptr}, {"error", {{"code", -32600}, {"message", "invalid request: expected array or object"}}}, {"jsonrpc", "2.0"}}.dump();
return json{{"id", nullptr}, {"error", {{"code", invalid_request}, {"message", "invalid request: expected array or object"}}}, {"jsonrpc", "2.0"}}.dump();
}
} catch (json::parse_error &e) {
return json{{"id", nullptr}, {"error", {{"code", -32700}, {"message", std::string("parse error: ") + e.what()}}}, {"jsonrpc", "2.0"}}.dump();
return json{{"id", nullptr}, {"error", {{"code", parse_error}, {"message", std::string("parse error: ") + e.what()}}}, {"jsonrpc", "2.0"}}.dump();
}
}

Expand All @@ -73,24 +73,24 @@ namespace jsonrpccxx {
}
return json{{"id", id}, {"error", error}, {"jsonrpc", "2.0"}};
} catch (std::exception &e) {
return json{{"id", id}, {"error", {{"code", -32603}, {"message", std::string("internal server error: ") + e.what()}}}, {"jsonrpc", "2.0"}};
return json{{"id", id}, {"error", {{"code", internal_error}, {"message", std::string("internal server error: ") + e.what()}}}, {"jsonrpc", "2.0"}};
} catch (...) {
return json{{"id", id}, {"error", {{"code", -32603}, {"message", std::string("internal server error")}}}, {"jsonrpc", "2.0"}};
return json{{"id", id}, {"error", {{"code", internal_error}, {"message", std::string("internal server error")}}}, {"jsonrpc", "2.0"}};
}
}

json ProcessSingleRequest(json &request) {
if (!has_key_type(request, "jsonrpc", json::value_t::string) || request["jsonrpc"] != "2.0") {
throw JsonRpcException(-32600, R"(invalid request: missing jsonrpc field set to "2.0")");
throw JsonRpcException(invalid_request, R"(invalid request: missing jsonrpc field set to "2.0")");
}
if (!has_key_type(request, "method", json::value_t::string)) {
throw JsonRpcException(-32600, "invalid request: method field must be a string");
throw JsonRpcException(invalid_request, "invalid request: method field must be a string");
}
if (has_key(request, "id") && !valid_id(request)) {
throw JsonRpcException(-32600, "invalid request: id field must be a number, string or null");
throw JsonRpcException(invalid_request, "invalid request: id field must be a number, string or null");
}
if (has_key(request, "params") && !(request["params"].is_array() || request["params"].is_object() || request["params"].is_null())) {
throw JsonRpcException(-32600, "invalid request: params field must be an array, object or 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();
Expand All @@ -99,7 +99,7 @@ namespace jsonrpccxx {
try {
dispatcher.InvokeNotification(request["method"], request["params"]);
return json();
} catch (std::exception &e) {
} catch (std::exception &) {
return json();
}
} else {
Expand Down
14 changes: 7 additions & 7 deletions include/jsonrpccxx/typemapper.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,25 +67,25 @@ namespace jsonrpccxx {
inline void check_param_type(size_t index, const json &x, json::value_t expectedType, typename std::enable_if<std::is_arithmetic<T>::value>::type * = 0) {
if (expectedType == json::value_t::number_unsigned && x.type() == json::value_t::number_integer) {
if (x.get<long long int>() < 0)
throw JsonRpcException(-32602, "invalid parameter: must be " + type_name(expectedType) + ", but is " + type_name(x.type()), index);
throw JsonRpcException(invalid_params, "invalid parameter: must be " + type_name(expectedType) + ", but is " + type_name(x.type()), index);
} else if (x.type() == json::value_t::number_unsigned && expectedType == json::value_t::number_integer) {
if (x.get<long long unsigned>() > std::numeric_limits<T>::max()) {
throw JsonRpcException(-32602, "invalid parameter: exceeds value range of " + type_name(expectedType), index);
throw JsonRpcException(invalid_params, "invalid parameter: exceeds value range of " + type_name(expectedType), index);
}
}
else if ((x.type() == json::value_t::number_unsigned || x.type() == json::value_t::number_integer) && expectedType == json::value_t::number_float) {
if (static_cast<long long int>(x.get<double>()) != x.get<long long int>()) {
throw JsonRpcException(-32602, "invalid parameter: exceeds value range of " + type_name(expectedType), index);
throw JsonRpcException(invalid_params, "invalid parameter: exceeds value range of " + type_name(expectedType), index);
}
} else if (x.type() != expectedType) {
throw JsonRpcException(-32602, "invalid parameter: must be " + type_name(expectedType) + ", but is " + type_name(x.type()), index);
throw JsonRpcException(invalid_params, "invalid parameter: must be " + type_name(expectedType) + ", but is " + type_name(x.type()), index);
}
}

template <typename T>
inline void check_param_type(size_t index, const json &x, json::value_t expectedType, typename std::enable_if<!std::is_arithmetic<T>::value>::type * = 0) {
if (x.type() != expectedType) {
throw JsonRpcException(-32602, "invalid parameter: must be " + type_name(expectedType) + ", but is " + type_name(x.type()), index);
throw JsonRpcException(invalid_params, "invalid parameter: must be " + type_name(expectedType) + ", but is " + type_name(x.type()), index);
}
}

Expand All @@ -99,7 +99,7 @@ namespace jsonrpccxx {
size_t formalSize = sizeof...(ParamTypes);
// TODO: add lenient mode for backwards compatible additional params
if (actualSize != formalSize) {
throw JsonRpcException(-32602, "invalid parameter: expected " + std::to_string(formalSize) + " argument(s), but found " + std::to_string(actualSize));
throw JsonRpcException(invalid_params, "invalid parameter: expected " + std::to_string(formalSize) + " argument(s), but found " + std::to_string(actualSize));
}
(check_param_type<typename std::decay<ParamTypes>::type>(index, params[index], GetType(type<typename std::decay<ParamTypes>::type>())), ...);
return method(params[index].get<typename std::decay<ParamTypes>::type>()...);
Expand Down Expand Up @@ -133,7 +133,7 @@ namespace jsonrpccxx {
// TODO: add lenient mode for backwards compatible additional params
// if ((!allow_unkown_params && actualSize != formalSize) || (allow_unkown_params && actualSize < formalSize)) {
if (actualSize != formalSize) {
throw JsonRpcException(-32602, "invalid parameter: expected " + std::to_string(formalSize) + " argument(s), but found " + std::to_string(actualSize));
throw JsonRpcException(invalid_params, "invalid parameter: expected " + std::to_string(formalSize) + " argument(s), but found " + std::to_string(actualSize));
}
(check_param_type<typename std::decay<ParamTypes>::type>(index, params[index], GetType(type<typename std::decay<ParamTypes>::type>())), ...);
method(params[index].get<typename std::decay<ParamTypes>::type>()...);
Expand Down
24 changes: 24 additions & 0 deletions test/common.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#include "catch/catch.hpp"
#include <iostream>
#include <jsonrpccxx/common.hpp>

#define TEST_MODULE "[common]"

using namespace std;
using namespace jsonrpccxx;
using namespace Catch::Matchers;

TEST_CASE("exception error type", TEST_MODULE) {
CHECK(JsonRpcException(-32700, "").Type() == parse_error);
CHECK(JsonRpcException(-32600, "").Type() == invalid_request);
CHECK(JsonRpcException(-32601, "").Type() == method_not_found);
CHECK(JsonRpcException(-32602, "").Type() == invalid_params);
CHECK(JsonRpcException(-32603, "").Type() == internal_error);

for(int c = -32000; c >= -32099; c--)
CHECK(JsonRpcException(c, "").Type() == server_error);

CHECK(JsonRpcException(0, "").Type() == invalid);
CHECK(JsonRpcException(32700, "").Type() == invalid);
CHECK(JsonRpcException(33000, "").Type() == invalid);
}

0 comments on commit 4099778

Please sign in to comment.