rpc: Add check for unintended option/parameter name clashes

Also add flag to allow RPC methods that intendionally accept options and
parameters with the same name bypass the check.

Check and flag were suggested by ajtowns
https://github.com/bitcoin/bitcoin/pull/26485#issuecomment-1507916357

Co-authored-by: Anthony Towns <aj@erisian.com.au>
This commit is contained in:
Ryan Ofsky
2023-04-19 15:02:38 -04:00
parent 95d7de0964
commit 2cd28e9fef
4 changed files with 82 additions and 6 deletions

View File

@@ -528,6 +528,53 @@ BOOST_AUTO_TEST_CASE(rpc_getblockstats_calculate_percentiles_by_weight)
}
}
// Make sure errors are triggered appropriately if parameters have the same names.
BOOST_AUTO_TEST_CASE(check_dup_param_names)
{
enum ParamType { POSITIONAL, NAMED, NAMED_ONLY };
auto make_rpc = [](std::vector<std::tuple<std::string, ParamType>> param_names) {
std::vector<RPCArg> params;
std::vector<RPCArg> options;
auto push_options = [&] { if (!options.empty()) params.emplace_back(RPCArg{strprintf("options%i", params.size()), RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Optional::OMITTED, "", std::move(options)}); };
for (auto& [param_name, param_type] : param_names) {
if (param_type == POSITIONAL) {
push_options();
params.emplace_back(std::move(param_name), RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "description");
} else {
options.emplace_back(std::move(param_name), RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "description", RPCArgOptions{.also_positional = param_type == NAMED});
}
}
push_options();
return RPCHelpMan{"method_name", "description", params, RPCResults{}, RPCExamples{""}};
};
// No errors if parameter names are unique.
make_rpc({{"p1", POSITIONAL}, {"p2", POSITIONAL}});
make_rpc({{"p1", POSITIONAL}, {"p2", NAMED}});
make_rpc({{"p1", POSITIONAL}, {"p2", NAMED_ONLY}});
make_rpc({{"p1", NAMED}, {"p2", POSITIONAL}});
make_rpc({{"p1", NAMED}, {"p2", NAMED}});
make_rpc({{"p1", NAMED}, {"p2", NAMED_ONLY}});
make_rpc({{"p1", NAMED_ONLY}, {"p2", POSITIONAL}});
make_rpc({{"p1", NAMED_ONLY}, {"p2", NAMED}});
make_rpc({{"p1", NAMED_ONLY}, {"p2", NAMED_ONLY}});
// Error if parameters names are duplicates, unless one parameter is
// positional and the other is named and .also_positional is true.
BOOST_CHECK_THROW(make_rpc({{"p1", POSITIONAL}, {"p1", POSITIONAL}}), NonFatalCheckError);
make_rpc({{"p1", POSITIONAL}, {"p1", NAMED}});
BOOST_CHECK_THROW(make_rpc({{"p1", POSITIONAL}, {"p1", NAMED_ONLY}}), NonFatalCheckError);
make_rpc({{"p1", NAMED}, {"p1", POSITIONAL}});
BOOST_CHECK_THROW(make_rpc({{"p1", NAMED}, {"p1", NAMED}}), NonFatalCheckError);
BOOST_CHECK_THROW(make_rpc({{"p1", NAMED}, {"p1", NAMED_ONLY}}), NonFatalCheckError);
BOOST_CHECK_THROW(make_rpc({{"p1", NAMED_ONLY}, {"p1", POSITIONAL}}), NonFatalCheckError);
BOOST_CHECK_THROW(make_rpc({{"p1", NAMED_ONLY}, {"p1", NAMED}}), NonFatalCheckError);
BOOST_CHECK_THROW(make_rpc({{"p1", NAMED_ONLY}, {"p1", NAMED_ONLY}}), NonFatalCheckError);
// Make sure duplicate aliases are detected too.
BOOST_CHECK_THROW(make_rpc({{"p1", POSITIONAL}, {"p2|p1", NAMED_ONLY}}), NonFatalCheckError);
}
BOOST_AUTO_TEST_CASE(help_example)
{
// test different argument types