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>
@@ -581,7 +582,26 @@ UniValue RPCHelpMan::HandleRequest(const JSONRPCRequest& request) const
581
582
}
582
583
UniValue ret = m_fun (*this , request);
583
584
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
+ }
585
605
}
586
606
return ret;
587
607
}
@@ -860,53 +880,77 @@ void RPCResult::ToSections(Sections& sections, const OuterType outer_type, const
860
880
NONFATAL_UNREACHABLE ();
861
881
}
862
882
863
- bool RPCResult::MatchesType ( const UniValue& result) const
883
+ static const std::optional< UniValue::VType> ExpectedType (RPCResult::Type type)
864
884
{
865
- if (m_skip_type_check) {
866
- return true ;
867
- }
868
- switch (m_type) {
885
+ using Type = RPCResult::Type;
886
+ switch (type) {
869
887
case Type::ELISION:
870
888
case Type::ANY: {
871
- return true ;
889
+ return std::nullopt ;
872
890
}
873
891
case Type::NONE: {
874
- return UniValue::VNULL == result. getType () ;
892
+ return UniValue::VNULL;
875
893
}
876
894
case Type::STR:
877
895
case Type::STR_HEX: {
878
- return UniValue::VSTR == result. getType () ;
896
+ return UniValue::VSTR;
879
897
}
880
898
case Type::NUM:
881
899
case Type::STR_AMOUNT:
882
900
case Type::NUM_TIME: {
883
- return UniValue::VNUM == result. getType () ;
901
+ return UniValue::VNUM;
884
902
}
885
903
case Type::BOOL: {
886
- return UniValue::VBOOL == result. getType () ;
904
+ return UniValue::VBOOL;
887
905
}
888
906
case Type::ARR_FIXED:
889
907
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);
891
933
for (size_t i{0 }; i < result.get_array ().size (); ++i) {
892
934
// If there are more results than documented, re-use the last doc_inner.
893
935
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);
895
938
}
896
- return true ; // empty result array is valid
939
+ if (errors.empty ()) return true ; // empty result array is valid
940
+ return errors;
897
941
}
898
- case Type::OBJ_DYN:
899
- case Type::OBJ: {
900
- if (UniValue::VOBJ != result.getType ()) return false ;
942
+
943
+ if (UniValue::VOBJ == result.getType ()) {
901
944
if (!m_inner.empty () && m_inner.at (0 ).m_type == Type::ELISION) return true ;
945
+ UniValue errors (UniValue::VOBJ);
902
946
if (m_type == Type::OBJ_DYN) {
903
947
const RPCResult& doc_inner{m_inner.at (0 )}; // Assume all types are the same, randomly pick the first
904
948
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);
908
951
}
909
- return true ; // empty result obj is valid
952
+ if (errors.empty ()) return true ; // empty result obj is valid
953
+ return errors;
910
954
}
911
955
std::set<std::string> doc_keys;
912
956
for (const auto & doc_entry : m_inner) {
@@ -916,26 +960,26 @@ bool RPCResult::MatchesType(const UniValue& result) const
916
960
result.getObjMap (result_obj);
917
961
for (const auto & result_entry : result_obj) {
918
962
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 " );
920
964
}
921
965
}
922
966
923
967
for (const auto & doc_entry : m_inner) {
924
968
const auto result_it{result_obj.find (doc_entry.m_key_name )};
925
969
if (result_it == result_obj.end ()) {
926
970
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 " );
928
972
}
929
973
continue ;
930
974
}
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);
934
977
}
935
- return true ;
978
+ if (errors.empty ()) return true ;
979
+ return errors;
936
980
}
937
- } // no default case, so the compiler can warn about missing cases
938
- NONFATAL_UNREACHABLE () ;
981
+
982
+ return true ;
939
983
}
940
984
941
985
void RPCResult::CheckInnerDoc () const
0 commit comments