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\n Please 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
885929void RPCResult::CheckInnerDoc () const
0 commit comments