mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-09-11 21:20:39 +02:00
Merge bitcoin/bitcoin#35946: rpc: Improve some type specs for openrpc
e07d826e0erpc: Fix type in ApplyTypeStrOverride (Shuvam Pandey)c94074fa1brpc: Surface OBJ_USER_KEYS description for openrpc (sedited)c020c21d54rpc: Handle skip type args for openrpc (sedited) Pull request description: This was initially motivated by testing the dump of the schema against open-rpc-generator, which crashed with: ``` open-rpc-generator generate -t client -l rust -n bitcoin_client -d ./openrpc.gen.json -o ./generated There was error at generator runtime: TypeError: Cannot convert undefined or null to object ``` The changes here fix this crash (albeit perfectly valid existing schema), but I think creating a more complete output is helpful on its own. The openrpc schema dumps can eventually be re-used for the rpc docs and to track rpc interface changes more accurately. Adding the CreateTxDoc outputs section seems useful for that. Also includes a type tightening from number to integer in `ApplyTypeStrOverride` to reflect the actual behaviour in the rpc calls, where only integers are accepted. ACKs for top commit: achow101: ACKe07d826e0ewillcl-ark: ACKe07d826e0eTree-SHA512: d0454a71b4f1dab1daf8a0d5b1e5bf1c1b8f1a16d26638d4a64a2652402ad74230366cabf0cf4135a16d0bdab584d3d4b2a47f2968a4eff605348ce85e8dbadb
This commit is contained in:
@@ -243,7 +243,7 @@ static RPCMethod getrpcinfo()
|
||||
}
|
||||
|
||||
namespace {
|
||||
UniValue OpenRPCArgSchema(const RPCArg& arg, bool include_hidden);
|
||||
UniValue OpenRPCArgSchema(const RPCArg& arg, bool include_hidden, bool in_skip_type_check);
|
||||
UniValue OpenRPCResultSchema(const RPCResult& result);
|
||||
|
||||
UniValue MakeObject(std::initializer_list<std::pair<std::string, UniValue>> entries)
|
||||
@@ -262,21 +262,21 @@ void PushUniqueSchema(UniValue& schemas, std::unordered_set<std::string>& seen,
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(misc-no-recursion)
|
||||
UniValue DedupArrayItemsSchema(std::span<const RPCArg> inner, bool include_hidden)
|
||||
UniValue DedupArrayItemsSchema(std::span<const RPCArg> inner, bool include_hidden, bool in_skip_type_check)
|
||||
{
|
||||
if (inner.empty()) return UniValue{UniValue::VOBJ};
|
||||
if (inner.size() == 1) return OpenRPCArgSchema(inner.front(), include_hidden);
|
||||
if (inner.size() == 1) return OpenRPCArgSchema(inner.front(), include_hidden, in_skip_type_check);
|
||||
|
||||
UniValue one_of{UniValue::VARR};
|
||||
std::unordered_set<std::string> seen;
|
||||
for (const auto& item : inner) {
|
||||
PushUniqueSchema(one_of, seen, OpenRPCArgSchema(item, include_hidden));
|
||||
PushUniqueSchema(one_of, seen, OpenRPCArgSchema(item, include_hidden, in_skip_type_check));
|
||||
}
|
||||
|
||||
if (one_of.size() == 1) return one_of[0];
|
||||
|
||||
UniValue items{UniValue::VOBJ};
|
||||
items.pushKV("oneOf", std::move(one_of));
|
||||
items.pushKV(in_skip_type_check ? "anyOf" : "oneOf", std::move(one_of));
|
||||
return items;
|
||||
}
|
||||
|
||||
@@ -311,7 +311,7 @@ void ApplyTypeStrOverride(UniValue& schema, const RPCArg& arg)
|
||||
};
|
||||
if (number_or_string.contains(type_label)) {
|
||||
UniValue one_of{UniValue::VARR};
|
||||
one_of.push_back(MakeObject({{"type", "number"}}));
|
||||
one_of.push_back(MakeObject({{"type", "integer"}}));
|
||||
one_of.push_back(MakeObject({{"type", "string"}}));
|
||||
schema = UniValue{UniValue::VOBJ};
|
||||
schema.pushKV("oneOf", std::move(one_of));
|
||||
@@ -331,14 +331,18 @@ void ApplyArgFallback(UniValue& schema, const RPCArg& arg)
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(misc-no-recursion)
|
||||
UniValue OpenRPCArgSchema(const RPCArg& arg, bool include_hidden)
|
||||
UniValue OpenRPCArgSchema(const RPCArg& arg, bool include_hidden, bool in_skip_type_check)
|
||||
{
|
||||
UniValue schema{UniValue::VOBJ};
|
||||
if (arg.m_opts.skip_type_check) {
|
||||
ApplyTypeStrOverride(schema, arg);
|
||||
if (schema.empty() && arg.m_type == RPCArg::Type::ARR) {
|
||||
UniValue items{UniValue::VOBJ};
|
||||
items.pushKV("type", "array");
|
||||
items.pushKV("items", DedupArrayItemsSchema(arg.m_inner, include_hidden, /*in_skip_type_check=*/true));
|
||||
|
||||
UniValue one_of{UniValue::VARR};
|
||||
one_of.push_back(MakeObject({{"type", "array"}}));
|
||||
one_of.push_back(std::move(items));
|
||||
one_of.push_back(MakeObject({{"type", "object"}}));
|
||||
schema.pushKV("oneOf", std::move(one_of));
|
||||
}
|
||||
@@ -383,7 +387,7 @@ UniValue OpenRPCArgSchema(const RPCArg& arg, bool include_hidden)
|
||||
break;
|
||||
}
|
||||
case RPCArg::Type::ARR: {
|
||||
UniValue items{DedupArrayItemsSchema(arg.m_inner, include_hidden)};
|
||||
UniValue items{DedupArrayItemsSchema(arg.m_inner, include_hidden, in_skip_type_check)};
|
||||
schema.pushKV("type", "array");
|
||||
schema.pushKV("items", std::move(items));
|
||||
break;
|
||||
@@ -394,7 +398,7 @@ UniValue OpenRPCArgSchema(const RPCArg& arg, bool include_hidden)
|
||||
UniValue required{UniValue::VARR};
|
||||
for (const auto& inner : arg.m_inner) {
|
||||
if (!include_hidden && inner.m_opts.hidden) continue;
|
||||
UniValue prop{OpenRPCArgSchema(inner, include_hidden)};
|
||||
UniValue prop{OpenRPCArgSchema(inner, include_hidden, in_skip_type_check)};
|
||||
if (!inner.m_description.empty()) prop.pushKV("description", inner.m_description);
|
||||
if (inner.m_opts.placeholder) prop.pushKV("x-bitcoin-placeholder", true);
|
||||
if (inner.m_opts.also_positional) prop.pushKV("x-bitcoin-also-positional", true);
|
||||
@@ -410,7 +414,10 @@ UniValue OpenRPCArgSchema(const RPCArg& arg, bool include_hidden)
|
||||
case RPCArg::Type::OBJ_USER_KEYS: {
|
||||
schema.pushKV("type", "object");
|
||||
if (!arg.m_inner.empty()) {
|
||||
schema.pushKV("additionalProperties", OpenRPCArgSchema(arg.m_inner[0], include_hidden));
|
||||
schema.pushKV("additionalProperties", OpenRPCArgSchema(arg.m_inner[0], include_hidden, in_skip_type_check));
|
||||
if (!arg.m_inner[0].m_description.empty()) {
|
||||
schema.pushKV("description", arg.m_inner[0].m_description);
|
||||
}
|
||||
} else {
|
||||
schema.pushKV("additionalProperties", true);
|
||||
}
|
||||
@@ -911,7 +918,7 @@ UniValue CRPCTable::buildOpenRPCDoc(bool include_hidden) const
|
||||
UniValue param{UniValue::VOBJ};
|
||||
param.pushKV("name", arg.GetFirstName());
|
||||
param.pushKV("required", !arg.IsOptional());
|
||||
param.pushKV("schema", OpenRPCArgSchema(arg, include_hidden));
|
||||
param.pushKV("schema", OpenRPCArgSchema(arg, include_hidden, /*in_skip_type_check=*/false));
|
||||
|
||||
std::vector<std::string> names{SplitString(arg.m_names, '|')};
|
||||
if (names.size() > 1) {
|
||||
|
||||
@@ -52,7 +52,7 @@ class OpenRPCDocTest(BitcoinTestFramework):
|
||||
self.log.info("Checking type_str override schemas")
|
||||
getblockstats = find_method(openrpc, "getblockstats")
|
||||
hash_or_height = find_param(getblockstats, "hash_or_height")
|
||||
assert_equal(hash_or_height["schema"], {"oneOf": [{"type": "number"}, {"type": "string"}]})
|
||||
assert_equal(hash_or_height["schema"], {"oneOf": [{"type": "integer"}, {"type": "string"}]})
|
||||
|
||||
self.log.info("Checking fixed-length array schemas")
|
||||
deriveaddresses = find_method(openrpc, "deriveaddresses")
|
||||
@@ -75,7 +75,11 @@ class OpenRPCDocTest(BitcoinTestFramework):
|
||||
self.log.info("Checking relaxed schemas for unchecked RPC types")
|
||||
createrawtransaction = find_method(openrpc, "createrawtransaction")
|
||||
outputs = find_param(createrawtransaction, "outputs")
|
||||
assert_equal(outputs["schema"], {"oneOf": [{"type": "array"}, {"type": "object"}]})
|
||||
address_description = "A key-value pair. The key (string) is the bitcoin address, the value (float or string) is the amount in BTC"
|
||||
address_obj = {"type": "object", "additionalProperties": {"oneOf": [{"type": "number"},{"type": "string"}]}, "description": address_description}
|
||||
data_description = "A key-value pair. The key must be \"data\", the value is hex-encoded data that becomes a part of an OP_RETURN output"
|
||||
data_obj = {"type": "object", "properties": { "data": {"type": "string", "pattern": "^[0-9a-fA-F]+$", "description": data_description}}, "additionalProperties": False, "required": ["data"]}
|
||||
assert_equal(outputs["schema"], {"oneOf": [{"type": "array", "items": {"anyOf": [address_obj, data_obj]}}, {"type": "object"}]})
|
||||
|
||||
getdescriptoractivity = find_method(openrpc, "getdescriptoractivity")
|
||||
activity = getdescriptoractivity["result"]["schema"]["properties"]["activity"]
|
||||
|
||||
Reference in New Issue
Block a user