2
2
// Distributed under the MIT software license, see the accompanying
3
3
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
4
5
+ #include < clientversion.h>
5
6
#include < consensus/amount.h>
6
7
#include < key_io.h>
7
8
#include < outputtype.h>
@@ -568,7 +569,26 @@ UniValue RPCHelpMan::HandleRequest(const JSONRPCRequest& request) const
568
569
}
569
570
UniValue ret = m_fun (*this , request);
570
571
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
+ }
572
592
}
573
593
return ret;
574
594
}
@@ -883,53 +903,77 @@ void RPCResult::ToSections(Sections& sections, const OuterType outer_type, const
883
903
NONFATAL_UNREACHABLE ();
884
904
}
885
905
886
- bool RPCResult::MatchesType ( const UniValue& result) const
906
+ static const std::optional< UniValue::VType> ExpectedType (RPCResult::Type type)
887
907
{
888
- if (m_skip_type_check) {
889
- return true ;
890
- }
891
- switch (m_type) {
908
+ using Type = RPCResult::Type;
909
+ switch (type) {
892
910
case Type::ELISION:
893
911
case Type::ANY: {
894
- return true ;
912
+ return std::nullopt ;
895
913
}
896
914
case Type::NONE: {
897
- return UniValue::VNULL == result. getType () ;
915
+ return UniValue::VNULL;
898
916
}
899
917
case Type::STR:
900
918
case Type::STR_HEX: {
901
- return UniValue::VSTR == result. getType () ;
919
+ return UniValue::VSTR;
902
920
}
903
921
case Type::NUM:
904
922
case Type::STR_AMOUNT:
905
923
case Type::NUM_TIME: {
906
- return UniValue::VNUM == result. getType () ;
924
+ return UniValue::VNUM;
907
925
}
908
926
case Type::BOOL: {
909
- return UniValue::VBOOL == result. getType () ;
927
+ return UniValue::VBOOL;
910
928
}
911
929
case Type::ARR_FIXED:
912
930
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);
914
956
for (size_t i{0 }; i < result.get_array ().size (); ++i) {
915
957
// If there are more results than documented, re-use the last doc_inner.
916
958
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);
918
961
}
919
- return true ; // empty result array is valid
962
+ if (errors.empty ()) return true ; // empty result array is valid
963
+ return errors;
920
964
}
921
- case Type::OBJ_DYN:
922
- case Type::OBJ: {
923
- if (UniValue::VOBJ != result.getType ()) return false ;
965
+
966
+ if (UniValue::VOBJ == result.getType ()) {
924
967
if (!m_inner.empty () && m_inner.at (0 ).m_type == Type::ELISION) return true ;
968
+ UniValue errors (UniValue::VOBJ);
925
969
if (m_type == Type::OBJ_DYN) {
926
970
const RPCResult& doc_inner{m_inner.at (0 )}; // Assume all types are the same, randomly pick the first
927
971
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);
931
974
}
932
- return true ; // empty result obj is valid
975
+ if (errors.empty ()) return true ; // empty result obj is valid
976
+ return errors;
933
977
}
934
978
std::set<std::string> doc_keys;
935
979
for (const auto & doc_entry : m_inner) {
@@ -939,26 +983,26 @@ bool RPCResult::MatchesType(const UniValue& result) const
939
983
result.getObjMap (result_obj);
940
984
for (const auto & result_entry : result_obj) {
941
985
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 " );
943
987
}
944
988
}
945
989
946
990
for (const auto & doc_entry : m_inner) {
947
991
const auto result_it{result_obj.find (doc_entry.m_key_name )};
948
992
if (result_it == result_obj.end ()) {
949
993
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 " );
951
995
}
952
996
continue ;
953
997
}
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);
957
1000
}
958
- return true ;
1001
+ if (errors.empty ()) return true ;
1002
+ return errors;
959
1003
}
960
- } // no default case, so the compiler can warn about missing cases
961
- NONFATAL_UNREACHABLE () ;
1004
+
1005
+ return true ;
962
1006
}
963
1007
964
1008
void RPCResult::CheckInnerDoc () const
0 commit comments