Skip to content

Commit 6f1c76a

Browse files
committed
rpc: Support named arguments
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.
1 parent 5865d41 commit 6f1c76a

File tree

2 files changed

+57
-9
lines changed

2 files changed

+57
-9
lines changed

src/rpc/server.cpp

Lines changed: 56 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include <boost/algorithm/string/case_conv.hpp> // for to_upper()
2727

2828
#include <memory> // for unique_ptr
29+
#include <unordered_map>
2930

3031
using namespace RPCServer;
3132
using namespace std;
@@ -268,11 +269,11 @@ UniValue stop(const JSONRPCRequest& jsonRequest)
268269
* Call Table
269270
*/
270271
static const CRPCCommand vRPCCommands[] =
271-
{ // category name actor (function) okSafeMode
272-
// --------------------- ------------------------ ----------------------- ----------
272+
{ // category name actor (function) okSafe argNames
273+
// --------------------- ------------------------ ----------------------- ------ ----------
273274
/* Overall control/query calls */
274-
{ "control", "help", &help, true },
275-
{ "control", "stop", &stop, true },
275+
{ "control", "help", &help, true, {"command"} },
276+
{ "control", "stop", &stop, true, {} },
276277
};
277278

278279
CRPCTable::CRPCTable()
@@ -379,12 +380,12 @@ void JSONRPCRequest::parse(const UniValue& valRequest)
379380

380381
// Parse params
381382
UniValue valParams = find_value(request, "params");
382-
if (valParams.isArray())
383-
params = valParams.get_array();
383+
if (valParams.isArray() || valParams.isObject())
384+
params = valParams;
384385
else if (valParams.isNull())
385386
params = UniValue(UniValue::VARR);
386387
else
387-
throw JSONRPCError(RPC_INVALID_REQUEST, "Params must be an array");
388+
throw JSONRPCError(RPC_INVALID_REQUEST, "Params must be an array or object");
388389
}
389390

390391
static UniValue JSONRPCExecOne(const UniValue& req)
@@ -420,6 +421,48 @@ std::string JSONRPCExecBatch(const UniValue& vReq)
420421
return ret.write() + "\n";
421422
}
422423

424+
/**
425+
* Process named arguments into a vector of positional arguments, based on the
426+
* passed-in specification for the RPC call's arguments.
427+
*/
428+
static inline JSONRPCRequest transformNamedArguments(const JSONRPCRequest& in, const std::vector<std::string>& argNames)
429+
{
430+
JSONRPCRequest out = in;
431+
out.params = UniValue(UniValue::VARR);
432+
// Build a map of parameters, and remove ones that have been processed, so that we can throw a focused error if
433+
// there is an unknown one.
434+
const std::vector<std::string>& keys = in.params.getKeys();
435+
const std::vector<UniValue>& values = in.params.getValues();
436+
std::unordered_map<std::string, const UniValue*> argsIn;
437+
for (size_t i=0; i<keys.size(); ++i) {
438+
argsIn[keys[i]] = &values[i];
439+
}
440+
// Process expected parameters.
441+
int hole = 0;
442+
for (const std::string &argName: argNames) {
443+
auto fr = argsIn.find(argName);
444+
if (fr != argsIn.end()) {
445+
for (int i = 0; i < hole; ++i) {
446+
// Fill hole between specified parameters with JSON nulls,
447+
// but not at the end (for backwards compatibility with calls
448+
// that act based on number of specified parameters).
449+
out.params.push_back(UniValue());
450+
}
451+
hole = 0;
452+
out.params.push_back(*fr->second);
453+
argsIn.erase(fr);
454+
} else {
455+
hole += 1;
456+
}
457+
}
458+
// If there are still arguments in the argsIn map, this is an error.
459+
if (!argsIn.empty()) {
460+
throw JSONRPCError(RPC_INVALID_PARAMETER, "Unknown named parameter " + argsIn.begin()->first);
461+
}
462+
// Return request with named arguments transformed to positional arguments
463+
return out;
464+
}
465+
423466
UniValue CRPCTable::execute(const JSONRPCRequest &request) const
424467
{
425468
// Return immediately if in warmup
@@ -438,8 +481,12 @@ UniValue CRPCTable::execute(const JSONRPCRequest &request) const
438481

439482
try
440483
{
441-
// Execute
442-
return pcmd->actor(request);
484+
// Execute, convert arguments to array if necessary
485+
if (request.params.isObject()) {
486+
return pcmd->actor(transformNamedArguments(request, pcmd->argNames));
487+
} else {
488+
return pcmd->actor(request);
489+
}
443490
}
444491
catch (const std::exception& e)
445492
{

src/rpc/server.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ class CRPCCommand
136136
std::string name;
137137
rpcfn_type actor;
138138
bool okSafeMode;
139+
std::vector<std::string> argNames;
139140
};
140141

141142
/**

0 commit comments

Comments
 (0)