@@ -774,7 +774,7 @@ void RPCResult::ToSections(Sections& sections, const OuterType outer_type, const
774774 // Elements in a JSON structure (dictionary or array) are separated by a comma
775775 const std::string maybe_separator{outer_type != OuterType::NONE ? " ," : " " };
776776
777- // The key name if recursed into an dictionary
777+ // The key name if recursed into a dictionary
778778 const std::string maybe_key{
779779 outer_type == OuterType::OBJ ?
780780 " \" " + this ->m_key_name + " \" : " :
@@ -865,10 +865,11 @@ void RPCResult::ToSections(Sections& sections, const OuterType outer_type, const
865865
866866bool RPCResult::MatchesType (const UniValue& result) const
867867{
868- switch (m_type) {
869- case Type::ELISION: {
870- return false ;
868+ if (m_skip_type_check) {
869+ return true ;
871870 }
871+ switch (m_type) {
872+ case Type::ELISION:
872873 case Type::ANY: {
873874 return true ;
874875 }
@@ -889,11 +890,52 @@ bool RPCResult::MatchesType(const UniValue& result) const
889890 }
890891 case Type::ARR_FIXED:
891892 case Type::ARR: {
892- return UniValue::VARR == result.getType ();
893+ if (UniValue::VARR != result.getType ()) return false ;
894+ for (size_t i{0 }; i < result.get_array ().size (); ++i) {
895+ // If there are more results than documented, re-use the last doc_inner.
896+ const RPCResult& doc_inner{m_inner.at (std::min (m_inner.size () - 1 , i))};
897+ if (!doc_inner.MatchesType (result.get_array ()[i])) return false ;
898+ }
899+ return true ; // empty result array is valid
893900 }
894901 case Type::OBJ_DYN:
895902 case Type::OBJ: {
896- return UniValue::VOBJ == result.getType ();
903+ if (UniValue::VOBJ != result.getType ()) return false ;
904+ if (!m_inner.empty () && m_inner.at (0 ).m_type == Type::ELISION) return true ;
905+ if (m_type == Type::OBJ_DYN) {
906+ const RPCResult& doc_inner{m_inner.at (0 )}; // Assume all types are the same, randomly pick the first
907+ for (size_t i{0 }; i < result.get_obj ().size (); ++i) {
908+ if (!doc_inner.MatchesType (result.get_obj ()[i])) {
909+ return false ;
910+ }
911+ }
912+ return true ; // empty result obj is valid
913+ }
914+ std::set<std::string> doc_keys;
915+ for (const auto & doc_entry : m_inner) {
916+ doc_keys.insert (doc_entry.m_key_name );
917+ }
918+ std::map<std::string, UniValue> result_obj;
919+ result.getObjMap (result_obj);
920+ for (const auto & result_entry : result_obj) {
921+ if (doc_keys.find (result_entry.first ) == doc_keys.end ()) {
922+ return false ; // missing documentation
923+ }
924+ }
925+
926+ for (const auto & doc_entry : m_inner) {
927+ const auto result_it{result_obj.find (doc_entry.m_key_name )};
928+ if (result_it == result_obj.end ()) {
929+ if (!doc_entry.m_optional ) {
930+ return false ; // result is missing a required key
931+ }
932+ continue ;
933+ }
934+ if (!doc_entry.MatchesType (result_it->second )) {
935+ return false ; // wrong type
936+ }
937+ }
938+ return true ;
897939 }
898940 } // no default case, so the compiler can warn about missing cases
899941 CHECK_NONFATAL (false );
0 commit comments