22// Distributed under the MIT software license, see the accompanying
33// file COPYING or http://www.opensource.org/licenses/mit-license.php.
44
5+ #include < clientversion.h>
56#include < consensus/amount.h>
67#include < key_io.h>
78#include < outputtype.h>
@@ -568,7 +569,26 @@ UniValue RPCHelpMan::HandleRequest(const JSONRPCRequest& request) const
568569 }
569570 UniValue ret = m_fun (*this , request);
570571 if (gArgs .GetBoolArg (" -rpcdoccheck" , DEFAULT_RPC_DOC_CHECK)) {
571- CHECK_NONFATAL (std::any_of (m_results.m_results .begin (), m_results.m_results .end (), [&ret](const RPCResult& res) { return res.MatchesType (ret); }));
572+ UniValue mismatch{UniValue::VARR};
573+ for (const auto & res : m_results.m_results ) {
574+ UniValue match{res.MatchesType (ret)};
575+ if (match.isTrue ()) {
576+ mismatch.setNull ();
577+ break ;
578+ }
579+ mismatch.push_back (match);
580+ }
581+ if (!mismatch.isNull ()) {
582+ std::string explain{
583+ mismatch.empty () ? " no possible results defined" :
584+ mismatch.size () == 1 ? mismatch[0 ].write (4 ) :
585+ mismatch.write (4 )};
586+ throw std::runtime_error{
587+ strprintf (" Internal bug detected: RPC call \" %s\" returned incorrect type:\n %s\n %s %s\n Please report this issue here: %s\n " ,
588+ m_name, explain,
589+ PACKAGE_NAME, FormatFullVersion (),
590+ PACKAGE_BUGREPORT)};
591+ }
572592 }
573593 return ret;
574594}
@@ -883,53 +903,77 @@ void RPCResult::ToSections(Sections& sections, const OuterType outer_type, const
883903 NONFATAL_UNREACHABLE ();
884904}
885905
886- bool RPCResult::MatchesType ( const UniValue& result) const
906+ static const std::optional< UniValue::VType> ExpectedType (RPCResult::Type type)
887907{
888- if (m_skip_type_check) {
889- return true ;
890- }
891- switch (m_type) {
908+ using Type = RPCResult::Type;
909+ switch (type) {
892910 case Type::ELISION:
893911 case Type::ANY: {
894- return true ;
912+ return std:: nullopt ;
895913 }
896914 case Type::NONE: {
897- return UniValue::VNULL == result. getType () ;
915+ return UniValue::VNULL;
898916 }
899917 case Type::STR:
900918 case Type::STR_HEX: {
901- return UniValue::VSTR == result. getType () ;
919+ return UniValue::VSTR;
902920 }
903921 case Type::NUM:
904922 case Type::STR_AMOUNT:
905923 case Type::NUM_TIME: {
906- return UniValue::VNUM == result. getType () ;
924+ return UniValue::VNUM;
907925 }
908926 case Type::BOOL: {
909- return UniValue::VBOOL == result. getType () ;
927+ return UniValue::VBOOL;
910928 }
911929 case Type::ARR_FIXED:
912930 case Type::ARR: {
913- if (UniValue::VARR != result.getType ()) return false ;
931+ return UniValue::VARR;
932+ }
933+ case Type::OBJ_DYN:
934+ case Type::OBJ: {
935+ return UniValue::VOBJ;
936+ }
937+ } // no default case, so the compiler can warn about missing cases
938+ NONFATAL_UNREACHABLE ();
939+ }
940+
941+ UniValue RPCResult::MatchesType (const UniValue& result) const
942+ {
943+ if (m_skip_type_check) {
944+ return true ;
945+ }
946+
947+ const auto exp_type = ExpectedType (m_type);
948+ if (!exp_type) return true ; // can be any type, so nothing to check
949+
950+ if (*exp_type != result.getType ()) {
951+ return strprintf (" returned type is %s, but declared as %s in doc" , uvTypeName (result.getType ()), uvTypeName (*exp_type));
952+ }
953+
954+ if (UniValue::VARR == result.getType ()) {
955+ UniValue errors (UniValue::VOBJ);
914956 for (size_t i{0 }; i < result.get_array ().size (); ++i) {
915957 // If there are more results than documented, re-use the last doc_inner.
916958 const RPCResult& doc_inner{m_inner.at (std::min (m_inner.size () - 1 , i))};
917- if (!doc_inner.MatchesType (result.get_array ()[i])) return false ;
959+ UniValue match{doc_inner.MatchesType (result.get_array ()[i])};
960+ if (!match.isTrue ()) errors.pushKV (strprintf (" %d" , i), match);
918961 }
919- return true ; // empty result array is valid
962+ if (errors.empty ()) return true ; // empty result array is valid
963+ return errors;
920964 }
921- case Type::OBJ_DYN:
922- case Type::OBJ: {
923- if (UniValue::VOBJ != result.getType ()) return false ;
965+
966+ if (UniValue::VOBJ == result.getType ()) {
924967 if (!m_inner.empty () && m_inner.at (0 ).m_type == Type::ELISION) return true ;
968+ UniValue errors (UniValue::VOBJ);
925969 if (m_type == Type::OBJ_DYN) {
926970 const RPCResult& doc_inner{m_inner.at (0 )}; // Assume all types are the same, randomly pick the first
927971 for (size_t i{0 }; i < result.get_obj ().size (); ++i) {
928- if (!doc_inner.MatchesType (result.get_obj ()[i])) {
929- return false ;
930- }
972+ UniValue match{doc_inner.MatchesType (result.get_obj ()[i])};
973+ if (!match.isTrue ()) errors.pushKV (result.getKeys ()[i], match);
931974 }
932- return true ; // empty result obj is valid
975+ if (errors.empty ()) return true ; // empty result obj is valid
976+ return errors;
933977 }
934978 std::set<std::string> doc_keys;
935979 for (const auto & doc_entry : m_inner) {
@@ -939,26 +983,26 @@ bool RPCResult::MatchesType(const UniValue& result) const
939983 result.getObjMap (result_obj);
940984 for (const auto & result_entry : result_obj) {
941985 if (doc_keys.find (result_entry.first ) == doc_keys.end ()) {
942- return false ; // missing documentation
986+ errors. pushKV (result_entry. first , " key returned that was not in doc " );
943987 }
944988 }
945989
946990 for (const auto & doc_entry : m_inner) {
947991 const auto result_it{result_obj.find (doc_entry.m_key_name )};
948992 if (result_it == result_obj.end ()) {
949993 if (!doc_entry.m_optional ) {
950- return false ; // result is missing a required key
994+ errors. pushKV (doc_entry. m_key_name , " key missing, despite not being optional in doc " );
951995 }
952996 continue ;
953997 }
954- if (!doc_entry.MatchesType (result_it->second )) {
955- return false ; // wrong type
956- }
998+ UniValue match{doc_entry.MatchesType (result_it->second )};
999+ if (!match.isTrue ()) errors.pushKV (doc_entry.m_key_name , match);
9571000 }
958- return true ;
1001+ if (errors.empty ()) return true ;
1002+ return errors;
9591003 }
960- } // no default case, so the compiler can warn about missing cases
961- NONFATAL_UNREACHABLE () ;
1004+
1005+ return true ;
9621006}
9631007
9641008void RPCResult::CheckInnerDoc () const
0 commit comments