@@ -774,7 +774,7 @@ void RPCResult::ToSections(Sections& sections, const OuterType outer_type, const
774
774
// Elements in a JSON structure (dictionary or array) are separated by a comma
775
775
const std::string maybe_separator{outer_type != OuterType::NONE ? " ," : " " };
776
776
777
- // The key name if recursed into an dictionary
777
+ // The key name if recursed into a dictionary
778
778
const std::string maybe_key{
779
779
outer_type == OuterType::OBJ ?
780
780
" \" " + this ->m_key_name + " \" : " :
@@ -865,10 +865,11 @@ void RPCResult::ToSections(Sections& sections, const OuterType outer_type, const
865
865
866
866
bool RPCResult::MatchesType (const UniValue& result) const
867
867
{
868
- switch (m_type) {
869
- case Type::ELISION: {
870
- return false ;
868
+ if (m_skip_type_check) {
869
+ return true ;
871
870
}
871
+ switch (m_type) {
872
+ case Type::ELISION:
872
873
case Type::ANY: {
873
874
return true ;
874
875
}
@@ -889,11 +890,52 @@ bool RPCResult::MatchesType(const UniValue& result) const
889
890
}
890
891
case Type::ARR_FIXED:
891
892
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
893
900
}
894
901
case Type::OBJ_DYN:
895
902
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 ;
897
939
}
898
940
} // no default case, so the compiler can warn about missing cases
899
941
CHECK_NONFATAL (false );
0 commit comments