diff --git a/src/rpc/request.cpp b/src/rpc/request.cpp index a72191909988c..e6c6f1118f773 100644 --- a/src/rpc/request.cpp +++ b/src/rpc/request.cpp @@ -196,15 +196,13 @@ void JSONRPCRequest::parse(const UniValue& valRequest) const UniValue& JSONRPCRequest::JSONRPCParameters::operator[](const std::string& key) const { const auto it = named.find(key); - Assert(it != named.end()); + if (it == named.end()) return NullUniValue; return *it->second; } const UniValue& JSONRPCRequest::JSONRPCParameters::operator[](size_t pos) const { - if (pos >= positional.size()) { - return NullUniValue; - } + if (pos >= positional.size()) return NullUniValue; const UniValue* item = positional.at(pos); return item ? *item : NullUniValue; } diff --git a/src/rpc/request.h b/src/rpc/request.h index 0f67d242a6769..227076bb7e86f 100644 --- a/src/rpc/request.h +++ b/src/rpc/request.h @@ -34,8 +34,7 @@ class JSONRPCRequest public: /** The parameters as received */ UniValue received; - /** Parameter name to parameter. All parameters will be present, - * those not provided at all (neither named nor positional) will be stored as NullUniValue */ + /** Parameter name to parameter. Only provided parameters will be present. */ std::unordered_map named; /** Parameter position to parameter. * Parameters not provided at all (neither named nor positional) will be stored as nullptr, @@ -46,9 +45,9 @@ class JSONRPCRequest /** Process the received parameters into named and positional mappings */ void ProcessParameters(const std::vector& argNames); - /** Retrieve a parameter by its name */ + /** Retrieve a parameter by its name. Unknown parameters are returned as NullUniValue. */ const UniValue& operator[](const std::string& key) const; - /** Retrieve a parameter by its position */ + /** Retrieve a parameter by its position. Out of bound parameters are returned as NullUniValue */ const UniValue& operator[](size_t pos) const; /** The number of parameters received */ diff --git a/src/test/rpc_tests.cpp b/src/test/rpc_tests.cpp index e5512e1a1207d..84b053953f04c 100644 --- a/src/test/rpc_tests.cpp +++ b/src/test/rpc_tests.cpp @@ -88,6 +88,7 @@ BOOST_AUTO_TEST_CASE(rpc_namedparams) // Make sure named arguments are transformed into positional arguments in correct places separated by nulls BOOST_CHECK_EQUAL(TransformParams(JSON(R"({"arg2": 2, "arg4": 4})"), arg_names).write(), "[null,2,null,4]"); + BOOST_CHECK_EQUAL(TransformParams(JSON(R"({"arg2": 2, "arg4": 4, "arg5": null})"), arg_names).write(), "[null,2,null,4,null]"); // Make sure named argument specified multiple times raises an exception BOOST_CHECK_EXCEPTION(TransformParams(JSON(R"({"arg2": 2, "arg2": 4})"), arg_names), UniValue, @@ -541,4 +542,49 @@ BOOST_AUTO_TEST_CASE(help_example) BOOST_CHECK_NE(HelpExampleRpcNamed("foo", {{"arg", true}}), HelpExampleRpcNamed("foo", {{"arg", "true"}})); } +static void TestParamsByName(const UniValue& params) +{ + const std::vector arg_names{"arg0", "arg1", "arg2", "arg3", "arg4", "arg5"}; + CRPCTable table; + CRPCCommand command{"category", "method", + [&](const JSONRPCRequest& request, UniValue&, bool) -> bool + { + for (size_t i = 0; i < request.params.size() * 2; ++i) { + std::string arg_name = strprintf("arg%u", i); + if (i < arg_names.size()) { + // Known parameters should be the same for both positional and named + BOOST_CHECK_EQUAL(request.params[arg_name].getType(), request.params[i].getType()); + BOOST_CHECK_EQUAL(request.params[arg_name].getValStr(), request.params[i].getValStr()); + } else { + // Unknown positionals should still be accessible + auto arg = request.params[i]; + if (i > request.params.size()) { + // Out of bound positionals are always NullUniValue + BOOST_CHECK(arg.isNull()); + } + // Unknown named are always NullUniValue + BOOST_CHECK(request.params[arg_name].isNull()); + } + } + return true; + }, + arg_names, + /*unique_id=*/0 + }; + table.appendCommand("method", &command); + JSONRPCRequest request; + request.strMethod = "method"; + request.params.received = params; + if (RPCIsInWarmup(nullptr)) SetRPCWarmupFinished(); + table.execute(request); +} + +BOOST_AUTO_TEST_CASE(rpc_params_by_name) +{ + TestParamsByName(JSON(R"({"arg2": 2, "arg4": 4})")); + TestParamsByName(JSON(R"({"arg2": 2, "arg4": 4, "arg5": null})")); + TestParamsByName(JSON(R"([1, 2, 3, 4, 5, 6, 7, 8])")); + TestParamsByName(JSON(R"({"args": [1, 2, 3], "arg5": 5})")); +} + BOOST_AUTO_TEST_SUITE_END()