From 4099778b97b5d6774c1b8611f7b1d061dbf778a4 Mon Sep 17 00:00:00 2001 From: Peter Spiess-Knafl Date: Sat, 13 Mar 2021 10:25:41 +0100 Subject: [PATCH] Add enum for specified error types (Closes #17) --- CMakeLists.txt | 2 +- include/jsonrpccxx/batchclient.hpp | 8 ++++---- include/jsonrpccxx/client.hpp | 6 +++--- include/jsonrpccxx/common.hpp | 22 +++++++++++++++++++++- include/jsonrpccxx/dispatcher.hpp | 14 +++++++------- include/jsonrpccxx/server.hpp | 18 +++++++++--------- include/jsonrpccxx/typemapper.hpp | 14 +++++++------- test/common.cpp | 24 ++++++++++++++++++++++++ 8 files changed, 76 insertions(+), 32 deletions(-) create mode 100644 test/common.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 9feebad..de21f21 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/include/jsonrpccxx/batchclient.hpp b/include/jsonrpccxx/batchclient.hpp index c2084de..727fe05 100644 --- a/include/jsonrpccxx/batchclient.hpp +++ b/include/jsonrpccxx/batchclient.hpp @@ -64,12 +64,12 @@ namespace jsonrpccxx { try { return response[results[id]]["result"].get(); } 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(); } @@ -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()); } } }; diff --git a/include/jsonrpccxx/client.hpp b/include/jsonrpccxx/client.hpp index b776067..3241d15 100644 --- a/include/jsonrpccxx/client.hpp +++ b/include/jsonrpccxx/client.hpp @@ -60,7 +60,7 @@ 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) @@ -68,9 +68,9 @@ namespace jsonrpccxx { else return JsonRpcResponse{response["id"].get(), response["result"].get()}; } - 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()); } } diff --git a/include/jsonrpccxx/common.hpp b/include/jsonrpccxx/common.hpp index ccc43dc..f269b4d 100644 --- a/include/jsonrpccxx/common.hpp +++ b/include/jsonrpccxx/common.hpp @@ -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(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; } @@ -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: diff --git a/include/jsonrpccxx/dispatcher.hpp b/include/jsonrpccxx/dispatcher.hpp index 572d55f..35389b7 100644 --- a/include/jsonrpccxx/dispatcher.hpp +++ b/include/jsonrpccxx/dispatcher.hpp @@ -53,12 +53,12 @@ namespace jsonrpccxx { json InvokeMethod(const std::string &name, const json ¶ms) { 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); } @@ -67,12 +67,12 @@ namespace jsonrpccxx { void InvokeNotification(const std::string &name, const json ¶ms) { 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); } @@ -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 diff --git a/include/jsonrpccxx/server.hpp b/include/jsonrpccxx/server.hpp index 03e9567..d3a6315 100644 --- a/include/jsonrpccxx/server.hpp +++ b/include/jsonrpccxx/server.hpp @@ -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(); } } @@ -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(); @@ -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 { diff --git a/include/jsonrpccxx/typemapper.hpp b/include/jsonrpccxx/typemapper.hpp index 9a77d26..6695a6a 100644 --- a/include/jsonrpccxx/typemapper.hpp +++ b/include/jsonrpccxx/typemapper.hpp @@ -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::value>::type * = 0) { if (expectedType == json::value_t::number_unsigned && x.type() == json::value_t::number_integer) { if (x.get() < 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() > std::numeric_limits::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(x.get()) != x.get()) { - 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 inline void check_param_type(size_t index, const json &x, json::value_t expectedType, typename std::enable_if::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); } } @@ -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::type>(index, params[index], GetType(type::type>())), ...); return method(params[index].get::type>()...); @@ -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::type>(index, params[index], GetType(type::type>())), ...); method(params[index].get::type>()...); diff --git a/test/common.cpp b/test/common.cpp new file mode 100644 index 0000000..f660109 --- /dev/null +++ b/test/common.cpp @@ -0,0 +1,24 @@ +#include "catch/catch.hpp" +#include +#include + +#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); +} \ No newline at end of file