Skip to content

Commit 50ee95f

Browse files
MarcoFalkevijaydasmp
authored andcommitted
Merge bitcoin#26887: RPC: make RPCResult::MatchesType return useful errors
3d1a4d8 RPC: make RPCResult::MatchesType return useful errors (Anthony Towns) Pull request description: Currently if you don't correctly update the description of the return value for an RPC call, you essentially just get an assertion failure with no useful information; this generates a description of the problems instead. ACKs for top commit: MarcoFalke: re-ACK 3d1a4d8 🌷 Tree-SHA512: cf0580b7046faab0128672a74f8cc5a1655dfdca6646a2e38b51f0fb5f672c98aad6cd4c5769454a2d644a67da639ccb1c8ff5d24d3d6b4446a082398a643722
1 parent 4bcc5e4 commit 50ee95f

2 files changed

Lines changed: 77 additions & 31 deletions

File tree

src/rpc/util.cpp

Lines changed: 73 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
44

55
#include <chainparamsbase.h>
6+
#include <clientversion.h>
67
#include <consensus/amount.h>
78
#include <key_io.h>
89
#include <outputtype.h>
@@ -527,7 +528,26 @@ UniValue RPCHelpMan::HandleRequest(const JSONRPCRequest& request) const
527528
}
528529
UniValue ret = m_fun(*this, request);
529530
if (gArgs.GetBoolArg("-rpcdoccheck", DEFAULT_RPC_DOC_CHECK)) {
530-
CHECK_NONFATAL(std::any_of(m_results.m_results.begin(), m_results.m_results.end(), [&ret](const RPCResult& res) { return res.MatchesType(ret); }));
531+
UniValue mismatch{UniValue::VARR};
532+
for (const auto& res : m_results.m_results) {
533+
UniValue match{res.MatchesType(ret)};
534+
if (match.isTrue()) {
535+
mismatch.setNull();
536+
break;
537+
}
538+
mismatch.push_back(match);
539+
}
540+
if (!mismatch.isNull()) {
541+
std::string explain{
542+
mismatch.empty() ? "no possible results defined" :
543+
mismatch.size() == 1 ? mismatch[0].write(4) :
544+
mismatch.write(4)};
545+
throw std::runtime_error{
546+
strprintf("Internal bug detected: RPC call \"%s\" returned incorrect type:\n%s\n%s %s\nPlease report this issue here: %s\n",
547+
m_name, explain,
548+
PACKAGE_NAME, FormatFullVersion(),
549+
PACKAGE_BUGREPORT)};
550+
}
531551
}
532552
return ret;
533553
}
@@ -804,53 +824,77 @@ void RPCResult::ToSections(Sections& sections, const OuterType outer_type, const
804824
NONFATAL_UNREACHABLE();
805825
}
806826

807-
bool RPCResult::MatchesType(const UniValue& result) const
827+
static const std::optional<UniValue::VType> ExpectedType(RPCResult::Type type)
808828
{
809-
if (m_skip_type_check) {
810-
return true;
811-
}
812-
switch (m_type) {
829+
using Type = RPCResult::Type;
830+
switch (type) {
813831
case Type::ELISION:
814832
case Type::ANY: {
815-
return true;
833+
return std::nullopt;
816834
}
817835
case Type::NONE: {
818-
return UniValue::VNULL == result.getType();
836+
return UniValue::VNULL;
819837
}
820838
case Type::STR:
821839
case Type::STR_HEX: {
822-
return UniValue::VSTR == result.getType();
840+
return UniValue::VSTR;
823841
}
824842
case Type::NUM:
825843
case Type::STR_AMOUNT:
826844
case Type::NUM_TIME: {
827-
return UniValue::VNUM == result.getType();
845+
return UniValue::VNUM;
828846
}
829847
case Type::BOOL: {
830-
return UniValue::VBOOL == result.getType();
848+
return UniValue::VBOOL;
831849
}
832850
case Type::ARR_FIXED:
833851
case Type::ARR: {
834-
if (UniValue::VARR != result.getType()) return false;
852+
return UniValue::VARR;
853+
}
854+
case Type::OBJ_DYN:
855+
case Type::OBJ: {
856+
return UniValue::VOBJ;
857+
}
858+
} // no default case, so the compiler can warn about missing cases
859+
NONFATAL_UNREACHABLE();
860+
}
861+
862+
UniValue RPCResult::MatchesType(const UniValue& result) const
863+
{
864+
if (m_skip_type_check) {
865+
return true;
866+
}
867+
868+
const auto exp_type = ExpectedType(m_type);
869+
if (!exp_type) return true; // can be any type, so nothing to check
870+
871+
if (*exp_type != result.getType()) {
872+
return strprintf("returned type is %s, but declared as %s in doc", uvTypeName(result.getType()), uvTypeName(*exp_type));
873+
}
874+
875+
if (UniValue::VARR == result.getType()) {
876+
UniValue errors(UniValue::VOBJ);
835877
for (size_t i{0}; i < result.get_array().size(); ++i) {
836878
// If there are more results than documented, re-use the last doc_inner.
837879
const RPCResult& doc_inner{m_inner.at(std::min(m_inner.size() - 1, i))};
838-
if (!doc_inner.MatchesType(result.get_array()[i])) return false;
880+
UniValue match{doc_inner.MatchesType(result.get_array()[i])};
881+
if (!match.isTrue()) errors.pushKV(strprintf("%d", i), match);
839882
}
840-
return true; // empty result array is valid
883+
if (errors.empty()) return true; // empty result array is valid
884+
return errors;
841885
}
842-
case Type::OBJ_DYN:
843-
case Type::OBJ: {
844-
if (UniValue::VOBJ != result.getType()) return false;
886+
887+
if (UniValue::VOBJ == result.getType()) {
845888
if (!m_inner.empty() && m_inner.at(0).m_type == Type::ELISION) return true;
889+
UniValue errors(UniValue::VOBJ);
846890
if (m_type == Type::OBJ_DYN) {
847891
const RPCResult& doc_inner{m_inner.at(0)}; // Assume all types are the same, randomly pick the first
848892
for (size_t i{0}; i < result.get_obj().size(); ++i) {
849-
if (!doc_inner.MatchesType(result.get_obj()[i])) {
850-
return false;
851-
}
893+
UniValue match{doc_inner.MatchesType(result.get_obj()[i])};
894+
if (!match.isTrue()) errors.pushKV(result.getKeys()[i], match);
852895
}
853-
return true; // empty result obj is valid
896+
if (errors.empty()) return true; // empty result obj is valid
897+
return errors;
854898
}
855899
std::set<std::string> doc_keys;
856900
for (const auto& doc_entry : m_inner) {
@@ -860,26 +904,26 @@ bool RPCResult::MatchesType(const UniValue& result) const
860904
result.getObjMap(result_obj);
861905
for (const auto& result_entry : result_obj) {
862906
if (doc_keys.find(result_entry.first) == doc_keys.end()) {
863-
return false; // missing documentation
907+
errors.pushKV(result_entry.first, "key returned that was not in doc");
864908
}
865909
}
866910

867911
for (const auto& doc_entry : m_inner) {
868912
const auto result_it{result_obj.find(doc_entry.m_key_name)};
869913
if (result_it == result_obj.end()) {
870914
if (!doc_entry.m_optional) {
871-
return false; // result is missing a required key
915+
errors.pushKV(doc_entry.m_key_name, "key missing, despite not being optional in doc");
872916
}
873917
continue;
874918
}
875-
if (!doc_entry.MatchesType(result_it->second)) {
876-
return false; // wrong type
877-
}
919+
UniValue match{doc_entry.MatchesType(result_it->second)};
920+
if (!match.isTrue()) errors.pushKV(doc_entry.m_key_name, match);
878921
}
879-
return true;
922+
if (errors.empty()) return true;
923+
return errors;
880924
}
881-
} // no default case, so the compiler can warn about missing cases
882-
NONFATAL_UNREACHABLE();
925+
926+
return true;
883927
}
884928

885929
void RPCResult::CheckInnerDoc() const

src/rpc/util.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -326,8 +326,10 @@ struct RPCResult {
326326
std::string ToStringObj() const;
327327
/** Return the description string, including the result type. */
328328
std::string ToDescriptionString() const;
329-
/** Check whether the result JSON type matches. */
330-
bool MatchesType(const UniValue& result) const;
329+
/** Check whether the result JSON type matches.
330+
* Returns true if type matches, or object describing error(s) if not.
331+
*/
332+
UniValue MatchesType(const UniValue& result) const;
331333

332334
private:
333335
void CheckInnerDoc() const;

0 commit comments

Comments
 (0)