diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index 4b37cc8c42c..1bc8933e624 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -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> entries) @@ -262,21 +262,21 @@ void PushUniqueSchema(UniValue& schemas, std::unordered_set& seen, } // NOLINTNEXTLINE(misc-no-recursion) -UniValue DedupArrayItemsSchema(std::span inner, bool include_hidden) +UniValue DedupArrayItemsSchema(std::span 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 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 names{SplitString(arg.m_names, '|')}; if (names.size() > 1) { diff --git a/test/functional/rpc_openrpc.py b/test/functional/rpc_openrpc.py index 91195b6d30a..32be191f58f 100755 --- a/test/functional/rpc_openrpc.py +++ b/test/functional/rpc_openrpc.py @@ -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"]