mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-04-29 14:59:39 -04:00
RPC: Add add OBJ_NAMED_PARAMS type
OBJ_NAMED_PARAMS type works the same as OBJ type except it registers the object keys to be accepted as top-level named-only RPC parameters. Generated documentation also lists the object keys seperately in a new "Named arguments" section of help text. Named-only RPC parameters have the same semantics as python keyword-only arguments (https://peps.python.org/pep-3102/). They are always required to be passed by name, so they don't affect interpretation of positional arguments, and aren't affected when positional arguments are added or removed. The new OBJ_NAMED_PARAMS type is used in the next commit to make it easier to pass options values to various RPC methods. Co-authored-by: Andrew Chow <github@achow101.com>
This commit is contained in:
parent
1d7f1ada48
commit
702b56d2a8
5 changed files with 133 additions and 29 deletions
|
@ -392,7 +392,7 @@ std::string JSONRPCExecBatch(const JSONRPCRequest& jreq, const UniValue& vReq)
|
||||||
* Process named arguments into a vector of positional arguments, based on the
|
* Process named arguments into a vector of positional arguments, based on the
|
||||||
* passed-in specification for the RPC call's arguments.
|
* passed-in specification for the RPC call's arguments.
|
||||||
*/
|
*/
|
||||||
static inline JSONRPCRequest transformNamedArguments(const JSONRPCRequest& in, const std::vector<std::string>& argNames)
|
static inline JSONRPCRequest transformNamedArguments(const JSONRPCRequest& in, const std::vector<std::pair<std::string, bool>>& argNames)
|
||||||
{
|
{
|
||||||
JSONRPCRequest out = in;
|
JSONRPCRequest out = in;
|
||||||
out.params = UniValue(UniValue::VARR);
|
out.params = UniValue(UniValue::VARR);
|
||||||
|
@ -417,7 +417,9 @@ static inline JSONRPCRequest transformNamedArguments(const JSONRPCRequest& in, c
|
||||||
// "args" parameter, if present.
|
// "args" parameter, if present.
|
||||||
int hole = 0;
|
int hole = 0;
|
||||||
int initial_hole_size = 0;
|
int initial_hole_size = 0;
|
||||||
for (const std::string &argNamePattern: argNames) {
|
const std::string* initial_param = nullptr;
|
||||||
|
UniValue options{UniValue::VOBJ};
|
||||||
|
for (const auto& [argNamePattern, named_only]: argNames) {
|
||||||
std::vector<std::string> vargNames = SplitString(argNamePattern, '|');
|
std::vector<std::string> vargNames = SplitString(argNamePattern, '|');
|
||||||
auto fr = argsIn.end();
|
auto fr = argsIn.end();
|
||||||
for (const std::string & argName : vargNames) {
|
for (const std::string & argName : vargNames) {
|
||||||
|
@ -426,7 +428,22 @@ static inline JSONRPCRequest transformNamedArguments(const JSONRPCRequest& in, c
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (fr != argsIn.end()) {
|
|
||||||
|
// Handle named-only parameters by pushing them into a temporary options
|
||||||
|
// object, and then pushing the accumulated options as the next
|
||||||
|
// positional argument.
|
||||||
|
if (named_only) {
|
||||||
|
if (fr != argsIn.end()) {
|
||||||
|
if (options.exists(fr->first)) {
|
||||||
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Parameter " + fr->first + " specified multiple times");
|
||||||
|
}
|
||||||
|
options.__pushKV(fr->first, *fr->second);
|
||||||
|
argsIn.erase(fr);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!options.empty() || fr != argsIn.end()) {
|
||||||
for (int i = 0; i < hole; ++i) {
|
for (int i = 0; i < hole; ++i) {
|
||||||
// Fill hole between specified parameters with JSON nulls,
|
// Fill hole between specified parameters with JSON nulls,
|
||||||
// but not at the end (for backwards compatibility with calls
|
// but not at the end (for backwards compatibility with calls
|
||||||
|
@ -434,12 +451,26 @@ static inline JSONRPCRequest transformNamedArguments(const JSONRPCRequest& in, c
|
||||||
out.params.push_back(UniValue());
|
out.params.push_back(UniValue());
|
||||||
}
|
}
|
||||||
hole = 0;
|
hole = 0;
|
||||||
out.params.push_back(*fr->second);
|
if (!initial_param) initial_param = &argNamePattern;
|
||||||
argsIn.erase(fr);
|
|
||||||
} else {
|
} else {
|
||||||
hole += 1;
|
hole += 1;
|
||||||
if (out.params.empty()) initial_hole_size = hole;
|
if (out.params.empty()) initial_hole_size = hole;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If named input parameter "fr" is present, push it onto out.params. If
|
||||||
|
// options are present, push them onto out.params. If both are present,
|
||||||
|
// throw an error.
|
||||||
|
if (fr != argsIn.end()) {
|
||||||
|
if (!options.empty()) {
|
||||||
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Parameter " + fr->first + " conflicts with parameter " + options.getKeys().front());
|
||||||
|
}
|
||||||
|
out.params.push_back(*fr->second);
|
||||||
|
argsIn.erase(fr);
|
||||||
|
}
|
||||||
|
if (!options.empty()) {
|
||||||
|
out.params.push_back(std::move(options));
|
||||||
|
options = UniValue{UniValue::VOBJ};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// If leftover "args" param was found, use it as a source of positional
|
// If leftover "args" param was found, use it as a source of positional
|
||||||
// arguments and add named arguments after. This is a convenience for
|
// arguments and add named arguments after. This is a convenience for
|
||||||
|
@ -447,9 +478,8 @@ static inline JSONRPCRequest transformNamedArguments(const JSONRPCRequest& in, c
|
||||||
// arguments as described in doc/JSON-RPC-interface.md#parameter-passing
|
// arguments as described in doc/JSON-RPC-interface.md#parameter-passing
|
||||||
auto positional_args{argsIn.extract("args")};
|
auto positional_args{argsIn.extract("args")};
|
||||||
if (positional_args && positional_args.mapped()->isArray()) {
|
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() && initial_param) {
|
||||||
if (initial_hole_size < (int)positional_args.mapped()->size() && has_named_arguments) {
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Parameter " + *initial_param + " specified twice both as positional and named argument");
|
||||||
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.
|
// Assign positional_args to out.params and append named_args after.
|
||||||
UniValue named_args{std::move(out.params)};
|
UniValue named_args{std::move(out.params)};
|
||||||
|
|
|
@ -95,7 +95,7 @@ public:
|
||||||
using Actor = std::function<bool(const JSONRPCRequest& request, UniValue& result, bool last_handler)>;
|
using Actor = std::function<bool(const JSONRPCRequest& request, UniValue& result, bool last_handler)>;
|
||||||
|
|
||||||
//! Constructor taking Actor callback supporting multiple handlers.
|
//! Constructor taking Actor callback supporting multiple handlers.
|
||||||
CRPCCommand(std::string category, std::string name, Actor actor, std::vector<std::string> args, intptr_t unique_id)
|
CRPCCommand(std::string category, std::string name, Actor actor, std::vector<std::pair<std::string, bool>> args, intptr_t unique_id)
|
||||||
: category(std::move(category)), name(std::move(name)), actor(std::move(actor)), argNames(std::move(args)),
|
: category(std::move(category)), name(std::move(name)), actor(std::move(actor)), argNames(std::move(args)),
|
||||||
unique_id(unique_id)
|
unique_id(unique_id)
|
||||||
{
|
{
|
||||||
|
@ -115,7 +115,16 @@ public:
|
||||||
std::string category;
|
std::string category;
|
||||||
std::string name;
|
std::string name;
|
||||||
Actor actor;
|
Actor actor;
|
||||||
std::vector<std::string> argNames;
|
//! List of method arguments and whether they are named-only. Incoming RPC
|
||||||
|
//! requests contain a "params" field that can either be an array containing
|
||||||
|
//! unnamed arguments or an object containing named arguments. The
|
||||||
|
//! "argNames" vector is used in the latter case to transform the params
|
||||||
|
//! object into an array. Each argument in "argNames" gets mapped to a
|
||||||
|
//! unique position in the array, based on the order it is listed, unless
|
||||||
|
//! the argument is a named-only argument with argNames[x].second set to
|
||||||
|
//! true. Named-only arguments are combined into a JSON object that is
|
||||||
|
//! appended after other arguments, see transformNamedArguments for details.
|
||||||
|
std::vector<std::pair<std::string, bool>> argNames;
|
||||||
intptr_t unique_id;
|
intptr_t unique_id;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -389,7 +389,8 @@ struct Sections {
|
||||||
case RPCArg::Type::NUM:
|
case RPCArg::Type::NUM:
|
||||||
case RPCArg::Type::AMOUNT:
|
case RPCArg::Type::AMOUNT:
|
||||||
case RPCArg::Type::RANGE:
|
case RPCArg::Type::RANGE:
|
||||||
case RPCArg::Type::BOOL: {
|
case RPCArg::Type::BOOL:
|
||||||
|
case RPCArg::Type::OBJ_NAMED_PARAMS: {
|
||||||
if (is_top_level_arg) return; // Nothing more to do for non-recursive types on first recursion
|
if (is_top_level_arg) return; // Nothing more to do for non-recursive types on first recursion
|
||||||
auto left = indent;
|
auto left = indent;
|
||||||
if (arg.m_opts.type_str.size() != 0 && push_name) {
|
if (arg.m_opts.type_str.size() != 0 && push_name) {
|
||||||
|
@ -605,12 +606,17 @@ bool RPCHelpMan::IsValidNumArgs(size_t num_args) const
|
||||||
return num_required_args <= num_args && num_args <= m_args.size();
|
return num_required_args <= num_args && num_args <= m_args.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::string> RPCHelpMan::GetArgNames() const
|
std::vector<std::pair<std::string, bool>> RPCHelpMan::GetArgNames() const
|
||||||
{
|
{
|
||||||
std::vector<std::string> ret;
|
std::vector<std::pair<std::string, bool>> ret;
|
||||||
ret.reserve(m_args.size());
|
ret.reserve(m_args.size());
|
||||||
for (const auto& arg : m_args) {
|
for (const auto& arg : m_args) {
|
||||||
ret.emplace_back(arg.m_names);
|
if (arg.m_type == RPCArg::Type::OBJ_NAMED_PARAMS) {
|
||||||
|
for (const auto& inner : arg.m_inner) {
|
||||||
|
ret.emplace_back(inner.m_names, /*named_only=*/true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ret.emplace_back(arg.m_names, /*named_only=*/false);
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@ -642,20 +648,31 @@ std::string RPCHelpMan::ToString() const
|
||||||
|
|
||||||
// Arguments
|
// Arguments
|
||||||
Sections sections;
|
Sections sections;
|
||||||
|
Sections named_only_sections;
|
||||||
for (size_t i{0}; i < m_args.size(); ++i) {
|
for (size_t i{0}; i < m_args.size(); ++i) {
|
||||||
const auto& arg = m_args.at(i);
|
const auto& arg = m_args.at(i);
|
||||||
if (arg.m_opts.hidden) break; // Any arg that follows is also hidden
|
if (arg.m_opts.hidden) break; // Any arg that follows is also hidden
|
||||||
|
|
||||||
if (i == 0) ret += "\nArguments:\n";
|
|
||||||
|
|
||||||
// Push named argument name and description
|
// Push named argument name and description
|
||||||
sections.m_sections.emplace_back(::ToString(i + 1) + ". " + arg.GetFirstName(), arg.ToDescriptionString(/*is_named_arg=*/true));
|
sections.m_sections.emplace_back(::ToString(i + 1) + ". " + arg.GetFirstName(), arg.ToDescriptionString(/*is_named_arg=*/true));
|
||||||
sections.m_max_pad = std::max(sections.m_max_pad, sections.m_sections.back().m_left.size());
|
sections.m_max_pad = std::max(sections.m_max_pad, sections.m_sections.back().m_left.size());
|
||||||
|
|
||||||
// Recursively push nested args
|
// Recursively push nested args
|
||||||
sections.Push(arg);
|
sections.Push(arg);
|
||||||
|
|
||||||
|
// Push named-only argument sections
|
||||||
|
if (arg.m_type == RPCArg::Type::OBJ_NAMED_PARAMS) {
|
||||||
|
for (const auto& arg_inner : arg.m_inner) {
|
||||||
|
named_only_sections.PushSection({arg_inner.GetFirstName(), arg_inner.ToDescriptionString(/*is_named_arg=*/true)});
|
||||||
|
named_only_sections.Push(arg_inner);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!sections.m_sections.empty()) ret += "\nArguments:\n";
|
||||||
ret += sections.ToString();
|
ret += sections.ToString();
|
||||||
|
if (!named_only_sections.m_sections.empty()) ret += "\nNamed Arguments:\n";
|
||||||
|
ret += named_only_sections.ToString();
|
||||||
|
|
||||||
// Result
|
// Result
|
||||||
ret += m_results.ToDescriptionString();
|
ret += m_results.ToDescriptionString();
|
||||||
|
@ -669,17 +686,30 @@ std::string RPCHelpMan::ToString() const
|
||||||
UniValue RPCHelpMan::GetArgMap() const
|
UniValue RPCHelpMan::GetArgMap() const
|
||||||
{
|
{
|
||||||
UniValue arr{UniValue::VARR};
|
UniValue arr{UniValue::VARR};
|
||||||
|
|
||||||
|
auto push_back_arg_info = [&arr](const std::string& rpc_name, int pos, const std::string& arg_name, const RPCArg::Type& type) {
|
||||||
|
UniValue map{UniValue::VARR};
|
||||||
|
map.push_back(rpc_name);
|
||||||
|
map.push_back(pos);
|
||||||
|
map.push_back(arg_name);
|
||||||
|
map.push_back(type == RPCArg::Type::STR ||
|
||||||
|
type == RPCArg::Type::STR_HEX);
|
||||||
|
arr.push_back(map);
|
||||||
|
};
|
||||||
|
|
||||||
for (int i{0}; i < int(m_args.size()); ++i) {
|
for (int i{0}; i < int(m_args.size()); ++i) {
|
||||||
const auto& arg = m_args.at(i);
|
const auto& arg = m_args.at(i);
|
||||||
std::vector<std::string> arg_names = SplitString(arg.m_names, '|');
|
std::vector<std::string> arg_names = SplitString(arg.m_names, '|');
|
||||||
for (const auto& arg_name : arg_names) {
|
for (const auto& arg_name : arg_names) {
|
||||||
UniValue map{UniValue::VARR};
|
push_back_arg_info(m_name, i, arg_name, arg.m_type);
|
||||||
map.push_back(m_name);
|
if (arg.m_type == RPCArg::Type::OBJ_NAMED_PARAMS) {
|
||||||
map.push_back(i);
|
for (const auto& inner : arg.m_inner) {
|
||||||
map.push_back(arg_name);
|
std::vector<std::string> inner_names = SplitString(inner.m_names, '|');
|
||||||
map.push_back(arg.m_type == RPCArg::Type::STR ||
|
for (const std::string& inner_name : inner_names) {
|
||||||
arg.m_type == RPCArg::Type::STR_HEX);
|
push_back_arg_info(m_name, i, inner_name, inner.m_type);
|
||||||
arr.push_back(map);
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return arr;
|
return arr;
|
||||||
|
@ -708,6 +738,7 @@ static std::optional<UniValue::VType> ExpectedType(RPCArg::Type type)
|
||||||
return UniValue::VBOOL;
|
return UniValue::VBOOL;
|
||||||
}
|
}
|
||||||
case Type::OBJ:
|
case Type::OBJ:
|
||||||
|
case Type::OBJ_NAMED_PARAMS:
|
||||||
case Type::OBJ_USER_KEYS: {
|
case Type::OBJ_USER_KEYS: {
|
||||||
return UniValue::VOBJ;
|
return UniValue::VOBJ;
|
||||||
}
|
}
|
||||||
|
@ -781,6 +812,7 @@ std::string RPCArg::ToDescriptionString(bool is_named_arg) const
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Type::OBJ:
|
case Type::OBJ:
|
||||||
|
case Type::OBJ_NAMED_PARAMS:
|
||||||
case Type::OBJ_USER_KEYS: {
|
case Type::OBJ_USER_KEYS: {
|
||||||
ret += "json object";
|
ret += "json object";
|
||||||
break;
|
break;
|
||||||
|
@ -809,6 +841,7 @@ std::string RPCArg::ToDescriptionString(bool is_named_arg) const
|
||||||
} // no default case, so the compiler can warn about missing cases
|
} // no default case, so the compiler can warn about missing cases
|
||||||
}
|
}
|
||||||
ret += ")";
|
ret += ")";
|
||||||
|
if (m_type == Type::OBJ_NAMED_PARAMS) ret += " Options object that can be used to pass named arguments, listed below.";
|
||||||
ret += m_description.empty() ? "" : " " + m_description;
|
ret += m_description.empty() ? "" : " " + m_description;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@ -1054,6 +1087,7 @@ std::string RPCArg::ToStringObj(const bool oneline) const
|
||||||
}
|
}
|
||||||
return res + "...]";
|
return res + "...]";
|
||||||
case Type::OBJ:
|
case Type::OBJ:
|
||||||
|
case Type::OBJ_NAMED_PARAMS:
|
||||||
case Type::OBJ_USER_KEYS:
|
case Type::OBJ_USER_KEYS:
|
||||||
// Currently unused, so avoid writing dead code
|
// Currently unused, so avoid writing dead code
|
||||||
NONFATAL_UNREACHABLE();
|
NONFATAL_UNREACHABLE();
|
||||||
|
@ -1077,6 +1111,7 @@ std::string RPCArg::ToString(const bool oneline) const
|
||||||
return GetFirstName();
|
return GetFirstName();
|
||||||
}
|
}
|
||||||
case Type::OBJ:
|
case Type::OBJ:
|
||||||
|
case Type::OBJ_NAMED_PARAMS:
|
||||||
case Type::OBJ_USER_KEYS: {
|
case Type::OBJ_USER_KEYS: {
|
||||||
const std::string res = Join(m_inner, ",", [&](const RPCArg& i) { return i.ToStringObj(oneline); });
|
const std::string res = Join(m_inner, ",", [&](const RPCArg& i) { return i.ToStringObj(oneline); });
|
||||||
if (m_type == Type::OBJ) {
|
if (m_type == Type::OBJ) {
|
||||||
|
|
|
@ -139,6 +139,13 @@ struct RPCArg {
|
||||||
STR,
|
STR,
|
||||||
NUM,
|
NUM,
|
||||||
BOOL,
|
BOOL,
|
||||||
|
OBJ_NAMED_PARAMS, //!< Special type that behaves almost exactly like
|
||||||
|
//!< OBJ, defining an options object with a list of
|
||||||
|
//!< pre-defined keys. The only difference between OBJ
|
||||||
|
//!< and OBJ_NAMED_PARAMS is that OBJ_NAMED_PARMS
|
||||||
|
//!< also allows the keys to be passed as top-level
|
||||||
|
//!< named parameters, as a more convenient way to pass
|
||||||
|
//!< options to the RPC method without nesting them.
|
||||||
OBJ_USER_KEYS, //!< Special type where the user must set the keys e.g. to define multiple addresses; as opposed to e.g. an options object where the keys are predefined
|
OBJ_USER_KEYS, //!< Special type where the user must set the keys e.g. to define multiple addresses; as opposed to e.g. an options object where the keys are predefined
|
||||||
AMOUNT, //!< Special type representing a floating point amount (can be either NUM or STR)
|
AMOUNT, //!< Special type representing a floating point amount (can be either NUM or STR)
|
||||||
STR_HEX, //!< Special type that is a STR with only hex chars
|
STR_HEX, //!< Special type that is a STR with only hex chars
|
||||||
|
@ -183,7 +190,7 @@ struct RPCArg {
|
||||||
m_description{std::move(description)},
|
m_description{std::move(description)},
|
||||||
m_opts{std::move(opts)}
|
m_opts{std::move(opts)}
|
||||||
{
|
{
|
||||||
CHECK_NONFATAL(type != Type::ARR && type != Type::OBJ && type != Type::OBJ_USER_KEYS);
|
CHECK_NONFATAL(type != Type::ARR && type != Type::OBJ && type != Type::OBJ_NAMED_PARAMS && type != Type::OBJ_USER_KEYS);
|
||||||
}
|
}
|
||||||
|
|
||||||
RPCArg(
|
RPCArg(
|
||||||
|
@ -200,7 +207,7 @@ struct RPCArg {
|
||||||
m_description{std::move(description)},
|
m_description{std::move(description)},
|
||||||
m_opts{std::move(opts)}
|
m_opts{std::move(opts)}
|
||||||
{
|
{
|
||||||
CHECK_NONFATAL(type == Type::ARR || type == Type::OBJ || type == Type::OBJ_USER_KEYS);
|
CHECK_NONFATAL(type == Type::ARR || type == Type::OBJ || type == Type::OBJ_NAMED_PARAMS || type == Type::OBJ_USER_KEYS);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsOptional() const;
|
bool IsOptional() const;
|
||||||
|
@ -369,7 +376,8 @@ public:
|
||||||
UniValue GetArgMap() const;
|
UniValue GetArgMap() const;
|
||||||
/** If the supplied number of args is neither too small nor too high */
|
/** If the supplied number of args is neither too small nor too high */
|
||||||
bool IsValidNumArgs(size_t num_args) const;
|
bool IsValidNumArgs(size_t num_args) const;
|
||||||
std::vector<std::string> GetArgNames() const;
|
//! Return list of arguments and whether they are named-only.
|
||||||
|
std::vector<std::pair<std::string, bool>> GetArgNames() const;
|
||||||
|
|
||||||
const std::string m_name;
|
const std::string m_name;
|
||||||
|
|
||||||
|
|
|
@ -42,11 +42,11 @@ private:
|
||||||
class RPCTestingSetup : public TestingSetup
|
class RPCTestingSetup : public TestingSetup
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
UniValue TransformParams(const UniValue& params, std::vector<std::string> arg_names) const;
|
UniValue TransformParams(const UniValue& params, std::vector<std::pair<std::string, bool>> arg_names) const;
|
||||||
UniValue CallRPC(std::string args);
|
UniValue CallRPC(std::string args);
|
||||||
};
|
};
|
||||||
|
|
||||||
UniValue RPCTestingSetup::TransformParams(const UniValue& params, std::vector<std::string> arg_names) const
|
UniValue RPCTestingSetup::TransformParams(const UniValue& params, std::vector<std::pair<std::string, bool>> arg_names) const
|
||||||
{
|
{
|
||||||
UniValue transformed_params;
|
UniValue transformed_params;
|
||||||
CRPCTable table;
|
CRPCTable table;
|
||||||
|
@ -84,7 +84,7 @@ BOOST_FIXTURE_TEST_SUITE(rpc_tests, RPCTestingSetup)
|
||||||
|
|
||||||
BOOST_AUTO_TEST_CASE(rpc_namedparams)
|
BOOST_AUTO_TEST_CASE(rpc_namedparams)
|
||||||
{
|
{
|
||||||
const std::vector<std::string> arg_names{"arg1", "arg2", "arg3", "arg4", "arg5"};
|
const std::vector<std::pair<std::string, bool>> arg_names{{"arg1", false}, {"arg2", false}, {"arg3", false}, {"arg4", false}, {"arg5", false}};
|
||||||
|
|
||||||
// Make sure named arguments are transformed into positional arguments in correct places separated by nulls
|
// 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]");
|
BOOST_CHECK_EQUAL(TransformParams(JSON(R"({"arg2": 2, "arg4": 4})"), arg_names).write(), "[null,2,null,4]");
|
||||||
|
@ -109,6 +109,28 @@ BOOST_AUTO_TEST_CASE(rpc_namedparams)
|
||||||
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_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_namedonlyparams)
|
||||||
|
{
|
||||||
|
const std::vector<std::pair<std::string, bool>> arg_names{{"arg1", false}, {"arg2", false}, {"opt1", true}, {"opt2", true}, {"options", false}};
|
||||||
|
|
||||||
|
// Make sure optional parameters are really optional.
|
||||||
|
BOOST_CHECK_EQUAL(TransformParams(JSON(R"({"arg1": 1, "arg2": 2})"), arg_names).write(), "[1,2]");
|
||||||
|
|
||||||
|
// Make sure named-only parameters are passed as options.
|
||||||
|
BOOST_CHECK_EQUAL(TransformParams(JSON(R"({"arg1": 1, "arg2": 2, "opt1": 10, "opt2": 20})"), arg_names).write(), R"([1,2,{"opt1":10,"opt2":20}])");
|
||||||
|
|
||||||
|
// Make sure options can be passed directly.
|
||||||
|
BOOST_CHECK_EQUAL(TransformParams(JSON(R"({"arg1": 1, "arg2": 2, "options":{"opt1": 10, "opt2": 20}})"), arg_names).write(), R"([1,2,{"opt1":10,"opt2":20}])");
|
||||||
|
|
||||||
|
// Make sure options and named parameters conflict.
|
||||||
|
BOOST_CHECK_EXCEPTION(TransformParams(JSON(R"({"arg1": 1, "arg2": 2, "opt1": 10, "options":{"opt1": 10}})"), arg_names), UniValue,
|
||||||
|
HasJSON(R"({"code":-8,"message":"Parameter options conflicts with parameter opt1"})"));
|
||||||
|
|
||||||
|
// Make sure options object specified through args array conflicts.
|
||||||
|
BOOST_CHECK_EXCEPTION(TransformParams(JSON(R"({"args": [1, 2, {"opt1": 10}], "opt2": 20})"), arg_names), UniValue,
|
||||||
|
HasJSON(R"({"code":-8,"message":"Parameter options specified twice both as positional and named argument"})"));
|
||||||
|
}
|
||||||
|
|
||||||
BOOST_AUTO_TEST_CASE(rpc_rawparams)
|
BOOST_AUTO_TEST_CASE(rpc_rawparams)
|
||||||
{
|
{
|
||||||
// Test raw transaction API argument handling
|
// Test raw transaction API argument handling
|
||||||
|
|
Loading…
Add table
Reference in a new issue