From 90ac03cae74c64b7d58cdd9aac2aaabae9ff5496 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?emrear=C4=B1y=C3=BCrek?= Date: Wed, 3 Sep 2025 18:06:21 +0200 Subject: [PATCH] fix: Support canonical names for type in account_objects (#2437) Fix: https://github.com/XRPLF/clio/issues/2275 --------- Co-authored-by: Sergey Kuznetsov --- src/rpc/common/Validators.cpp | 26 +++ src/rpc/common/Validators.hpp | 16 ++ src/rpc/handlers/AccountObjects.cpp | 4 +- src/rpc/handlers/AccountObjects.hpp | 7 +- src/rpc/handlers/LedgerData.cpp | 1 - src/rpc/handlers/LedgerData.hpp | 10 +- src/util/LedgerUtils.cpp | 57 +++++- src/util/LedgerUtils.hpp | 136 +++++++------- .../unit/rpc/handlers/AccountObjectsTests.cpp | 2 +- tests/unit/util/LedgerUtilsTests.cpp | 172 ++++++++++++++---- 10 files changed, 307 insertions(+), 124 deletions(-) diff --git a/src/rpc/common/Validators.cpp b/src/rpc/common/Validators.cpp index b7836edb..6f7cdf64 100644 --- a/src/rpc/common/Validators.cpp +++ b/src/rpc/common/Validators.cpp @@ -23,6 +23,7 @@ #include "rpc/RPCHelpers.hpp" #include "rpc/common/Types.hpp" #include "util/AccountUtils.hpp" +#include "util/LedgerUtils.hpp" #include "util/TimeUtils.hpp" #include @@ -120,6 +121,18 @@ CustomValidator CustomValidators::ledgerIndexValidator = return MaybeError{}; }}; +CustomValidator CustomValidators::ledgerTypeValidator = + CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError { + if (!value.is_string()) + return Error{Status{RippledError::rpcINVALID_PARAMS, fmt::format("Invalid field '{}', not string.", key)}}; + + auto const type = util::LedgerTypes::getLedgerEntryTypeFromStr(boost::json::value_to(value)); + if (type == ripple::ltANY) + return Error{Status{RippledError::rpcINVALID_PARAMS, fmt::format("Invalid field '{}'.", key)}}; + + return MaybeError{}; + }}; + CustomValidator CustomValidators::accountValidator = CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError { if (!value.is_string()) @@ -160,6 +173,19 @@ CustomValidator CustomValidators::accountMarkerValidator = return MaybeError{}; }}; +CustomValidator CustomValidators::accountTypeValidator = + CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError { + if (!value.is_string()) + return Error{Status{RippledError::rpcINVALID_PARAMS, fmt::format("Invalid field '{}', not string.", key)}}; + + auto const type = + util::LedgerTypes::getAccountOwnedLedgerTypeFromStr(boost::json::value_to(value)); + if (type == ripple::ltANY) + return Error{Status{RippledError::rpcINVALID_PARAMS, fmt::format("Invalid field '{}'.", key)}}; + + return MaybeError{}; + }}; + CustomValidator CustomValidators::currencyValidator = CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError { if (!value.is_string()) diff --git a/src/rpc/common/Validators.hpp b/src/rpc/common/Validators.hpp index 3c8258c1..ab38bddb 100644 --- a/src/rpc/common/Validators.hpp +++ b/src/rpc/common/Validators.hpp @@ -486,6 +486,14 @@ struct CustomValidators final { */ static CustomValidator ledgerIndexValidator; + /** + * @brief Provides a validator for ledger type. + * + * A type accepts canonical names of ledger entry types (case insensitive) or short names. + * Used by ledger_data. + */ + static CustomValidator ledgerTypeValidator; + /** * @brief Provides a commonly used validator for accounts. * @@ -508,6 +516,14 @@ struct CustomValidators final { */ static CustomValidator accountMarkerValidator; + /** + * @brief Provides a validator for account type. + * + * A type accepts canonical names of owned ledger entry types (case insensitive) or short names. + * Used by account_objects. + */ + static CustomValidator accountTypeValidator; + /** * @brief Provides a commonly used validator for uint160(AccountID) hex string. * diff --git a/src/rpc/handlers/AccountObjects.cpp b/src/rpc/handlers/AccountObjects.cpp index 51edddba..6afd185b 100644 --- a/src/rpc/handlers/AccountObjects.cpp +++ b/src/rpc/handlers/AccountObjects.cpp @@ -32,7 +32,6 @@ #include #include #include -#include #include #include #include @@ -160,7 +159,8 @@ tag_invoke(boost::json::value_to_tag, boost::json: } if (jsonObject.contains(JS(type))) - input.type = util::LedgerTypes::getLedgerEntryTypeFromStr(boost::json::value_to(jv.at(JS(type)))); + input.type = + util::LedgerTypes::getAccountOwnedLedgerTypeFromStr(boost::json::value_to(jv.at(JS(type)))); if (jsonObject.contains(JS(limit))) input.limit = jv.at(JS(limit)).as_int64(); diff --git a/src/rpc/handlers/AccountObjects.hpp b/src/rpc/handlers/AccountObjects.hpp index f7d39515..f795518b 100644 --- a/src/rpc/handlers/AccountObjects.hpp +++ b/src/rpc/handlers/AccountObjects.hpp @@ -25,11 +25,9 @@ #include "rpc/common/Specs.hpp" #include "rpc/common/Types.hpp" #include "rpc/common/Validators.hpp" -#include "util/LedgerUtils.hpp" #include #include -#include #include #include @@ -107,7 +105,6 @@ public: static RpcSpecConstRef spec([[maybe_unused]] uint32_t apiVersion) { - auto const& accountOwnedTypes = util::LedgerTypes::getAccountOwnedLedgerTypeStrList(); static auto const kRPC_SPEC = RpcSpec{ {JS(account), validation::Required{}, validation::CustomValidators::accountValidator}, {JS(ledger_hash), validation::CustomValidators::uint256HexStringValidator}, @@ -116,9 +113,7 @@ public: validation::Type{}, validation::Min(1u), modifiers::Clamp(kLIMIT_MIN, kLIMIT_MAX)}, - {JS(type), - validation::Type{}, - validation::OneOf(accountOwnedTypes.cbegin(), accountOwnedTypes.cend())}, + {JS(type), validation::CustomValidators::accountTypeValidator}, {JS(marker), validation::CustomValidators::accountMarkerValidator}, {JS(deletion_blockers_only), validation::Type{}}, }; diff --git a/src/rpc/handlers/LedgerData.cpp b/src/rpc/handlers/LedgerData.cpp index fae0af90..851fb3e3 100644 --- a/src/rpc/handlers/LedgerData.cpp +++ b/src/rpc/handlers/LedgerData.cpp @@ -34,7 +34,6 @@ #include #include #include -#include #include #include #include diff --git a/src/rpc/handlers/LedgerData.hpp b/src/rpc/handlers/LedgerData.hpp index b63be1c6..5fbc8ba6 100644 --- a/src/rpc/handlers/LedgerData.hpp +++ b/src/rpc/handlers/LedgerData.hpp @@ -20,14 +20,12 @@ #pragma once #include "data/BackendInterface.hpp" -#include "rpc/Errors.hpp" #include "rpc/JS.hpp" #include "rpc/common/Checkers.hpp" #include "rpc/common/MetaProcessors.hpp" #include "rpc/common/Specs.hpp" #include "rpc/common/Types.hpp" #include "rpc/common/Validators.hpp" -#include "util/LedgerUtils.hpp" #include "util/log/Logger.hpp" #include @@ -36,7 +34,6 @@ #include #include #include -#include #include #include @@ -113,7 +110,6 @@ public: static RpcSpecConstRef spec([[maybe_unused]] uint32_t apiVersion) { - auto const& ledgerTypeStrs = util::LedgerTypes::getLedgerEntryTypeStrList(); static auto const kRPC_SPEC = RpcSpec{ {JS(binary), validation::Type{}}, {"out_of_order", validation::Type{}}, @@ -123,11 +119,7 @@ public: {JS(marker), validation::Type{}, meta::IfType{validation::CustomValidators::uint256HexStringValidator}}, - {JS(type), - meta::WithCustomError{ - validation::Type{}, Status{ripple::rpcINVALID_PARAMS, "Invalid field 'type', not string."} - }, - validation::OneOf(ledgerTypeStrs.cbegin(), ledgerTypeStrs.cend())}, + {JS(type), validation::CustomValidators::ledgerTypeValidator}, {JS(ledger), check::Deprecated{}}, }; return kRPC_SPEC; diff --git a/src/util/LedgerUtils.cpp b/src/util/LedgerUtils.cpp index 4bccb8e0..aafc7925 100644 --- a/src/util/LedgerUtils.cpp +++ b/src/util/LedgerUtils.cpp @@ -19,9 +19,14 @@ #include "util/LedgerUtils.hpp" +#include "util/JsonUtils.hpp" + +#include #include #include +#include +#include #include #include @@ -30,16 +35,52 @@ namespace util { ripple::LedgerEntryType LedgerTypes::getLedgerEntryTypeFromStr(std::string const& entryName) { - static std::unordered_map kTYPE_MAP = []() { - std::unordered_map map; - std::ranges::for_each(kLEDGER_TYPES, [&map](auto const& item) { map[item.name_] = item.type_; }); - return map; - }(); + if (auto const result = getLedgerTypeAttributeFromStr(entryName); result.has_value()) { + return result->get().type_; + } + return ripple::ltANY; +} - if (!kTYPE_MAP.contains(entryName)) - return ripple::ltANY; +ripple::LedgerEntryType +LedgerTypes::getAccountOwnedLedgerTypeFromStr(std::string const& entryName) +{ + if (auto const result = getLedgerTypeAttributeFromStr(entryName); + result.has_value() && result->get().category_ != LedgerTypeAttribute::LedgerCategory::Chain) { + return result->get().type_; + } - return kTYPE_MAP.at(entryName); + return ripple::ltANY; +} + +std::optional> +LedgerTypes::getLedgerTypeAttributeFromStr(std::string const& entryName) +{ + static std::unordered_map> const kNAME_MAP = + []() { + std::unordered_map> map; + std::ranges::for_each(kLEDGER_TYPES, [&map](auto const& item) { + map.insert({util::toLower(item.name_), item}); + }); + return map; + }(); + + static std::unordered_map> const + kRPC_NAME_MAP = []() { + std::unordered_map> map; + std::ranges::for_each(kLEDGER_TYPES, [&map](auto const& item) { map.insert({item.rpcName_, item}); }); + return map; + }(); + + if (auto const it = kRPC_NAME_MAP.find(entryName); it != kRPC_NAME_MAP.end()) { + return it->second; + } + + auto const entryNameLowercase = util::toLower(entryName); + if (auto const it = kNAME_MAP.find(entryNameLowercase); it != kNAME_MAP.end()) { + return it->second; + } + + return std::nullopt; } } // namespace util diff --git a/src/util/LedgerUtils.hpp b/src/util/LedgerUtils.hpp index 1686d247..cbf81fc3 100644 --- a/src/util/LedgerUtils.hpp +++ b/src/util/LedgerUtils.hpp @@ -31,6 +31,8 @@ #include #include +#include +#include #include #include #include @@ -40,6 +42,7 @@ namespace util { class LedgerTypes; namespace impl { + class LedgerTypeAttribute { enum class LedgerCategory { Invalid, @@ -50,33 +53,40 @@ class LedgerTypeAttribute { ripple::LedgerEntryType type_ = ripple::ltANY; char const* name_ = nullptr; + char const* rpcName_ = nullptr; LedgerCategory category_ = LedgerCategory::Invalid; - constexpr LedgerTypeAttribute(char const* name, ripple::LedgerEntryType type, LedgerCategory category) - : type_(type), name_(name), category_(category) + constexpr LedgerTypeAttribute( + char const* name, + char const* rpcName, + ripple::LedgerEntryType type, + LedgerCategory category + ) + : type_{type}, name_{name}, rpcName_{rpcName}, category_{category} { } public: static constexpr LedgerTypeAttribute - chainLedgerType(char const* name, ripple::LedgerEntryType type) + chainLedgerType(char const* name, char const* rpcName, ripple::LedgerEntryType type) { - return LedgerTypeAttribute(name, type, LedgerCategory::Chain); + return LedgerTypeAttribute(name, rpcName, type, LedgerCategory::Chain); } static constexpr LedgerTypeAttribute - accountOwnedLedgerType(char const* name, ripple::LedgerEntryType type) + accountOwnedLedgerType(char const* name, char const* rpcName, ripple::LedgerEntryType type) { - return LedgerTypeAttribute(name, type, LedgerCategory::AccountOwned); + return LedgerTypeAttribute(name, rpcName, type, LedgerCategory::AccountOwned); } static constexpr LedgerTypeAttribute - deletionBlockerLedgerType(char const* name, ripple::LedgerEntryType type) + deletionBlockerLedgerType(char const* name, char const* rpcName, ripple::LedgerEntryType type) { - return LedgerTypeAttribute(name, type, LedgerCategory::DeletionBlocker); + return LedgerTypeAttribute(name, rpcName, type, LedgerCategory::DeletionBlocker); } friend class util::LedgerTypes; }; + } // namespace impl /** @@ -88,38 +98,51 @@ class LedgerTypes { using LedgerTypeAttributeList = LedgerTypeAttribute[]; static constexpr LedgerTypeAttributeList const kLEDGER_TYPES{ - LedgerTypeAttribute::accountOwnedLedgerType(JS(account), ripple::ltACCOUNT_ROOT), - LedgerTypeAttribute::chainLedgerType(JS(amendments), ripple::ltAMENDMENTS), - LedgerTypeAttribute::deletionBlockerLedgerType(JS(check), ripple::ltCHECK), - LedgerTypeAttribute::accountOwnedLedgerType(JS(deposit_preauth), ripple::ltDEPOSIT_PREAUTH), + LedgerTypeAttribute::accountOwnedLedgerType(JS(AccountRoot), JS(account), ripple::ltACCOUNT_ROOT), + LedgerTypeAttribute::chainLedgerType(JS(Amendments), JS(amendments), ripple::ltAMENDMENTS), + LedgerTypeAttribute::deletionBlockerLedgerType(JS(Check), JS(check), ripple::ltCHECK), + LedgerTypeAttribute::accountOwnedLedgerType(JS(DepositPreauth), JS(deposit_preauth), ripple::ltDEPOSIT_PREAUTH), // dir node belongs to account, but can not be filtered from account_objects - LedgerTypeAttribute::chainLedgerType(JS(directory), ripple::ltDIR_NODE), - LedgerTypeAttribute::deletionBlockerLedgerType(JS(escrow), ripple::ltESCROW), - LedgerTypeAttribute::chainLedgerType(JS(fee), ripple::ltFEE_SETTINGS), - LedgerTypeAttribute::chainLedgerType(JS(hashes), ripple::ltLEDGER_HASHES), - LedgerTypeAttribute::accountOwnedLedgerType(JS(offer), ripple::ltOFFER), - LedgerTypeAttribute::deletionBlockerLedgerType(JS(payment_channel), ripple::ltPAYCHAN), - LedgerTypeAttribute::accountOwnedLedgerType(JS(signer_list), ripple::ltSIGNER_LIST), - LedgerTypeAttribute::deletionBlockerLedgerType(JS(state), ripple::ltRIPPLE_STATE), - LedgerTypeAttribute::accountOwnedLedgerType(JS(ticket), ripple::ltTICKET), - LedgerTypeAttribute::accountOwnedLedgerType(JS(nft_offer), ripple::ltNFTOKEN_OFFER), - LedgerTypeAttribute::deletionBlockerLedgerType(JS(nft_page), ripple::ltNFTOKEN_PAGE), - LedgerTypeAttribute::accountOwnedLedgerType(JS(amm), ripple::ltAMM), - LedgerTypeAttribute::deletionBlockerLedgerType(JS(bridge), ripple::ltBRIDGE), - LedgerTypeAttribute::deletionBlockerLedgerType(JS(xchain_owned_claim_id), ripple::ltXCHAIN_OWNED_CLAIM_ID), + LedgerTypeAttribute::chainLedgerType(JS(DirectoryNode), JS(directory), ripple::ltDIR_NODE), + LedgerTypeAttribute::deletionBlockerLedgerType(JS(Escrow), JS(escrow), ripple::ltESCROW), + LedgerTypeAttribute::chainLedgerType(JS(FeeSettings), JS(fee), ripple::ltFEE_SETTINGS), + LedgerTypeAttribute::chainLedgerType(JS(LedgerHashes), JS(hashes), ripple::ltLEDGER_HASHES), + LedgerTypeAttribute::accountOwnedLedgerType(JS(Offer), JS(offer), ripple::ltOFFER), + LedgerTypeAttribute::deletionBlockerLedgerType(JS(PayChannel), JS(payment_channel), ripple::ltPAYCHAN), + LedgerTypeAttribute::accountOwnedLedgerType(JS(SignerList), JS(signer_list), ripple::ltSIGNER_LIST), + LedgerTypeAttribute::deletionBlockerLedgerType(JS(RippleState), JS(state), ripple::ltRIPPLE_STATE), + LedgerTypeAttribute::accountOwnedLedgerType(JS(Ticket), JS(ticket), ripple::ltTICKET), + LedgerTypeAttribute::accountOwnedLedgerType(JS(NFTokenOffer), JS(nft_offer), ripple::ltNFTOKEN_OFFER), + LedgerTypeAttribute::deletionBlockerLedgerType(JS(NFTokenPage), JS(nft_page), ripple::ltNFTOKEN_PAGE), + LedgerTypeAttribute::accountOwnedLedgerType(JS(AMM), JS(amm), ripple::ltAMM), + LedgerTypeAttribute::deletionBlockerLedgerType(JS(Bridge), JS(bridge), ripple::ltBRIDGE), LedgerTypeAttribute::deletionBlockerLedgerType( + JS(XChainOwnedClaimID), + JS(xchain_owned_claim_id), + ripple::ltXCHAIN_OWNED_CLAIM_ID + ), + LedgerTypeAttribute::deletionBlockerLedgerType( + JS(XChainOwnedCreateAccountClaimID), JS(xchain_owned_create_account_claim_id), ripple::ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID ), - LedgerTypeAttribute::accountOwnedLedgerType(JS(did), ripple::ltDID), - LedgerTypeAttribute::accountOwnedLedgerType(JS(oracle), ripple::ltORACLE), - LedgerTypeAttribute::accountOwnedLedgerType(JS(credential), ripple::ltCREDENTIAL), - LedgerTypeAttribute::accountOwnedLedgerType(JS(vault), ripple::ltVAULT), - LedgerTypeAttribute::chainLedgerType(JS(nunl), ripple::ltNEGATIVE_UNL), - LedgerTypeAttribute::deletionBlockerLedgerType(JS(mpt_issuance), ripple::ltMPTOKEN_ISSUANCE), - LedgerTypeAttribute::deletionBlockerLedgerType(JS(mptoken), ripple::ltMPTOKEN), - LedgerTypeAttribute::deletionBlockerLedgerType(JS(permissioned_domain), ripple::ltPERMISSIONED_DOMAIN), - LedgerTypeAttribute::accountOwnedLedgerType(JS(delegate), ripple::ltDELEGATE), + LedgerTypeAttribute::accountOwnedLedgerType(JS(DID), JS(did), ripple::ltDID), + LedgerTypeAttribute::accountOwnedLedgerType(JS(Oracle), JS(oracle), ripple::ltORACLE), + LedgerTypeAttribute::accountOwnedLedgerType(JS(Credential), JS(credential), ripple::ltCREDENTIAL), + LedgerTypeAttribute::accountOwnedLedgerType(JS(Vault), JS(vault), ripple::ltVAULT), + LedgerTypeAttribute::chainLedgerType(JS(NegativeUNL), JS(nunl), ripple::ltNEGATIVE_UNL), + LedgerTypeAttribute::deletionBlockerLedgerType( + JS(MPTokenIssuance), + JS(mpt_issuance), + ripple::ltMPTOKEN_ISSUANCE + ), + LedgerTypeAttribute::deletionBlockerLedgerType(JS(MPToken), JS(mptoken), ripple::ltMPTOKEN), + LedgerTypeAttribute::deletionBlockerLedgerType( + JS(PermissionedDomain), + JS(permissioned_domain), + ripple::ltPERMISSIONED_DOMAIN + ), + LedgerTypeAttribute::accountOwnedLedgerType(JS(Delegate), JS(delegate), ripple::ltDELEGATE), }; public: @@ -131,32 +154,7 @@ public: getLedgerEntryTypeStrList() { std::array res{}; - std::ranges::transform(kLEDGER_TYPES, std::begin(res), [](auto const& item) { return item.name_; }); - return res; - } - - /** - * @brief Returns a list of all account owned ledger entry type as string. - * - * @return A list of all account owned ledger entry type as string. - */ - static constexpr auto - getAccountOwnedLedgerTypeStrList() - { - constexpr auto kFILTER = [](auto const& item) { - return item.category_ != LedgerTypeAttribute::LedgerCategory::Chain; - }; - - constexpr auto kACCOUNT_OWNED_COUNT = - std::count_if(std::begin(kLEDGER_TYPES), std::end(kLEDGER_TYPES), kFILTER); - std::array res{}; - auto it = std::begin(res); - std::ranges::for_each(kLEDGER_TYPES, [&](auto const& item) { - if (kFILTER(item)) { - *it = item.name_; - ++it; - } - }); + std::ranges::transform(kLEDGER_TYPES, std::begin(res), [](auto const& item) { return item.rpcName_; }); return res; } @@ -188,11 +186,25 @@ public: /** * @brief Returns the ripple::LedgerEntryType from the given string. * - * @param entryName The name of the ledger entry type + * @param entryName The name or canonical name (case-insensitive) of the ledger entry type for all categories * @return The ripple::LedgerEntryType of the given string, returns ltANY if not found. */ static ripple::LedgerEntryType getLedgerEntryTypeFromStr(std::string const& entryName); + + /** + * @brief Returns the ripple::LedgerEntryType from the given string. + * + * @param entryName The name or canonical name (case-insensitive) of the ledger entry type for account owned + * category + * @return The ripple::LedgerEntryType of the given string, returns ltANY if not found. + */ + static ripple::LedgerEntryType + getAccountOwnedLedgerTypeFromStr(std::string const& entryName); + +private: + static std::optional> + getLedgerTypeAttributeFromStr(std::string const& entryName); }; /** diff --git a/tests/unit/rpc/handlers/AccountObjectsTests.cpp b/tests/unit/rpc/handlers/AccountObjectsTests.cpp index 24d4e06b..8b723b5d 100644 --- a/tests/unit/rpc/handlers/AccountObjectsTests.cpp +++ b/tests/unit/rpc/handlers/AccountObjectsTests.cpp @@ -110,7 +110,7 @@ generateTestValuesForParametersTest() .testName = "TypeNotString", .testJson = R"JSON({"account": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", "type": 1})JSON", .expectedError = "invalidParams", - .expectedErrorMessage = "Invalid parameters." + .expectedErrorMessage = "Invalid field 'type', not string." }, AccountObjectsParamTestCaseBundle{ .testName = "TypeInvalid", diff --git a/tests/unit/util/LedgerUtilsTests.cpp b/tests/unit/util/LedgerUtilsTests.cpp index ba92154f..b563ae1c 100644 --- a/tests/unit/util/LedgerUtilsTests.cpp +++ b/tests/unit/util/LedgerUtilsTests.cpp @@ -26,7 +26,9 @@ #include #include +#include #include +#include TEST(LedgerUtilsTests, LedgerObjectTypeList) { @@ -68,46 +70,14 @@ TEST(LedgerUtilsTests, LedgerObjectTypeList) })); } -TEST(LedgerUtilsTests, AccountOwnedTypeList) -{ - constexpr auto kACCOUNT_OWNED = util::LedgerTypes::getAccountOwnedLedgerTypeStrList(); - static constexpr char const* kCORRECT_TYPES[] = { - JS(account), - JS(check), - JS(deposit_preauth), - JS(escrow), - JS(offer), - JS(payment_channel), - JS(signer_list), - JS(state), - JS(ticket), - JS(nft_offer), - JS(nft_page), - JS(amm), - JS(bridge), - JS(xchain_owned_claim_id), - JS(xchain_owned_create_account_claim_id), - JS(did), - JS(oracle), - JS(credential), - JS(mpt_issuance), - JS(mptoken), - JS(permissioned_domain), - JS(vault), - JS(delegate) - }; - - static_assert(std::size(kCORRECT_TYPES) == kACCOUNT_OWNED.size()); - static_assert(std::ranges::all_of(kCORRECT_TYPES, [&kACCOUNT_OWNED](std::string_view type) { - return std::ranges::find(kACCOUNT_OWNED, type) != std::cend(kACCOUNT_OWNED); - })); -} - TEST(LedgerUtilsTests, StrToType) { EXPECT_EQ(util::LedgerTypes::getLedgerEntryTypeFromStr("mess"), ripple::ltANY); EXPECT_EQ(util::LedgerTypes::getLedgerEntryTypeFromStr("tomato"), ripple::ltANY); EXPECT_EQ(util::LedgerTypes::getLedgerEntryTypeFromStr("account"), ripple::ltACCOUNT_ROOT); + EXPECT_EQ(util::LedgerTypes::getLedgerEntryTypeFromStr("AccoUnt"), ripple::ltANY); + EXPECT_EQ(util::LedgerTypes::getLedgerEntryTypeFromStr("AccountRoot"), ripple::ltACCOUNT_ROOT); + EXPECT_EQ(util::LedgerTypes::getLedgerEntryTypeFromStr("ACCOUNTRoot"), ripple::ltACCOUNT_ROOT); constexpr auto kTYPES = util::LedgerTypes::getLedgerEntryTypeStrList(); std::ranges::for_each(kTYPES, [](auto const& typeStr) { @@ -139,3 +109,135 @@ TEST(LedgerUtilsTests, DeletionBlockerTypes) std::cend(kDELETION_BLOCKERS); })); } + +struct LedgerEntryTypeParam { + std::string input; + ripple::LedgerEntryType expected; +}; + +static LedgerEntryTypeParam const kCHAIN_TEST_CASES[] = { + // Using RPC name with exact match + {.input = "amendments", .expected = ripple::ltAMENDMENTS}, + {.input = "directory", .expected = ripple::ltDIR_NODE}, + {.input = "fee", .expected = ripple::ltFEE_SETTINGS}, + {.input = "hashes", .expected = ripple::ltLEDGER_HASHES}, + {.input = "nunl", .expected = ripple::ltNEGATIVE_UNL}, + + // Using canonical name with exact match + {.input = "Amendments", .expected = ripple::ltAMENDMENTS}, + {.input = "DirectoryNode", .expected = ripple::ltDIR_NODE}, + {.input = "FeeSettings", .expected = ripple::ltFEE_SETTINGS}, + {.input = "LedgerHashes", .expected = ripple::ltLEDGER_HASHES}, + {.input = "NegativeUNL", .expected = ripple::ltNEGATIVE_UNL} +}; + +static LedgerEntryTypeParam const kACCOUNT_OWNED_TEST_CASES[] = { + // Using RPC name with exact match + {.input = "account", .expected = ripple::ltACCOUNT_ROOT}, + {.input = "check", .expected = ripple::ltCHECK}, + {.input = "deposit_preauth", .expected = ripple::ltDEPOSIT_PREAUTH}, + {.input = "escrow", .expected = ripple::ltESCROW}, + {.input = "offer", .expected = ripple::ltOFFER}, + {.input = "payment_channel", .expected = ripple::ltPAYCHAN}, + {.input = "signer_list", .expected = ripple::ltSIGNER_LIST}, + {.input = "state", .expected = ripple::ltRIPPLE_STATE}, + {.input = "ticket", .expected = ripple::ltTICKET}, + {.input = "nft_offer", .expected = ripple::ltNFTOKEN_OFFER}, + {.input = "nft_page", .expected = ripple::ltNFTOKEN_PAGE}, + {.input = "amm", .expected = ripple::ltAMM}, + {.input = "bridge", .expected = ripple::ltBRIDGE}, + {.input = "xchain_owned_claim_id", .expected = ripple::ltXCHAIN_OWNED_CLAIM_ID}, + {.input = "xchain_owned_create_account_claim_id", .expected = ripple::ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID}, + {.input = "did", .expected = ripple::ltDID}, + {.input = "oracle", .expected = ripple::ltORACLE}, + {.input = "credential", .expected = ripple::ltCREDENTIAL}, + {.input = "mpt_issuance", .expected = ripple::ltMPTOKEN_ISSUANCE}, + {.input = "mptoken", .expected = ripple::ltMPTOKEN}, + {.input = "permissioned_domain", .expected = ripple::ltPERMISSIONED_DOMAIN}, + {.input = "vault", .expected = ripple::ltVAULT}, + {.input = "delegate", .expected = ripple::ltDELEGATE}, + + // Using canonical name with exact match + {.input = "AccountRoot", .expected = ripple::ltACCOUNT_ROOT}, + {.input = "Check", .expected = ripple::ltCHECK}, + {.input = "DepositPreauth", .expected = ripple::ltDEPOSIT_PREAUTH}, + {.input = "Escrow", .expected = ripple::ltESCROW}, + {.input = "Offer", .expected = ripple::ltOFFER}, + {.input = "PayChannel", .expected = ripple::ltPAYCHAN}, + {.input = "SignerList", .expected = ripple::ltSIGNER_LIST}, + {.input = "RippleState", .expected = ripple::ltRIPPLE_STATE}, + {.input = "Ticket", .expected = ripple::ltTICKET}, + {.input = "NFTokenOffer", .expected = ripple::ltNFTOKEN_OFFER}, + {.input = "NFTokenPage", .expected = ripple::ltNFTOKEN_PAGE}, + {.input = "AMM", .expected = ripple::ltAMM}, + {.input = "Bridge", .expected = ripple::ltBRIDGE}, + {.input = "XChainOwnedClaimID", .expected = ripple::ltXCHAIN_OWNED_CLAIM_ID}, + {.input = "XChainOwnedCreateAccountClaimID", .expected = ripple::ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID}, + {.input = "DID", .expected = ripple::ltDID}, + {.input = "Oracle", .expected = ripple::ltORACLE}, + {.input = "Credential", .expected = ripple::ltCREDENTIAL}, + {.input = "MPTokenIssuance", .expected = ripple::ltMPTOKEN_ISSUANCE}, + {.input = "MPToken", .expected = ripple::ltMPTOKEN}, + {.input = "PermissionedDomain", .expected = ripple::ltPERMISSIONED_DOMAIN}, + {.input = "Vault", .expected = ripple::ltVAULT}, + {.input = "Delegate", .expected = ripple::ltDELEGATE} +}; + +static LedgerEntryTypeParam const kCASE_INSENSITIVE_TEST_CASES[] = { + // With canonical name in mixedcase + {.input = "mPtOKenIssuance", .expected = ripple::ltMPTOKEN_ISSUANCE}, + // With canonical name in lowercase + {.input = "mptokenissuance", .expected = ripple::ltMPTOKEN_ISSUANCE}, +}; + +static LedgerEntryTypeParam const kINVALID_TEST_CASES[] = { + {.input = "", .expected = ripple::ltANY}, + {.input = "1234", .expected = ripple::ltANY}, + {.input = "unknown", .expected = ripple::ltANY}, + // With RPC name with inexact match + {.input = "MPT_Issuance", .expected = ripple::ltANY} +}; + +class LedgerEntryTypeFromStrTest : public ::testing::TestWithParam {}; + +TEST_P(LedgerEntryTypeFromStrTest, GetLedgerEntryTypeFromStr) +{ + auto const& param = GetParam(); + auto const result = util::LedgerTypes::getLedgerEntryTypeFromStr(param.input); + EXPECT_EQ(result, param.expected) << param.input; +} + +INSTANTIATE_TEST_SUITE_P( + LedgerUtilsTests, + LedgerEntryTypeFromStrTest, + ::testing::ValuesIn([]() { + std::vector v; + v.insert(v.end(), std::begin(kCHAIN_TEST_CASES), std::end(kCHAIN_TEST_CASES)); + v.insert(v.end(), std::begin(kACCOUNT_OWNED_TEST_CASES), std::end(kACCOUNT_OWNED_TEST_CASES)); + v.insert(v.end(), std::begin(kCASE_INSENSITIVE_TEST_CASES), std::end(kCASE_INSENSITIVE_TEST_CASES)); + v.insert(v.end(), std::begin(kINVALID_TEST_CASES), std::end(kINVALID_TEST_CASES)); + return v; + }()) +); + +class AccountOwnedLedgerTypeFromStrTest : public ::testing::TestWithParam {}; + +TEST_P(AccountOwnedLedgerTypeFromStrTest, GetAccountOwnedLedgerTypeFromStr) +{ + auto const& param = GetParam(); + auto const result = util::LedgerTypes::getAccountOwnedLedgerTypeFromStr(param.input); + EXPECT_EQ(result, param.expected); +} + +INSTANTIATE_TEST_SUITE_P( + LedgerUtilsTests, + AccountOwnedLedgerTypeFromStrTest, + ::testing::ValuesIn([]() { + std::vector v; + v.insert(v.end(), std::begin(kACCOUNT_OWNED_TEST_CASES), std::end(kACCOUNT_OWNED_TEST_CASES)); + v.insert(v.end(), std::begin(kCASE_INSENSITIVE_TEST_CASES), std::end(kCASE_INSENSITIVE_TEST_CASES)); + v.insert(v.end(), std::begin(kINVALID_TEST_CASES), std::end(kINVALID_TEST_CASES)); + v.push_back({"amendments", ripple::ltANY}); // chain type should return ltANY + return v; + }()) +);