Skip to content

Commit a00e323

Browse files
laanwjrandom-zebra
authored andcommitted
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 a0da903 commit a00e323

File tree

2 files changed

+59
-13
lines changed

2 files changed

+59
-13
lines changed

src/rpc/server.cpp

Lines changed: 58 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@
2323
#include <boost/signals2/signal.hpp>
2424
#include <boost/thread.hpp>
2525

26-
#include <univalue.h>
27-
2826
#include <memory> // for unique_ptr
27+
#include <univalue.h>
28+
#include <unordered_map>
2929

3030
static bool fRPCRunning = false;
3131
static bool fRPCInWarmup = true;
@@ -274,12 +274,11 @@ UniValue stop(const JSONRPCRequest& jsonRequest)
274274
*/
275275
static const CRPCCommand vRPCCommands[] =
276276
{
277-
// category name actor (function) okSafeMode
278-
// --------------------- ------------------------ ----------------------- ----------
279-
/* Overall control/query calls */
280-
281-
{"control", "help", &help, true },
282-
{"control", "stop", &stop, true },
277+
// category name actor (function) okSafe argNames
278+
// --------------------- ------------------------ ----------------------- ------ ----------
279+
/* Overall control/query calls */
280+
{ "control", "help", &help, true, {"command"} },
281+
{ "control", "stop", &stop, true, {} },
283282
};
284283

285284
CRPCTable::CRPCTable()
@@ -385,12 +384,12 @@ void JSONRPCRequest::parse(const UniValue& valRequest)
385384

386385
// Parse params
387386
UniValue valParams = find_value(request, "params");
388-
if (valParams.isArray())
389-
params = valParams.get_array();
387+
if (valParams.isArray() || valParams.isObject())
388+
params = valParams;
390389
else if (valParams.isNull())
391390
params = UniValue(UniValue::VARR);
392391
else
393-
throw JSONRPCError(RPC_INVALID_REQUEST, "Params must be an array");
392+
throw JSONRPCError(RPC_INVALID_REQUEST, "Params must be an array or object");
394393
}
395394

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

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

447488
try {
448-
// Execute
449-
return pcmd->actor(request);
489+
// Execute, convert arguments to array if necessary
490+
if (request.params.isObject()) {
491+
return pcmd->actor(transformNamedArguments(request, pcmd->argNames));
492+
} else {
493+
return pcmd->actor(request);
494+
}
450495
} catch (const std::exception& e) {
451496
throw JSONRPCError(RPC_MISC_ERROR, e.what());
452497
}

src/rpc/server.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ class CRPCCommand
138138
std::string name;
139139
rpcfn_type actor;
140140
bool okSafeMode;
141+
std::vector<std::string> argNames;
141142
};
142143

143144
/**

0 commit comments

Comments
 (0)