mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-06-25 08:21:24 +02:00
Merge bitcoin/bitcoin#26929: rpc: Throw more user friendly arg type check error (1.5/2)
fafeddfe0e6748e9769ad3dd526a6c0eaf6f4aac rpc: Throw more user friendly arg type check error (MarcoFalke) Pull request description: The arg type check error doesn't list which arg (position or name) failed. Fix that. ACKs for top commit: stickies-v: ACK fafeddfe0e6748e9769ad3dd526a6c0eaf6f4aac - although I think the functional test isn't in a logical place (but not blocking) Tree-SHA512: 17425aa145aab5045940ec74fff28f0e3b2b17ae55f91c4bb5cbcdff0ef13732f8e31621d85964dc2c04333ea37dbe528296ac61be27541384b44e37957555c8
This commit is contained in:
commit
ab98673f05
@ -31,14 +31,6 @@ std::string GetAllOutputTypes()
|
|||||||
return Join(ret, ", ");
|
return Join(ret, ", ");
|
||||||
}
|
}
|
||||||
|
|
||||||
void RPCTypeCheckArgument(const UniValue& value, const UniValueType& typeExpected)
|
|
||||||
{
|
|
||||||
if (!typeExpected.typeAny && value.type() != typeExpected.type) {
|
|
||||||
throw JSONRPCError(RPC_TYPE_ERROR,
|
|
||||||
strprintf("JSON value of type %s is not of expected type %s", uvTypeName(value.type()), uvTypeName(typeExpected.type)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void RPCTypeCheckObj(const UniValue& o,
|
void RPCTypeCheckObj(const UniValue& o,
|
||||||
const std::map<std::string, UniValueType>& typesExpected,
|
const std::map<std::string, UniValueType>& typesExpected,
|
||||||
bool fAllowNull,
|
bool fAllowNull,
|
||||||
@ -564,8 +556,16 @@ UniValue RPCHelpMan::HandleRequest(const JSONRPCRequest& request) const
|
|||||||
if (request.mode == JSONRPCRequest::GET_HELP || !IsValidNumArgs(request.params.size())) {
|
if (request.mode == JSONRPCRequest::GET_HELP || !IsValidNumArgs(request.params.size())) {
|
||||||
throw std::runtime_error(ToString());
|
throw std::runtime_error(ToString());
|
||||||
}
|
}
|
||||||
|
UniValue arg_mismatch{UniValue::VOBJ};
|
||||||
for (size_t i{0}; i < m_args.size(); ++i) {
|
for (size_t i{0}; i < m_args.size(); ++i) {
|
||||||
m_args.at(i).MatchesType(request.params[i]);
|
const auto& arg{m_args.at(i)};
|
||||||
|
UniValue match{arg.MatchesType(request.params[i])};
|
||||||
|
if (!match.isTrue()) {
|
||||||
|
arg_mismatch.pushKV(strprintf("Position %s (%s)", i + 1, arg.m_names), std::move(match));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!arg_mismatch.empty()) {
|
||||||
|
throw JSONRPCError(RPC_TYPE_ERROR, strprintf("Wrong type passed:\n%s", arg_mismatch.write(4)));
|
||||||
}
|
}
|
||||||
UniValue ret = m_fun(*this, request);
|
UniValue ret = m_fun(*this, request);
|
||||||
if (gArgs.GetBoolArg("-rpcdoccheck", DEFAULT_RPC_DOC_CHECK)) {
|
if (gArgs.GetBoolArg("-rpcdoccheck", DEFAULT_RPC_DOC_CHECK)) {
|
||||||
@ -684,42 +684,50 @@ UniValue RPCHelpMan::GetArgMap() const
|
|||||||
return arr;
|
return arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RPCArg::MatchesType(const UniValue& request) const
|
static std::optional<UniValue::VType> ExpectedType(RPCArg::Type type)
|
||||||
{
|
{
|
||||||
if (m_opts.skip_type_check) return;
|
using Type = RPCArg::Type;
|
||||||
if (IsOptional() && request.isNull()) return;
|
switch (type) {
|
||||||
switch (m_type) {
|
|
||||||
case Type::STR_HEX:
|
case Type::STR_HEX:
|
||||||
case Type::STR: {
|
case Type::STR: {
|
||||||
RPCTypeCheckArgument(request, UniValue::VSTR);
|
return UniValue::VSTR;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
case Type::NUM: {
|
case Type::NUM: {
|
||||||
RPCTypeCheckArgument(request, UniValue::VNUM);
|
return UniValue::VNUM;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
case Type::AMOUNT: {
|
case Type::AMOUNT: {
|
||||||
// VNUM or VSTR, checked inside AmountFromValue()
|
// VNUM or VSTR, checked inside AmountFromValue()
|
||||||
return;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
case Type::RANGE: {
|
case Type::RANGE: {
|
||||||
// VNUM or VARR, checked inside ParseRange()
|
// VNUM or VARR, checked inside ParseRange()
|
||||||
return;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
case Type::BOOL: {
|
case Type::BOOL: {
|
||||||
RPCTypeCheckArgument(request, UniValue::VBOOL);
|
return UniValue::VBOOL;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
case Type::OBJ:
|
case Type::OBJ:
|
||||||
case Type::OBJ_USER_KEYS: {
|
case Type::OBJ_USER_KEYS: {
|
||||||
RPCTypeCheckArgument(request, UniValue::VOBJ);
|
return UniValue::VOBJ;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
case Type::ARR: {
|
case Type::ARR: {
|
||||||
RPCTypeCheckArgument(request, UniValue::VARR);
|
return UniValue::VARR;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
} // no default case, so the compiler can warn about missing cases
|
} // no default case, so the compiler can warn about missing cases
|
||||||
|
NONFATAL_UNREACHABLE();
|
||||||
|
}
|
||||||
|
|
||||||
|
UniValue RPCArg::MatchesType(const UniValue& request) const
|
||||||
|
{
|
||||||
|
if (m_opts.skip_type_check) return true;
|
||||||
|
if (IsOptional() && request.isNull()) return true;
|
||||||
|
const auto exp_type{ExpectedType(m_type)};
|
||||||
|
if (!exp_type) return true; // nothing to check
|
||||||
|
|
||||||
|
if (*exp_type != request.getType()) {
|
||||||
|
return strprintf("JSON value of type %s is not of expected type %s", uvTypeName(request.getType()), uvTypeName(*exp_type));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string RPCArg::GetFirstName() const
|
std::string RPCArg::GetFirstName() const
|
||||||
@ -902,7 +910,7 @@ void RPCResult::ToSections(Sections& sections, const OuterType outer_type, const
|
|||||||
NONFATAL_UNREACHABLE();
|
NONFATAL_UNREACHABLE();
|
||||||
}
|
}
|
||||||
|
|
||||||
static const std::optional<UniValue::VType> ExpectedType(RPCResult::Type type)
|
static std::optional<UniValue::VType> ExpectedType(RPCResult::Type type)
|
||||||
{
|
{
|
||||||
using Type = RPCResult::Type;
|
using Type = RPCResult::Type;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
@ -62,11 +62,6 @@ struct UniValueType {
|
|||||||
UniValue::VType type;
|
UniValue::VType type;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Type-check one argument; throws JSONRPCError if wrong type given.
|
|
||||||
*/
|
|
||||||
void RPCTypeCheckArgument(const UniValue& value, const UniValueType& typeExpected);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Check for expected keys/value types in an Object.
|
Check for expected keys/value types in an Object.
|
||||||
*/
|
*/
|
||||||
@ -210,8 +205,11 @@ struct RPCArg {
|
|||||||
|
|
||||||
bool IsOptional() const;
|
bool IsOptional() const;
|
||||||
|
|
||||||
/** Check whether the request JSON type matches. */
|
/**
|
||||||
void MatchesType(const UniValue& request) const;
|
* Check whether the request JSON type matches.
|
||||||
|
* Returns true if type matches, or object describing error(s) if not.
|
||||||
|
*/
|
||||||
|
UniValue MatchesType(const UniValue& request) const;
|
||||||
|
|
||||||
/** Return the first of all aliases */
|
/** Return the first of all aliases */
|
||||||
std::string GetFirstName() const;
|
std::string GetFirstName() const;
|
||||||
|
@ -25,6 +25,7 @@ from decimal import Decimal
|
|||||||
import http.client
|
import http.client
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import textwrap
|
||||||
|
|
||||||
from test_framework.blocktools import (
|
from test_framework.blocktools import (
|
||||||
MAX_FUTURE_BLOCK_TIME,
|
MAX_FUTURE_BLOCK_TIME,
|
||||||
@ -429,6 +430,17 @@ class BlockchainTest(BitcoinTestFramework):
|
|||||||
def _test_getnetworkhashps(self):
|
def _test_getnetworkhashps(self):
|
||||||
self.log.info("Test getnetworkhashps")
|
self.log.info("Test getnetworkhashps")
|
||||||
hashes_per_second = self.nodes[0].getnetworkhashps()
|
hashes_per_second = self.nodes[0].getnetworkhashps()
|
||||||
|
assert_raises_rpc_error(
|
||||||
|
-3,
|
||||||
|
textwrap.dedent("""
|
||||||
|
Wrong type passed:
|
||||||
|
{
|
||||||
|
"Position 1 (nblocks)": "JSON value of type string is not of expected type number",
|
||||||
|
"Position 2 (height)": "JSON value of type array is not of expected type number"
|
||||||
|
}
|
||||||
|
""").strip(),
|
||||||
|
lambda: self.nodes[0].getnetworkhashps("a", []),
|
||||||
|
)
|
||||||
# This should be 2 hashes every 10 minutes or 1/300
|
# This should be 2 hashes every 10 minutes or 1/300
|
||||||
assert abs(hashes_per_second * 300 - 1) < 0.0001
|
assert abs(hashes_per_second * 300 - 1) < 0.0001
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user