diff --git a/src/qt/test/rpcnestedtests.cpp b/src/qt/test/rpcnestedtests.cpp --- a/src/qt/test/rpcnestedtests.cpp +++ b/src/qt/test/rpcnestedtests.cpp @@ -26,7 +26,7 @@ {"arg2", RPCArg::Type::STR, RPCArg::Optional::OMITTED, ""}, {"arg3", RPCArg::Type::STR, RPCArg::Optional::OMITTED, ""}, }, - {}, + RPCResult{RPCResult::Type::ANY, "", ""}, RPCExamples{""}, [](const RPCHelpMan &self, const Config &config, const JSONRPCRequest &request) -> UniValue { diff --git a/src/rpc/abc.cpp b/src/rpc/abc.cpp --- a/src/rpc/abc.cpp +++ b/src/rpc/abc.cpp @@ -18,7 +18,13 @@ "getexcessiveblock", "Return the excessive block size.", {}, - RPCResult{RPCResult::Type::NUM, "", "excessive block size in bytes"}, + RPCResult{RPCResult::Type::OBJ, + "", + "", + { + RPCResult{RPCResult::Type::NUM, "", + "excessive block size in bytes"}, + }}, RPCExamples{HelpExampleCli("getexcessiveblock", "") + HelpExampleRpc("getexcessiveblock", "")}, [&](const RPCHelpMan &self, const Config &config, diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -768,7 +768,7 @@ {"arg9", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, }, - RPCResult{RPCResult::Type::NONE, "", "Returns whatever was passed in"}, + RPCResult{RPCResult::Type::ANY, "", "Returns whatever was passed in"}, RPCExamples{""}, [&](const RPCHelpMan &self, const Config &config, const JSONRPCRequest &request) -> UniValue { diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -182,7 +182,10 @@ {"command", RPCArg::Type::STR, /* default */ "all commands", "The command to get help on"}, }, - RPCResult{RPCResult::Type::STR, "", "The help text"}, + { + RPCResult{RPCResult::Type::STR, "", "The help text"}, + RPCResult{RPCResult::Type::ANY, "", ""}, + }, RPCExamples{""}, [&](const RPCHelpMan &self, const Config &config, const JSONRPCRequest &jsonRequest) -> UniValue { diff --git a/src/rpc/util.h b/src/rpc/util.h --- a/src/rpc/util.h +++ b/src/rpc/util.h @@ -249,6 +249,7 @@ NUM, BOOL, NONE, + ANY, //!< Special type to disable type checks (for testing only) STR_AMOUNT, //!< Special string to represent a floating point amount STR_HEX, //!< Special string with only hex chars OBJ_DYN, //!< Special dictionary with keys that are not literals @@ -305,6 +306,8 @@ std::string ToStringObj() const; /** Return the description string, including the result type. */ std::string ToDescriptionString() const; + /** Check whether the result JSON type matches. */ + bool MatchesType(const UniValue &result) const; }; struct RPCResults { @@ -339,7 +342,8 @@ std::vector args, RPCResults results, RPCExamples examples, RPCMethodImpl fun); - UniValue HandleRequest(const Config &config, const JSONRPCRequest &request); + UniValue HandleRequest(const Config &config, + const JSONRPCRequest &request) const; std::string ToString() const; /** * Return the named args that need to be converted from string to another diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp --- a/src/rpc/util.cpp +++ b/src/rpc/util.cpp @@ -470,7 +470,12 @@ std::string RPCResults::ToDescriptionString() const { std::string result; + for (const auto &r : m_results) { + if (r.m_type == RPCResult::Type::ANY) { + // for testing only + continue; + } if (r.m_cond.empty()) { result += "\nResult:\n"; } else { @@ -488,7 +493,7 @@ } UniValue RPCHelpMan::HandleRequest(const Config &config, - const JSONRPCRequest &request) { + const JSONRPCRequest &request) const { if (request.mode == JSONRPCRequest::GET_ARGS) { return GetArgMap(); } @@ -500,7 +505,11 @@ !IsValidNumArgs(request.params.size())) { throw std::runtime_error(ToString()); } - return m_fun(*this, config, request); + const UniValue ret = m_fun(*this, config, request); + CHECK_NONFATAL(std::any_of( + m_results.m_results.begin(), m_results.m_results.end(), + [ret](const RPCResult &res) { return res.MatchesType(ret); })); + return ret; } bool RPCHelpMan::IsValidNumArgs(size_t num_args) const { @@ -716,6 +725,10 @@ {indent + "..." + maybe_separator, m_description}); return; } + case Type::ANY: { + // Only for testing + CHECK_NONFATAL(false); + } case Type::NONE: { sections.PushSection( {indent + "null" + maybe_separator, Description("json null")}); @@ -795,6 +808,41 @@ CHECK_NONFATAL(false); } +bool RPCResult::MatchesType(const UniValue &result) const { + switch (m_type) { + case Type::ELISION: { + return false; + } + case Type::ANY: { + return true; + } + case Type::NONE: { + return UniValue::VNULL == result.getType(); + } + case Type::STR: + case Type::STR_HEX: { + return UniValue::VSTR == result.getType(); + } + case Type::NUM: + case Type::STR_AMOUNT: + case Type::NUM_TIME: { + return UniValue::VNUM == result.getType(); + } + case Type::BOOL: { + return UniValue::VBOOL == result.getType(); + } + case Type::ARR_FIXED: + case Type::ARR: { + return UniValue::VARR == result.getType(); + } + case Type::OBJ_DYN: + case Type::OBJ: { + return UniValue::VOBJ == result.getType(); + } + } // no default case, so the compiler can warn about missing cases + CHECK_NONFATAL(false); +} + std::string RPCArg::ToStringObj(const bool oneline) const { std::string res; res += "\"";