|
|
|
@ -774,7 +774,7 @@ void RPCResult::ToSections(Sections& sections, const OuterType outer_type, const
|
|
|
|
|
// Elements in a JSON structure (dictionary or array) are separated by a comma
|
|
|
|
|
const std::string maybe_separator{outer_type != OuterType::NONE ? "," : ""};
|
|
|
|
|
|
|
|
|
|
// The key name if recursed into an dictionary
|
|
|
|
|
// The key name if recursed into a dictionary
|
|
|
|
|
const std::string maybe_key{
|
|
|
|
|
outer_type == OuterType::OBJ ?
|
|
|
|
|
"\"" + this->m_key_name + "\" : " :
|
|
|
|
@ -865,10 +865,11 @@ void RPCResult::ToSections(Sections& sections, const OuterType outer_type, const
|
|
|
|
|
|
|
|
|
|
bool RPCResult::MatchesType(const UniValue& result) const
|
|
|
|
|
{
|
|
|
|
|
switch (m_type) {
|
|
|
|
|
case Type::ELISION: {
|
|
|
|
|
return false;
|
|
|
|
|
if (m_skip_type_check) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
switch (m_type) {
|
|
|
|
|
case Type::ELISION:
|
|
|
|
|
case Type::ANY: {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
@ -889,11 +890,52 @@ bool RPCResult::MatchesType(const UniValue& result) const
|
|
|
|
|
}
|
|
|
|
|
case Type::ARR_FIXED:
|
|
|
|
|
case Type::ARR: {
|
|
|
|
|
return UniValue::VARR == result.getType();
|
|
|
|
|
if (UniValue::VARR != result.getType()) return false;
|
|
|
|
|
for (size_t i{0}; i < result.get_array().size(); ++i) {
|
|
|
|
|
// If there are more results than documented, re-use the last doc_inner.
|
|
|
|
|
const RPCResult& doc_inner{m_inner.at(std::min(m_inner.size() - 1, i))};
|
|
|
|
|
if (!doc_inner.MatchesType(result.get_array()[i])) return false;
|
|
|
|
|
}
|
|
|
|
|
return true; // empty result array is valid
|
|
|
|
|
}
|
|
|
|
|
case Type::OBJ_DYN:
|
|
|
|
|
case Type::OBJ: {
|
|
|
|
|
return UniValue::VOBJ == result.getType();
|
|
|
|
|
if (UniValue::VOBJ != result.getType()) return false;
|
|
|
|
|
if (!m_inner.empty() && m_inner.at(0).m_type == Type::ELISION) return true;
|
|
|
|
|
if (m_type == Type::OBJ_DYN) {
|
|
|
|
|
const RPCResult& doc_inner{m_inner.at(0)}; // Assume all types are the same, randomly pick the first
|
|
|
|
|
for (size_t i{0}; i < result.get_obj().size(); ++i) {
|
|
|
|
|
if (!doc_inner.MatchesType(result.get_obj()[i])) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true; // empty result obj is valid
|
|
|
|
|
}
|
|
|
|
|
std::set<std::string> doc_keys;
|
|
|
|
|
for (const auto& doc_entry : m_inner) {
|
|
|
|
|
doc_keys.insert(doc_entry.m_key_name);
|
|
|
|
|
}
|
|
|
|
|
std::map<std::string, UniValue> result_obj;
|
|
|
|
|
result.getObjMap(result_obj);
|
|
|
|
|
for (const auto& result_entry : result_obj) {
|
|
|
|
|
if (doc_keys.find(result_entry.first) == doc_keys.end()) {
|
|
|
|
|
return false; // missing documentation
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const auto& doc_entry : m_inner) {
|
|
|
|
|
const auto result_it{result_obj.find(doc_entry.m_key_name)};
|
|
|
|
|
if (result_it == result_obj.end()) {
|
|
|
|
|
if (!doc_entry.m_optional) {
|
|
|
|
|
return false; // result is missing a required key
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (!doc_entry.MatchesType(result_it->second)) {
|
|
|
|
|
return false; // wrong type
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
} // no default case, so the compiler can warn about missing cases
|
|
|
|
|
CHECK_NONFATAL(false);
|
|
|
|
|