fix: account_objects returns error when filter does not make sense (#1579)

Fix #1488
This commit is contained in:
cyan317
2024-08-05 14:37:46 +01:00
committed by GitHub
parent 2a74a65b22
commit a7f34490b1
11 changed files with 233 additions and 130 deletions

View File

@@ -71,7 +71,7 @@ AccountObjectsHandler::process(AccountObjectsHandler::Input input, Context const
if (input.deletionBlockersOnly) {
typeFilter.emplace();
auto const& deletionBlockers = util::getDeletionBlockerLedgerTypes();
auto const& deletionBlockers = util::LedgerTypes::GetDeletionBlockerLedgerTypes();
typeFilter->reserve(deletionBlockers.size());
for (auto type : deletionBlockers) {
@@ -159,7 +159,7 @@ tag_invoke(boost::json::value_to_tag<AccountObjectsHandler::Input>, boost::json:
}
if (jsonObject.contains(JS(type)))
input.type = util::getLedgerEntryTypeFromStr(boost::json::value_to<std::string>(jv.at(JS(type))));
input.type = util::LedgerTypes::GetLedgerEntryTypeFromStr(boost::json::value_to<std::string>(jv.at(JS(type))));
if (jsonObject.contains(JS(limit)))
input.limit = jv.at(JS(limit)).as_int64();

View File

@@ -111,7 +111,7 @@ public:
static RpcSpecConstRef
spec([[maybe_unused]] uint32_t apiVersion)
{
auto const& ledgerTypeStrs = util::getLedgerEntryTypeStrs();
auto const& accountOwnedTypes = util::LedgerTypes::GetAccountOwnedLedgerTypeStrList();
static auto const rpcSpec = RpcSpec{
{JS(account), validation::Required{}, validation::CustomValidators::AccountValidator},
{JS(ledger_hash), validation::CustomValidators::Uint256HexStringValidator},
@@ -122,7 +122,7 @@ public:
modifiers::Clamp<int32_t>(LIMIT_MIN, LIMIT_MAX)},
{JS(type),
validation::Type<std::string>{},
validation::OneOf<std::string>(ledgerTypeStrs.cbegin(), ledgerTypeStrs.cend())},
validation::OneOf<std::string>(accountOwnedTypes.cbegin(), accountOwnedTypes.cend())},
{JS(marker), validation::CustomValidators::AccountMarkerValidator},
{JS(deletion_blockers_only), validation::Type<bool>{}},
};

View File

@@ -33,6 +33,7 @@
#include <boost/json/value_to.hpp>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/strHex.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/LedgerHeader.h>
#include <xrpl/protocol/STLedgerEntry.h>
@@ -195,11 +196,11 @@ tag_invoke(boost::json::value_to_tag<LedgerDataHandler::Input>, boost::json::val
if (jsonObject.contains("out_of_order"))
input.outOfOrder = jsonObject.at("out_of_order").as_bool();
if (jsonObject.contains("marker")) {
if (jsonObject.at("marker").is_string()) {
input.marker = ripple::uint256{boost::json::value_to<std::string>(jsonObject.at("marker")).data()};
if (jsonObject.contains(JS(marker))) {
if (jsonObject.at(JS(marker)).is_string()) {
input.marker = ripple::uint256{boost::json::value_to<std::string>(jsonObject.at(JS(marker))).data()};
} else {
input.diffMarker = jsonObject.at("marker").as_int64();
input.diffMarker = jsonObject.at(JS(marker)).as_int64();
}
}
@@ -215,7 +216,7 @@ tag_invoke(boost::json::value_to_tag<LedgerDataHandler::Input>, boost::json::val
}
if (jsonObject.contains(JS(type)))
input.type = util::getLedgerEntryTypeFromStr(boost::json::value_to<std::string>(jsonObject.at(JS(type))));
input.type = util::LedgerTypes::GetLedgerEntryTypeFromStr(boost::json::value_to<std::string>(jv.at(JS(type))));
return input;
}

View File

@@ -114,7 +114,7 @@ public:
static RpcSpecConstRef
spec([[maybe_unused]] uint32_t apiVersion)
{
auto const& ledgerTypeStrs = util::getLedgerEntryTypeStrs();
auto const& ledgerTypeStrs = util::LedgerTypes::GetLedgerEntryTypeStrList();
static auto const rpcSpec = RpcSpec{
{JS(binary), validation::Type<bool>{}},
{"out_of_order", validation::Type<bool>{}},

View File

@@ -39,7 +39,6 @@
#include <variant>
using namespace ripple;
using namespace ::rpc;
namespace rpc {
@@ -91,11 +90,11 @@ tag_invoke(boost::json::value_from_tag, boost::json::value& jv, NFTInfoHandler::
{JS(nft_id), output.nftID},
{JS(ledger_index), output.ledgerIndex},
{JS(owner), output.owner},
{"is_burned", output.isBurned},
{JS(is_burned), output.isBurned},
{JS(flags), output.flags},
{"transfer_fee", output.transferFee},
{JS(issuer), output.issuer},
{"nft_taxon", output.taxon},
{JS(nft_taxon), output.taxon},
{JS(nft_serial), output.serial},
{JS(validated), output.validated},
{JS(uri), output.uri},

View File

@@ -85,13 +85,13 @@ NFTsByIssuerHandler::process(NFTsByIssuerHandler::Input input, Context const& ct
nftJson[JS(nft_id)] = strHex(nft.tokenID);
nftJson[JS(ledger_index)] = nft.ledgerSequence;
nftJson[JS(owner)] = toBase58(nft.owner);
nftJson["is_burned"] = nft.isBurned;
nftJson[JS(is_burned)] = nft.isBurned;
nftJson[JS(uri)] = strHex(nft.uri);
nftJson[JS(flags)] = nft::getFlags(nft.tokenID);
nftJson["transfer_fee"] = nft::getTransferFee(nft.tokenID);
nftJson[JS(issuer)] = toBase58(nft::getIssuer(nft.tokenID));
nftJson["nft_taxon"] = nft::toUInt32(nft::getTaxon(nft.tokenID));
nftJson[JS(nft_taxon)] = nft::toUInt32(nft::getTaxon(nft.tokenID));
nftJson[JS(nft_serial)] = nft::getSerial(nft.tokenID);
output.nfts.push_back(nftJson);
@@ -118,7 +118,7 @@ tag_invoke(boost::json::value_from_tag, boost::json::value& jv, NFTsByIssuerHand
jv.as_object()[JS(marker)] = *(output.marker);
if (output.nftTaxon.has_value())
jv.as_object()["nft_taxon"] = *(output.nftTaxon);
jv.as_object()[JS(nft_taxon)] = *(output.nftTaxon);
}
NFTsByIssuerHandler::Input
@@ -143,8 +143,8 @@ tag_invoke(boost::json::value_to_tag<NFTsByIssuerHandler::Input>, boost::json::v
if (jsonObject.contains(JS(limit)))
input.limit = jsonObject.at(JS(limit)).as_int64();
if (jsonObject.contains("nft_taxon"))
input.nftTaxon = jsonObject.at("nft_taxon").as_int64();
if (jsonObject.contains(JS(nft_taxon)))
input.nftTaxon = jsonObject.at(JS(nft_taxon)).as_int64();
if (jsonObject.contains(JS(marker)))
input.marker = boost::json::value_to<std::string>(jsonObject.at(JS(marker)));

View File

@@ -96,7 +96,7 @@ public:
{
static auto const rpcSpec = RpcSpec{
{JS(issuer), validation::Required{}, validation::CustomValidators::AccountValidator},
{"nft_taxon", validation::Type<uint32_t>{}},
{JS(nft_taxon), validation::Type<uint32_t>{}},
{JS(ledger_hash), validation::CustomValidators::Uint256HexStringValidator},
{JS(ledger_index), validation::CustomValidators::LedgerIndexValidator},
{JS(limit),

View File

@@ -19,99 +19,29 @@
#include "util/LedgerUtils.hpp"
#include "rpc/JS.hpp"
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/jss.h>
#include <algorithm>
#include <iterator>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
namespace util {
namespace impl {
struct LedgerTypeAttributes {
ripple::LedgerEntryType type = ripple::ltANY;
bool deletionBlocker = false;
LedgerTypeAttributes(ripple::LedgerEntryType type, bool blocker = false) : type(type), deletionBlocker(blocker)
{
}
};
// Ledger entry type filter list, add new types here to support filtering for ledger_data and
// account_objects
static std::unordered_map<std::string, LedgerTypeAttributes> const LEDGER_TYPES_MAP{{
{JS(account), LedgerTypeAttributes(ripple::ltACCOUNT_ROOT)},
{JS(amendments), LedgerTypeAttributes(ripple::ltAMENDMENTS)},
{JS(check), LedgerTypeAttributes(ripple::ltCHECK, true)},
{JS(deposit_preauth), LedgerTypeAttributes(ripple::ltDEPOSIT_PREAUTH)},
{JS(directory), LedgerTypeAttributes(ripple::ltDIR_NODE)},
{JS(escrow), LedgerTypeAttributes(ripple::ltESCROW, true)},
{JS(fee), LedgerTypeAttributes(ripple::ltFEE_SETTINGS)},
{JS(hashes), LedgerTypeAttributes(ripple::ltLEDGER_HASHES)},
{JS(offer), LedgerTypeAttributes(ripple::ltOFFER)},
{JS(payment_channel), LedgerTypeAttributes(ripple::ltPAYCHAN, true)},
{JS(signer_list), LedgerTypeAttributes(ripple::ltSIGNER_LIST)},
{JS(state), LedgerTypeAttributes(ripple::ltRIPPLE_STATE, true)},
{JS(ticket), LedgerTypeAttributes(ripple::ltTICKET)},
{JS(nft_offer), LedgerTypeAttributes(ripple::ltNFTOKEN_OFFER)},
{JS(nft_page), LedgerTypeAttributes(ripple::ltNFTOKEN_PAGE, true)},
{JS(amm), LedgerTypeAttributes(ripple::ltAMM)},
{JS(bridge), LedgerTypeAttributes(ripple::ltBRIDGE, true)},
{JS(xchain_owned_claim_id), LedgerTypeAttributes(ripple::ltXCHAIN_OWNED_CLAIM_ID, true)},
{JS(xchain_owned_create_account_claim_id),
LedgerTypeAttributes(ripple::ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID, true)},
{JS(did), LedgerTypeAttributes(ripple::ltDID)},
{JS(oracle), LedgerTypeAttributes(ripple::ltORACLE)},
{JS(nunl), LedgerTypeAttributes(ripple::ltNEGATIVE_UNL)},
}};
} // namespace impl
std::unordered_set<std::string> const&
getLedgerEntryTypeStrs()
{
static std::unordered_set<std::string> const typesKeys = []() {
std::unordered_set<std::string> keys;
std::transform(
impl::LEDGER_TYPES_MAP.begin(),
impl::LEDGER_TYPES_MAP.end(),
std::inserter(keys, keys.begin()),
[](auto const& item) { return item.first; }
);
return keys;
}();
return typesKeys;
}
ripple::LedgerEntryType
getLedgerEntryTypeFromStr(std::string const& entryName)
LedgerTypes::GetLedgerEntryTypeFromStr(std::string const& entryName)
{
if (impl::LEDGER_TYPES_MAP.find(entryName) == impl::LEDGER_TYPES_MAP.end())
return ripple::ltANY;
return impl::LEDGER_TYPES_MAP.at(entryName).type;
}
std::vector<ripple::LedgerEntryType> const&
getDeletionBlockerLedgerTypes()
{
static std::vector<ripple::LedgerEntryType> const deletionBlockerLedgerTypes = []() {
// TODO: Move to std::ranges::views::filter when move to higher clang
auto ret = std::vector<ripple::LedgerEntryType>{};
std::for_each(impl::LEDGER_TYPES_MAP.cbegin(), impl::LEDGER_TYPES_MAP.cend(), [&ret](auto const& item) {
if (item.second.deletionBlocker)
ret.push_back(item.second.type);
static std::unordered_map<std::string, ripple::LedgerEntryType> typeMap = []() {
std::unordered_map<std::string, ripple::LedgerEntryType> map;
std::for_each(std::begin(LEDGER_TYPES), std::end(LEDGER_TYPES), [&map](auto const& item) {
map[item.name] = item.type;
});
return ret;
return map;
}();
return deletionBlockerLedgerTypes;
if (typeMap.find(entryName) == typeMap.end())
return ripple::ltANY;
return typeMap.at(entryName);
}
} // namespace util

View File

@@ -19,6 +19,8 @@
#pragma once
#include "rpc/JS.hpp"
#include <fmt/core.h>
#include <xrpl/basics/Slice.h>
#include <xrpl/basics/StringUtilities.h>
@@ -27,36 +29,164 @@
#include <xrpl/protocol/LedgerHeader.h>
#include <xrpl/protocol/jss.h>
#include <algorithm>
#include <array>
#include <string>
#include <unordered_set>
#include <vector>
namespace util {
/**
* @brief Returns a string set of all supported ledger entry types
*
* @return The set of ledger entry types
*/
std::unordered_set<std::string> const&
getLedgerEntryTypeStrs();
class LedgerTypes;
namespace impl {
class LedgerTypeAttribute {
enum class LedgerCategory {
Invalid,
AccountOwned, // The ledger object is owned by account
Chain, // The ledger object is shared across the chain
DeletionBlocker // The ledger object is owned by account and it blocks deletion
};
ripple::LedgerEntryType type = ripple::ltANY;
char const* name = nullptr;
LedgerCategory category = LedgerCategory::Invalid;
constexpr LedgerTypeAttribute(char const* name, ripple::LedgerEntryType type, LedgerCategory category)
: type(type), name(name), category(category)
{
}
public:
static constexpr LedgerTypeAttribute
ChainLedgerType(char const* name, ripple::LedgerEntryType type)
{
return LedgerTypeAttribute(name, type, LedgerCategory::Chain);
}
static constexpr LedgerTypeAttribute
AccountOwnedLedgerType(char const* name, ripple::LedgerEntryType type)
{
return LedgerTypeAttribute(name, type, LedgerCategory::AccountOwned);
}
static constexpr LedgerTypeAttribute
DeletionBlockerLedgerType(char const* name, ripple::LedgerEntryType type)
{
return LedgerTypeAttribute(name, type, LedgerCategory::DeletionBlocker);
}
friend class util::LedgerTypes;
};
} // namespace impl
/**
* @brief Return the ledger type from a string representation
* @brief A helper class that provides lists of different ledger type catagory.
*
* @param entryName The string representation of the ledger entry type
* @return The ledger entry type
*/
ripple::LedgerEntryType
getLedgerEntryTypeFromStr(std::string const& entryName);
class LedgerTypes {
using LedgerTypeAttribute = impl::LedgerTypeAttribute;
using LedgerTypeAttributeList = LedgerTypeAttribute[];
/**
* @brief Return the list of ledger entry types which will block the account deletion
*
* @return The list of ledger entry types
static constexpr LedgerTypeAttributeList const LEDGER_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),
// 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::DeletionBlockerLedgerType(
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::ChainLedgerType(JS(nunl), ripple::ltNEGATIVE_UNL),
};
public:
/**
* @brief Returns a list of all ledger entry type as string.
* @return A list of all ledger entry type as string.
*/
std::vector<ripple::LedgerEntryType> const&
getDeletionBlockerLedgerTypes();
static constexpr auto
GetLedgerEntryTypeStrList()
{
std::array<char const*, std::size(LEDGER_TYPES)> res{};
std::transform(std::begin(LEDGER_TYPES), std::end(LEDGER_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()
{
auto constexpr filter = [](auto const& item) {
return item.category != LedgerTypeAttribute::LedgerCategory::Chain;
};
auto constexpr accountOwnedCount = std::count_if(std::begin(LEDGER_TYPES), std::end(LEDGER_TYPES), filter);
std::array<char const*, accountOwnedCount> res{};
auto it = std::begin(res);
std::for_each(std::begin(LEDGER_TYPES), std::end(LEDGER_TYPES), [&](auto const& item) {
if (filter(item)) {
*it = item.name;
++it;
}
});
return res;
}
/**
* @brief Returns a list of all account deletion blocker's type as string.
*
* @return A list of all account deletion blocker's type as string.
*/
static constexpr auto
GetDeletionBlockerLedgerTypes()
{
auto constexpr filter = [](auto const& item) {
return item.category == LedgerTypeAttribute::LedgerCategory::DeletionBlocker;
};
auto constexpr deletionBlockersCount = std::count_if(std::begin(LEDGER_TYPES), std::end(LEDGER_TYPES), filter);
std::array<ripple::LedgerEntryType, deletionBlockersCount> res{};
auto it = std::begin(res);
std::for_each(std::begin(LEDGER_TYPES), std::end(LEDGER_TYPES), [&](auto const& item) {
if (filter(item)) {
*it = item.type;
++it;
}
});
return res;
}
/**
* @brief Returns the ripple::LedgerEntryType from the given string.
*
* @param entryName The name of the ledger entry type
* @return The ripple::LedgerEntryType of the given string, returns ltANY if not found.
*/
static ripple::LedgerEntryType
GetLedgerEntryTypeFromStr(std::string const& entryName);
};
/**
* @brief Deserializes a ripple::LedgerHeader from ripple::Slice of data.

View File

@@ -95,6 +95,12 @@ generateTestValuesForParametersTest()
"invalidParams",
"Invalid field 'type'."
},
AccountObjectsParamTestCaseBundle{
"TypeNotAccountOwned",
R"({"account":"rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", "type":"amendments"})",
"invalidParams",
"Invalid field 'type'."
},
AccountObjectsParamTestCaseBundle{
"LedgerHashInvalid",
R"({"account":"rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", "ledger_hash":"1"})",

View File

@@ -26,11 +26,12 @@
#include <algorithm>
#include <iterator>
#include <string_view>
TEST(LedgerUtilsTests, LedgerObjectTypeList)
{
auto const& types = util::getLedgerEntryTypeStrs();
static char const* typesList[] = {
auto constexpr types = util::LedgerTypes::GetLedgerEntryTypeStrList();
static constexpr char const* typesList[] = {
JS(account),
JS(amendments),
JS(check),
@@ -54,27 +55,63 @@ TEST(LedgerUtilsTests, LedgerObjectTypeList)
JS(oracle),
JS(nunl)
};
ASSERT_TRUE(std::size(typesList) == types.size());
EXPECT_TRUE(std::all_of(std::cbegin(typesList), std::cend(typesList), [&types](auto const& type) {
static_assert(std::size(typesList) == types.size());
static_assert(std::all_of(std::cbegin(typesList), std::cend(typesList), [&types](std::string_view type) {
return std::find(std::cbegin(types), std::cend(types), type) != std::cend(types);
}));
}
TEST(LedgerUtilsTests, AccountOwnedTypeList)
{
auto constexpr accountOwned = util::LedgerTypes::GetAccountOwnedLedgerTypeStrList();
static constexpr char const* correctTypes[] = {
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)
};
static_assert(std::size(correctTypes) == accountOwned.size());
static_assert(std::all_of(
std::cbegin(correctTypes),
std::cend(correctTypes),
[&accountOwned](std::string_view type) {
return std::find(std::cbegin(accountOwned), std::cend(accountOwned), type) != std::cend(accountOwned);
}
));
}
TEST(LedgerUtilsTests, StrToType)
{
EXPECT_EQ(util::getLedgerEntryTypeFromStr("mess"), ripple::ltANY);
EXPECT_EQ(util::getLedgerEntryTypeFromStr("tomato"), ripple::ltANY);
EXPECT_EQ(util::getLedgerEntryTypeFromStr("account"), ripple::ltACCOUNT_ROOT);
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);
auto const& types = util::getLedgerEntryTypeStrs();
auto constexpr types = util::LedgerTypes::GetLedgerEntryTypeStrList();
std::for_each(types.cbegin(), types.cend(), [](auto const& typeStr) {
EXPECT_NE(util::getLedgerEntryTypeFromStr(typeStr), ripple::ltANY);
EXPECT_NE(util::LedgerTypes::GetLedgerEntryTypeFromStr(typeStr), ripple::ltANY);
});
}
TEST(LedgerUtilsTests, DeletionBlockerTypes)
{
auto const& testedTypes = util::getDeletionBlockerLedgerTypes();
auto constexpr testedTypes = util::LedgerTypes::GetDeletionBlockerLedgerTypes();
static ripple::LedgerEntryType constexpr deletionBlockers[] = {
ripple::ltCHECK,
@@ -87,8 +124,8 @@ TEST(LedgerUtilsTests, DeletionBlockerTypes)
ripple::ltBRIDGE
};
ASSERT_TRUE(std::size(deletionBlockers) == testedTypes.size());
EXPECT_TRUE(std::any_of(testedTypes.cbegin(), testedTypes.cend(), [](auto const& type) {
static_assert(std::size(deletionBlockers) == testedTypes.size());
static_assert(std::any_of(testedTypes.cbegin(), testedTypes.cend(), [](auto const& type) {
return std::find(std::cbegin(deletionBlockers), std::cend(deletionBlockers), type) !=
std::cend(deletionBlockers);
}));