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 committed Jan 5, 2017
1 parent 5865d41 commit 6f1c76a
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 9 deletions.
65 changes: 56 additions & 9 deletions src/rpc/server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include <boost/algorithm/string/case_conv.hpp> // for to_upper()

#include <memory> // for unique_ptr
#include <unordered_map>

using namespace RPCServer;
using namespace std;
Expand Down Expand Up @@ -268,11 +269,11 @@ UniValue stop(const JSONRPCRequest& jsonRequest)
* Call Table
*/
static const CRPCCommand vRPCCommands[] =
{ // category name actor (function) okSafeMode
// --------------------- ------------------------ ----------------------- ----------
{ // category name actor (function) okSafe argNames
// --------------------- ------------------------ ----------------------- ------ ----------
/* Overall control/query calls */
{ "control", "help", &help, true },
{ "control", "stop", &stop, true },
{ "control", "help", &help, true, {"command"} },
{ "control", "stop", &stop, true, {} },
};

CRPCTable::CRPCTable()
Expand Down Expand Up @@ -379,12 +380,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");
}

static UniValue JSONRPCExecOne(const UniValue& req)
Expand Down Expand Up @@ -420,6 +421,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 @@ -438,8 +481,12 @@ UniValue CRPCTable::execute(const JSONRPCRequest &request) const

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)
{
Expand Down
1 change: 1 addition & 0 deletions src/rpc/server.h
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ class CRPCCommand
std::string name;
rpcfn_type actor;
bool okSafeMode;
std::vector<std::string> argNames;
};

/**
Expand Down

0 comments on commit 6f1c76a

Please sign in to comment.