aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/rpc/client.cpp8
-rw-r--r--src/rpc/server.cpp28
-rw-r--r--src/test/rpc_tests.cpp60
3 files changed, 94 insertions, 2 deletions
diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp
index 8688263ef5..b3434b80c7 100644
--- a/src/rpc/client.cpp
+++ b/src/rpc/client.cpp
@@ -277,11 +277,13 @@ UniValue RPCConvertValues(const std::string &strMethod, const std::vector<std::s
UniValue RPCConvertNamedValues(const std::string &strMethod, const std::vector<std::string> &strParams)
{
UniValue params(UniValue::VOBJ);
+ UniValue positional_args{UniValue::VARR};
for (const std::string &s: strParams) {
size_t pos = s.find('=');
if (pos == std::string::npos) {
- throw(std::runtime_error("No '=' in named argument '"+s+"', this needs to be present for every argument (even if it is empty)"));
+ positional_args.push_back(rpcCvtTable.convert(strMethod, positional_args.size()) ? ParseNonRFCJSONValue(s) : s);
+ continue;
}
std::string name = s.substr(0, pos);
@@ -296,5 +298,9 @@ UniValue RPCConvertNamedValues(const std::string &strMethod, const std::vector<s
}
}
+ if (!positional_args.empty()) {
+ params.pushKV("args", positional_args);
+ }
+
return params;
}
diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp
index 1d7bd2eb94..f57133f75b 100644
--- a/src/rpc/server.cpp
+++ b/src/rpc/server.cpp
@@ -401,8 +401,16 @@ static inline JSONRPCRequest transformNamedArguments(const JSONRPCRequest& in, c
for (size_t i=0; i<keys.size(); ++i) {
argsIn[keys[i]] = &values[i];
}
- // Process expected parameters.
+ // Process expected parameters. If any parameters were left unspecified in
+ // the request before a parameter that was specified, null values need to be
+ // inserted at the unspecifed parameter positions, and the "hole" variable
+ // below tracks the number of null values that need to be inserted.
+ // The "initial_hole_size" variable stores the size of the initial hole,
+ // i.e. how many initial positional arguments were left unspecified. This is
+ // used after the for-loop to add initial positional arguments from the
+ // "args" parameter, if present.
int hole = 0;
+ int initial_hole_size = 0;
for (const std::string &argNamePattern: argNames) {
std::vector<std::string> vargNames = SplitString(argNamePattern, '|');
auto fr = argsIn.end();
@@ -424,6 +432,24 @@ static inline JSONRPCRequest transformNamedArguments(const JSONRPCRequest& in, c
argsIn.erase(fr);
} else {
hole += 1;
+ if (out.params.empty()) initial_hole_size = hole;
+ }
+ }
+ // If leftover "args" param was found, use it as a source of positional
+ // arguments and add named arguments after. This is a convenience for
+ // clients that want to pass a combination of named and positional
+ // arguments as described in doc/JSON-RPC-interface.md#parameter-passing
+ auto positional_args{argsIn.extract("args")};
+ if (positional_args && positional_args.mapped()->isArray()) {
+ const bool has_named_arguments{initial_hole_size < (int)argNames.size()};
+ if (initial_hole_size < (int)positional_args.mapped()->size() && has_named_arguments) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Parameter " + argNames[initial_hole_size] + " specified twice both as positional and named argument");
+ }
+ // Assign positional_args to out.params and append named_args after.
+ UniValue named_args{std::move(out.params)};
+ out.params = *positional_args.mapped();
+ for (size_t i{out.params.size()}; i < named_args.size(); ++i) {
+ out.params.push_back(named_args[i]);
}
}
// If there are still arguments in the argsIn map, this is an error.
diff --git a/src/test/rpc_tests.cpp b/src/test/rpc_tests.cpp
index a52530e179..21ccbe9648 100644
--- a/src/test/rpc_tests.cpp
+++ b/src/test/rpc_tests.cpp
@@ -17,12 +17,49 @@
#include <boost/test/unit_test.hpp>
+static UniValue JSON(std::string_view json)
+{
+ UniValue value;
+ BOOST_CHECK(value.read(json.data(), json.size()));
+ return value;
+}
+
+class HasJSON
+{
+public:
+ explicit HasJSON(std::string json) : m_json(std::move(json)) {}
+ bool operator()(const UniValue& value) const
+ {
+ std::string json{value.write()};
+ BOOST_CHECK_EQUAL(json, m_json);
+ return json == m_json;
+ };
+
+private:
+ const std::string m_json;
+};
+
class RPCTestingSetup : public TestingSetup
{
public:
+ UniValue TransformParams(const UniValue& params, std::vector<std::string> arg_names) const;
UniValue CallRPC(std::string args);
};
+UniValue RPCTestingSetup::TransformParams(const UniValue& params, std::vector<std::string> arg_names) const
+{
+ UniValue transformed_params;
+ CRPCTable table;
+ CRPCCommand command{"category", "method", [&](const JSONRPCRequest& request, UniValue&, bool) -> bool { transformed_params = request.params; return true; }, arg_names, /*unique_id=*/0};
+ table.appendCommand("method", &command);
+ JSONRPCRequest request;
+ request.strMethod = "method";
+ request.params = params;
+ if (RPCIsInWarmup(nullptr)) SetRPCWarmupFinished();
+ table.execute(request);
+ return transformed_params;
+}
+
UniValue RPCTestingSetup::CallRPC(std::string args)
{
std::vector<std::string> vArgs{SplitString(args, ' ')};
@@ -45,6 +82,29 @@ UniValue RPCTestingSetup::CallRPC(std::string args)
BOOST_FIXTURE_TEST_SUITE(rpc_tests, RPCTestingSetup)
+BOOST_AUTO_TEST_CASE(rpc_namedparams)
+{
+ const std::vector<std::string> arg_names{{"arg1", "arg2", "arg3", "arg4", "arg5"}};
+
+ // 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]");
+
+ // Make sure named and positional arguments can be combined.
+ BOOST_CHECK_EQUAL(TransformParams(JSON(R"({"arg5": 5, "args": [1, 2], "arg4": 4})"), arg_names).write(), "[1,2,null,4,5]");
+
+ // Make sure a unknown named argument raises an exception
+ BOOST_CHECK_EXCEPTION(TransformParams(JSON(R"({"arg2": 2, "unknown": 6})"), arg_names), UniValue,
+ HasJSON(R"({"code":-8,"message":"Unknown named parameter unknown"})"));
+
+ // Make sure an overlap between a named argument and positional argument raises an exception
+ BOOST_CHECK_EXCEPTION(TransformParams(JSON(R"({"args": [1,2,3], "arg4": 4, "arg2": 2})"), arg_names), UniValue,
+ HasJSON(R"({"code":-8,"message":"Parameter arg2 specified twice both as positional and named argument"})"));
+
+ // Make sure extra positional arguments can be passed through to the method implemenation, as long as they don't overlap with named arguments.
+ BOOST_CHECK_EQUAL(TransformParams(JSON(R"({"args": [1,2,3,4,5,6,7,8,9,10]})"), arg_names).write(), "[1,2,3,4,5,6,7,8,9,10]");
+ BOOST_CHECK_EQUAL(TransformParams(JSON(R"([1,2,3,4,5,6,7,8,9,10])"), arg_names).write(), "[1,2,3,4,5,6,7,8,9,10]");
+}
+
BOOST_AUTO_TEST_CASE(rpc_rawparams)
{
// Test raw transaction API argument handling