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>
@@ -581,7 +582,26 @@ UniValue RPCHelpMan::HandleRequest(const JSONRPCRequest& request) const
581582 }
582583 UniValue ret = m_fun (*this , request);
583584 if (gArgs .GetBoolArg (" -rpcdoccheck" , DEFAULT_RPC_DOC_CHECK)) {
584- CHECK_NONFATAL (std::any_of (m_results.m_results .begin (), m_results.m_results .end (), [&ret](const RPCResult& res) { return res.MatchesType (ret); }));
585+ UniValue mismatch{UniValue::VARR};
586+ for (const auto & res : m_results.m_results ) {
587+ UniValue match{res.MatchesType (ret)};
588+ if (match.isTrue ()) {
589+ mismatch.setNull ();
590+ break ;
591+ }
592+ mismatch.push_back (match);
593+ }
594+ if (!mismatch.isNull ()) {
595+ std::string explain{
596+ mismatch.empty () ? " no possible results defined" :
597+ mismatch.size () == 1 ? mismatch[0 ].write (4 ) :
598+ mismatch.write (4 )};
599+ throw std::runtime_error{
600+ strprintf (" Internal bug detected: RPC call \" %s\" returned incorrect type:\n %s\n %s %s\n Please report this issue here: %s\n " ,
601+ m_name, explain,
602+ PACKAGE_NAME, FormatFullVersion (),
603+ PACKAGE_BUGREPORT)};
604+ }
585605 }
586606 return ret;
587607}
@@ -860,53 +880,77 @@ void RPCResult::ToSections(Sections& sections, const OuterType outer_type, const
860880 NONFATAL_UNREACHABLE ();
861881}
862882
863- bool RPCResult::MatchesType ( const UniValue& result) const
883+ static const std::optional< UniValue::VType> ExpectedType (RPCResult::Type type)
864884{
865- if (m_skip_type_check) {
866- return true ;
867- }
868- switch (m_type) {
885+ using Type = RPCResult::Type;
886+ switch (type) {
869887 case Type::ELISION:
870888 case Type::ANY: {
871- return true ;
889+ return std:: nullopt ;
872890 }
873891 case Type::NONE: {
874- return UniValue::VNULL == result. getType () ;
892+ return UniValue::VNULL;
875893 }
876894 case Type::STR:
877895 case Type::STR_HEX: {
878- return UniValue::VSTR == result. getType () ;
896+ return UniValue::VSTR;
879897 }
880898 case Type::NUM:
881899 case Type::STR_AMOUNT:
882900 case Type::NUM_TIME: {
883- return UniValue::VNUM == result. getType () ;
901+ return UniValue::VNUM;
884902 }
885903 case Type::BOOL: {
886- return UniValue::VBOOL == result. getType () ;
904+ return UniValue::VBOOL;
887905 }
888906 case Type::ARR_FIXED:
889907 case Type::ARR: {
890- if (UniValue::VARR != result.getType ()) return false ;
908+ return UniValue::VARR;
909+ }
910+ case Type::OBJ_DYN:
911+ case Type::OBJ: {
912+ return UniValue::VOBJ;
913+ }
914+ } // no default case, so the compiler can warn about missing cases
915+ NONFATAL_UNREACHABLE ();
916+ }
917+
918+ UniValue RPCResult::MatchesType (const UniValue& result) const
919+ {
920+ if (m_skip_type_check) {
921+ return true ;
922+ }
923+
924+ const auto exp_type = ExpectedType (m_type);
925+ if (!exp_type) return true ; // can be any type, so nothing to check
926+
927+ if (*exp_type != result.getType ()) {
928+ return strprintf (" returned type is %s, but declared as %s in doc" , uvTypeName (result.getType ()), uvTypeName (*exp_type));
929+ }
930+
931+ if (UniValue::VARR == result.getType ()) {
932+ UniValue errors (UniValue::VOBJ);
891933 for (size_t i{0 }; i < result.get_array ().size (); ++i) {
892934 // If there are more results than documented, re-use the last doc_inner.
893935 const RPCResult& doc_inner{m_inner.at (std::min (m_inner.size () - 1 , i))};
894- if (!doc_inner.MatchesType (result.get_array ()[i])) return false ;
936+ UniValue match{doc_inner.MatchesType (result.get_array ()[i])};
937+ if (!match.isTrue ()) errors.pushKV (strprintf (" %d" , i), match);
895938 }
896- return true ; // empty result array is valid
939+ if (errors.empty ()) return true ; // empty result array is valid
940+ return errors;
897941 }
898- case Type::OBJ_DYN:
899- case Type::OBJ: {
900- if (UniValue::VOBJ != result.getType ()) return false ;
942+
943+ if (UniValue::VOBJ == result.getType ()) {
901944 if (!m_inner.empty () && m_inner.at (0 ).m_type == Type::ELISION) return true ;
945+ UniValue errors (UniValue::VOBJ);
902946 if (m_type == Type::OBJ_DYN) {
903947 const RPCResult& doc_inner{m_inner.at (0 )}; // Assume all types are the same, randomly pick the first
904948 for (size_t i{0 }; i < result.get_obj ().size (); ++i) {
905- if (!doc_inner.MatchesType (result.get_obj ()[i])) {
906- return false ;
907- }
949+ UniValue match{doc_inner.MatchesType (result.get_obj ()[i])};
950+ if (!match.isTrue ()) errors.pushKV (result.getKeys ()[i], match);
908951 }
909- return true ; // empty result obj is valid
952+ if (errors.empty ()) return true ; // empty result obj is valid
953+ return errors;
910954 }
911955 std::set<std::string> doc_keys;
912956 for (const auto & doc_entry : m_inner) {
@@ -916,26 +960,26 @@ bool RPCResult::MatchesType(const UniValue& result) const
916960 result.getObjMap (result_obj);
917961 for (const auto & result_entry : result_obj) {
918962 if (doc_keys.find (result_entry.first ) == doc_keys.end ()) {
919- return false ; // missing documentation
963+ errors. pushKV (result_entry. first , " key returned that was not in doc " );
920964 }
921965 }
922966
923967 for (const auto & doc_entry : m_inner) {
924968 const auto result_it{result_obj.find (doc_entry.m_key_name )};
925969 if (result_it == result_obj.end ()) {
926970 if (!doc_entry.m_optional ) {
927- return false ; // result is missing a required key
971+ errors. pushKV (doc_entry. m_key_name , " key missing, despite not being optional in doc " );
928972 }
929973 continue ;
930974 }
931- if (!doc_entry.MatchesType (result_it->second )) {
932- return false ; // wrong type
933- }
975+ UniValue match{doc_entry.MatchesType (result_it->second )};
976+ if (!match.isTrue ()) errors.pushKV (doc_entry.m_key_name , match);
934977 }
935- return true ;
978+ if (errors.empty ()) return true ;
979+ return errors;
936980 }
937- } // no default case, so the compiler can warn about missing cases
938- NONFATAL_UNREACHABLE () ;
981+
982+ return true ;
939983}
940984
941985void RPCResult::CheckInnerDoc () const
0 commit comments