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:
Ryan Ofsky
2022-11-10 11:54:11 -05:00
parent 1d7f1ada48
commit 702b56d2a8
5 changed files with 133 additions and 29 deletions

View File

@@ -389,7 +389,8 @@ struct Sections {
case RPCArg::Type::NUM:
case RPCArg::Type::AMOUNT:
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
auto left = indent;
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();
}
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());
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;
}
@@ -642,20 +648,31 @@ std::string RPCHelpMan::ToString() const
// Arguments
Sections sections;
Sections named_only_sections;
for (size_t i{0}; i < m_args.size(); ++i) {
const auto& arg = m_args.at(i);
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
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());
// Recursively push nested args
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();
if (!named_only_sections.m_sections.empty()) ret += "\nNamed Arguments:\n";
ret += named_only_sections.ToString();
// Result
ret += m_results.ToDescriptionString();
@@ -669,17 +686,30 @@ std::string RPCHelpMan::ToString() const
UniValue RPCHelpMan::GetArgMap() const
{
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) {
const auto& arg = m_args.at(i);
std::vector<std::string> arg_names = SplitString(arg.m_names, '|');
for (const auto& arg_name : arg_names) {
UniValue map{UniValue::VARR};
map.push_back(m_name);
map.push_back(i);
map.push_back(arg_name);
map.push_back(arg.m_type == RPCArg::Type::STR ||
arg.m_type == RPCArg::Type::STR_HEX);
arr.push_back(map);
push_back_arg_info(m_name, i, arg_name, arg.m_type);
if (arg.m_type == RPCArg::Type::OBJ_NAMED_PARAMS) {
for (const auto& inner : arg.m_inner) {
std::vector<std::string> inner_names = SplitString(inner.m_names, '|');
for (const std::string& inner_name : inner_names) {
push_back_arg_info(m_name, i, inner_name, inner.m_type);
}
}
}
}
}
return arr;
@@ -708,6 +738,7 @@ static std::optional<UniValue::VType> ExpectedType(RPCArg::Type type)
return UniValue::VBOOL;
}
case Type::OBJ:
case Type::OBJ_NAMED_PARAMS:
case Type::OBJ_USER_KEYS: {
return UniValue::VOBJ;
}
@@ -781,6 +812,7 @@ std::string RPCArg::ToDescriptionString(bool is_named_arg) const
break;
}
case Type::OBJ:
case Type::OBJ_NAMED_PARAMS:
case Type::OBJ_USER_KEYS: {
ret += "json object";
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
}
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;
return ret;
}
@@ -1054,6 +1087,7 @@ std::string RPCArg::ToStringObj(const bool oneline) const
}
return res + "...]";
case Type::OBJ:
case Type::OBJ_NAMED_PARAMS:
case Type::OBJ_USER_KEYS:
// Currently unused, so avoid writing dead code
NONFATAL_UNREACHABLE();
@@ -1077,6 +1111,7 @@ std::string RPCArg::ToString(const bool oneline) const
return GetFirstName();
}
case Type::OBJ:
case Type::OBJ_NAMED_PARAMS:
case Type::OBJ_USER_KEYS: {
const std::string res = Join(m_inner, ",", [&](const RPCArg& i) { return i.ToStringObj(oneline); });
if (m_type == Type::OBJ) {