Skip to content

Commit

Permalink
rpc: Support named arguments
Browse files Browse the repository at this point in the history
The [JSON-RPC specification](http://www.jsonrpc.org/specification)
allows passing parameters as an Array, for by-position
arguments, or an Object, for by-name arguments.

This implements by-name arguments, but preserves full backwards
compatibility. API using by-name arguments are
easier to extend, and easier to use (no need to guess which argument
goes where).

Named are mapped to positions by a per-call structure, provided through
the RPC command table.

Missing arguments will be replaced by null, except if at the end, then
the argument is left out completely.

Currently calls fail (though not crash) on intermediate nulls, but this
should be improved on a per-call basis later.
  • Loading branch information
laanwj authored and random-zebra committed May 25, 2021
1 parent a0da903 commit a00e323
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 13 deletions.
71 changes: 58 additions & 13 deletions src/rpc/server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@
#include <boost/signals2/signal.hpp>
#include <boost/thread.hpp>

#include <univalue.h>

#include <memory> // for unique_ptr
#include <univalue.h>
#include <unordered_map>

static bool fRPCRunning = false;
static bool fRPCInWarmup = true;
Expand Down Expand Up @@ -274,12 +274,11 @@ UniValue stop(const JSONRPCRequest& jsonRequest)
*/
static const CRPCCommand vRPCCommands[] =
{
// category name actor (function) okSafeMode
// --------------------- ------------------------ ----------------------- ----------
/* Overall control/query calls */

{"control", "help", &help, true },
{"control", "stop", &stop, true },
// category name actor (function) okSafe argNames
// --------------------- ------------------------ ----------------------- ------ ----------
/* Overall control/query calls */
{ "control", "help", &help, true, {"command"} },
{ "control", "stop", &stop, true, {} },
};

CRPCTable::CRPCTable()
Expand Down Expand Up @@ -385,12 +384,12 @@ void JSONRPCRequest::parse(const UniValue& valRequest)

// Parse params
UniValue valParams = find_value(request, "params");
if (valParams.isArray())
params = valParams.get_array();
if (valParams.isArray() || valParams.isObject())
params = valParams;
else if (valParams.isNull())
params = UniValue(UniValue::VARR);
else
throw JSONRPCError(RPC_INVALID_REQUEST, "Params must be an array");
throw JSONRPCError(RPC_INVALID_REQUEST, "Params must be an array or object");
}

bool IsDeprecatedRPCEnabled(const std::string& method)
Expand Down Expand Up @@ -429,6 +428,48 @@ std::string JSONRPCExecBatch(const UniValue& vReq)
return ret.write() + "\n";
}

/**
* Process named arguments into a vector of positional arguments, based on the
* passed-in specification for the RPC call's arguments.
*/
static inline JSONRPCRequest transformNamedArguments(const JSONRPCRequest& in, const std::vector<std::string>& argNames)
{
JSONRPCRequest out = in;
out.params = UniValue(UniValue::VARR);
// Build a map of parameters, and remove ones that have been processed, so that we can throw a focused error if
// there is an unknown one.
const std::vector<std::string>& keys = in.params.getKeys();
const std::vector<UniValue>& values = in.params.getValues();
std::unordered_map<std::string, const UniValue*> argsIn;
for (size_t i=0; i<keys.size(); ++i) {
argsIn[keys[i]] = &values[i];
}
// Process expected parameters.
int hole = 0;
for (const std::string &argName: argNames) {
auto fr = argsIn.find(argName);
if (fr != argsIn.end()) {
for (int i = 0; i < hole; ++i) {
// Fill hole between specified parameters with JSON nulls,
// but not at the end (for backwards compatibility with calls
// that act based on number of specified parameters).
out.params.push_back(UniValue());
}
hole = 0;
out.params.push_back(*fr->second);
argsIn.erase(fr);
} else {
hole += 1;
}
}
// If there are still arguments in the argsIn map, this is an error.
if (!argsIn.empty()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Unknown named parameter " + argsIn.begin()->first);
}
// Return request with named arguments transformed to positional arguments
return out;
}

UniValue CRPCTable::execute(const JSONRPCRequest &request) const
{
// Return immediately if in warmup
Expand All @@ -445,8 +486,12 @@ UniValue CRPCTable::execute(const JSONRPCRequest &request) const
g_rpcSignals.PreCommand(*pcmd);

try {
// Execute
return pcmd->actor(request);
// Execute, convert arguments to array if necessary
if (request.params.isObject()) {
return pcmd->actor(transformNamedArguments(request, pcmd->argNames));
} else {
return pcmd->actor(request);
}
} catch (const std::exception& e) {
throw JSONRPCError(RPC_MISC_ERROR, e.what());
}
Expand Down
1 change: 1 addition & 0 deletions src/rpc/server.h
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ class CRPCCommand
std::string name;
rpcfn_type actor;
bool okSafeMode;
std::vector<std::string> argNames;
};

/**
Expand Down

0 comments on commit a00e323

Please sign in to comment.