From 6edf03c152d3772e65d8f62e2919dadc68b60a93 Mon Sep 17 00:00:00 2001 From: Bronek Kozicki Date: Sat, 23 Mar 2024 04:28:16 +0900 Subject: [PATCH] Write improved `forAllApiVersions` used in NetworkOPs (#4833) --- Builds/CMake/RippledCore.cmake | 7 +- Builds/levelization/results/ordering.txt | 1 - src/ripple/app/ledger/BookListeners.cpp | 5 +- src/ripple/app/ledger/BookListeners.h | 2 +- src/ripple/app/ledger/OrderBookDB.h | 2 +- src/ripple/app/ledger/impl/LedgerToJson.cpp | 4 +- src/ripple/app/misc/NetworkOPs.cpp | 73 +- src/ripple/json/MultivarJson.h | 150 --- src/ripple/protocol/ApiVersion.h | 119 ++ src/ripple/protocol/MultiApiJson.h | 247 ++++ src/ripple/rpc/impl/RPCHelpers.h | 39 +- src/ripple/rpc/impl/ServerHandler.cpp | 2 +- src/test/app/PayChan_test.cpp | 7 +- src/test/json/MultivarJson_test.cpp | 387 ------ src/test/jtx/Env.h | 34 - src/test/protocol/ApiVersion_test.cpp | 75 ++ src/test/protocol/MultiApiJson_test.cpp | 1202 +++++++++++++++++++ src/test/rpc/AccountTx_test.cpp | 2 +- src/test/rpc/GatewayBalances_test.cpp | 7 +- src/test/rpc/LedgerRPC_test.cpp | 2 +- src/test/rpc/LedgerRequestRPC_test.cpp | 2 +- src/test/rpc/NoRipple_test.cpp | 7 +- src/test/rpc/RPCCall_test.cpp | 3 +- src/test/rpc/TransactionEntry_test.cpp | 2 +- src/test/rpc/Transaction_test.cpp | 4 +- src/test/rpc/Version_test.cpp | 28 +- 26 files changed, 1724 insertions(+), 689 deletions(-) delete mode 100644 src/ripple/json/MultivarJson.h create mode 100644 src/ripple/protocol/ApiVersion.h create mode 100644 src/ripple/protocol/MultiApiJson.h delete mode 100644 src/test/json/MultivarJson_test.cpp create mode 100644 src/test/protocol/ApiVersion_test.cpp create mode 100644 src/test/protocol/MultiApiJson_test.cpp diff --git a/Builds/CMake/RippledCore.cmake b/Builds/CMake/RippledCore.cmake index 75550d89ed..f826c42813 100644 --- a/Builds/CMake/RippledCore.cmake +++ b/Builds/CMake/RippledCore.cmake @@ -218,7 +218,6 @@ install ( install ( FILES src/ripple/json/JsonPropertyStream.h - src/ripple/json/MultivarJson.h src/ripple/json/Object.h src/ripple/json/Output.h src/ripple/json/Writer.h @@ -237,6 +236,7 @@ install ( src/ripple/protocol/AccountID.h src/ripple/protocol/AMMCore.h src/ripple/protocol/AmountConversions.h + src/ripple/protocol/ApiVersion.h src/ripple/protocol/Book.h src/ripple/protocol/BuildInfo.h src/ripple/protocol/ErrorCodes.h @@ -252,6 +252,8 @@ install ( src/ripple/protocol/KnownFormats.h src/ripple/protocol/LedgerFormats.h src/ripple/protocol/LedgerHeader.h + src/ripple/protocol/MultiApiJson.h + src/ripple/protocol/NFTSyntheticSerializer.h src/ripple/protocol/NFTokenID.h src/ripple/protocol/NFTokenOfferID.h src/ripple/protocol/NFTSyntheticSerializer.h @@ -944,7 +946,6 @@ if (tests) src/test/json/Output_test.cpp src/test/json/Writer_test.cpp src/test/json/json_value_test.cpp - src/test/json/MultivarJson_test.cpp #[===============================[ test sources: subdir: jtx @@ -1042,11 +1043,13 @@ if (tests) test sources: subdir: protocol #]===============================] + src/test/protocol/ApiVersion_test.cpp src/test/protocol/BuildInfo_test.cpp src/test/protocol/InnerObjectFormats_test.cpp src/test/protocol/Issue_test.cpp src/test/protocol/Hooks_test.cpp src/test/protocol/Memo_test.cpp + src/test/protocol/MultiApiJson_test.cpp src/test/protocol/PublicKey_test.cpp src/test/protocol/Quality_test.cpp src/test/protocol/STAccount_test.cpp diff --git a/Builds/levelization/results/ordering.txt b/Builds/levelization/results/ordering.txt index c03e5ca531..ed54065d03 100644 --- a/Builds/levelization/results/ordering.txt +++ b/Builds/levelization/results/ordering.txt @@ -132,7 +132,6 @@ test.csf > ripple.json test.csf > ripple.protocol test.json > ripple.beast test.json > ripple.json -test.json > ripple.rpc test.json > test.jtx test.jtx > ripple.app test.jtx > ripple.basics diff --git a/src/ripple/app/ledger/BookListeners.cpp b/src/ripple/app/ledger/BookListeners.cpp index bbc0058bc7..3c7e013e1d 100644 --- a/src/ripple/app/ledger/BookListeners.cpp +++ b/src/ripple/app/ledger/BookListeners.cpp @@ -54,8 +54,9 @@ BookListeners::publish( // Only publish jvObj if this is the first occurence if (havePublished.emplace(p->getSeq()).second) { - p->send( - jvObj.select(apiVersionSelector(p->getApiVersion())), true); + jvObj.visit( + p->getApiVersion(), // + [&](Json::Value const& jv) { p->send(jv, true); }); } ++it; } diff --git a/src/ripple/app/ledger/BookListeners.h b/src/ripple/app/ledger/BookListeners.h index 748378a12b..605cf6dc6a 100644 --- a/src/ripple/app/ledger/BookListeners.h +++ b/src/ripple/app/ledger/BookListeners.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_APP_LEDGER_BOOKLISTENERS_H_INCLUDED #define RIPPLE_APP_LEDGER_BOOKLISTENERS_H_INCLUDED -#include #include +#include #include #include diff --git a/src/ripple/app/ledger/OrderBookDB.h b/src/ripple/app/ledger/OrderBookDB.h index b072bafb0c..45705a6157 100644 --- a/src/ripple/app/ledger/OrderBookDB.h +++ b/src/ripple/app/ledger/OrderBookDB.h @@ -23,7 +23,7 @@ #include #include #include -#include +#include #include diff --git a/src/ripple/app/ledger/impl/LedgerToJson.cpp b/src/ripple/app/ledger/impl/LedgerToJson.cpp index 9cef4e65b5..1310dd13a6 100644 --- a/src/ripple/app/ledger/impl/LedgerToJson.cpp +++ b/src/ripple/app/ledger/impl/LedgerToJson.cpp @@ -168,9 +168,7 @@ fillJsonTx( if (validated) { auto const seq = fill.ledger.seq(); - txJson[jss::ledger_index] = (fill.context->apiVersion > 1) - ? Json::Value(seq) - : Json::Value(std::to_string(seq)); + txJson[jss::ledger_index] = seq; if (fill.closeTime) txJson[jss::close_time_iso] = to_string_iso(*fill.closeTime); } diff --git a/src/ripple/app/misc/NetworkOPs.cpp b/src/ripple/app/misc/NetworkOPs.cpp index 3bd6742357..cd85bc9e4e 100644 --- a/src/ripple/app/misc/NetworkOPs.cpp +++ b/src/ripple/app/misc/NetworkOPs.cpp @@ -52,7 +52,6 @@ #include #include #include -#include #include #include #include @@ -60,6 +59,7 @@ #include #include #include +#include #include #include #include @@ -68,7 +68,6 @@ #include #include #include -#include #include #include @@ -2213,11 +2212,11 @@ NetworkOPsImp::pubValidation(std::shared_ptr const& val) // NOTE Use MultiApiJson to publish two slightly different JSON objects // for consumers supporting different API versions MultiApiJson multiObj{jvObj}; - visit( - multiObj, // - [](Json::Value& jvTx, unsigned int apiVersion) { + multiObj.visit( + RPC::apiVersion<1>, // + [](Json::Value& jvTx) { // Type conversion for older API versions to string - if (jvTx.isMember(jss::ledger_index) && apiVersion < 2) + if (jvTx.isMember(jss::ledger_index)) { jvTx[jss::ledger_index] = std::to_string(jvTx[jss::ledger_index].asUInt()); @@ -2229,9 +2228,9 @@ NetworkOPsImp::pubValidation(std::shared_ptr const& val) { if (auto p = i->second.lock()) { - p->send( - multiObj.select(apiVersionSelector(p->getApiVersion())), - true); + multiObj.visit( + p->getApiVersion(), // + [&](Json::Value const& jv) { p->send(jv, true); }); ++i; } else @@ -2769,8 +2768,9 @@ NetworkOPsImp::pubProposedTransaction( if (p) { - p->send( - jvObj.select(apiVersionSelector(p->getApiVersion())), true); + jvObj.visit( + p->getApiVersion(), // + [&](Json::Value const& jv) { p->send(jv, true); }); ++it; } else @@ -3167,13 +3167,14 @@ NetworkOPsImp::transJson( std::string const hash = to_string(transaction->getTransactionID()); MultiApiJson multiObj{jvObj}; - visit( - multiObj, // - [&](Json::Value& jvTx, unsigned int apiVersion) { + forAllApiVersions( + multiObj.visit(), // + [&]( + Json::Value& jvTx, std::integral_constant) { RPC::insertDeliverMax( - jvTx[jss::transaction], transaction->getTxnType(), apiVersion); + jvTx[jss::transaction], transaction->getTxnType(), Version); - if (apiVersion > 1) + if constexpr (Version > 1) { jvTx[jss::tx_json] = jvTx.removeMember(jss::transaction); jvTx[jss::hash] = hash; @@ -3210,8 +3211,9 @@ NetworkOPsImp::pubValidatedTransaction( if (p) { - p->send( - jvObj.select(apiVersionSelector(p->getApiVersion())), true); + jvObj.visit( + p->getApiVersion(), // + [&](Json::Value const& jv) { p->send(jv, true); }); ++it; } else @@ -3226,8 +3228,9 @@ NetworkOPsImp::pubValidatedTransaction( if (p) { - p->send( - jvObj.select(apiVersionSelector(p->getApiVersion())), true); + jvObj.visit( + p->getApiVersion(), // + [&](Json::Value const& jv) { p->send(jv, true); }); ++it; } else @@ -3347,9 +3350,9 @@ NetworkOPsImp::pubAccountTransaction( for (InfoSub::ref isrListener : notify) { - isrListener->send( - jvObj.select(apiVersionSelector(isrListener->getApiVersion())), - true); + jvObj.visit( + isrListener->getApiVersion(), // + [&](Json::Value const& jv) { isrListener->send(jv, true); }); } if (last) @@ -3366,9 +3369,9 @@ NetworkOPsImp::pubAccountTransaction( jvObj.set(jss::account_history_tx_index, index->forwardTxIndex_++); - info.sink_->send( - jvObj.select(apiVersionSelector(info.sink_->getApiVersion())), - true); + jvObj.visit( + info.sink_->getApiVersion(), // + [&](Json::Value const& jv) { info.sink_->send(jv, true); }); } } } @@ -3426,9 +3429,9 @@ NetworkOPsImp::pubProposedAccountTransaction( MultiApiJson jvObj = transJson(tx, result, false, ledger, std::nullopt); for (InfoSub::ref isrListener : notify) - isrListener->send( - jvObj.select(apiVersionSelector(isrListener->getApiVersion())), - true); + jvObj.visit( + isrListener->getApiVersion(), // + [&](Json::Value const& jv) { isrListener->send(jv, true); }); assert( jvObj.isMember(jss::account_history_tx_stream) == @@ -3439,9 +3442,9 @@ NetworkOPsImp::pubProposedAccountTransaction( if (index->forwardTxIndex_ == 0 && !index->haveHistorical_) jvObj.set(jss::account_history_tx_first, true); jvObj.set(jss::account_history_tx_index, index->forwardTxIndex_++); - info.sink_->send( - jvObj.select(apiVersionSelector(info.sink_->getApiVersion())), - true); + jvObj.visit( + info.sink_->getApiVersion(), // + [&](Json::Value const& jv) { info.sink_->send(jv, true); }); } } } @@ -3647,9 +3650,9 @@ NetworkOPsImp::addAccountHistoryJob(SubAccountHistoryInfoWeak subInfo) bool unsubscribe) -> bool { if (auto sptr = subInfo.sinkWptr_.lock()) { - sptr->send( - jvObj.select(apiVersionSelector(sptr->getApiVersion())), - true); + jvObj.visit( + sptr->getApiVersion(), // + [&](Json::Value const& jv) { sptr->send(jv, true); }); if (unsubscribe) unsubAccountHistory(sptr, accountId, false); diff --git a/src/ripple/json/MultivarJson.h b/src/ripple/json/MultivarJson.h deleted file mode 100644 index d946bc6c03..0000000000 --- a/src/ripple/json/MultivarJson.h +++ /dev/null @@ -1,150 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2023 Ripple Labs Inc. - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ -//============================================================================== - -#ifndef RIPPLE_JSON_MULTIVARJSON_H_INCLUDED -#define RIPPLE_JSON_MULTIVARJSON_H_INCLUDED - -#include - -#include -#include -#include -#include -#include -#include - -namespace ripple { -template -struct MultivarJson -{ - std::array val = {}; - constexpr static std::size_t size = Size; - - explicit MultivarJson(Json::Value const& init = {}) - { - if (init == Json::Value{}) - return; // All elements are already default-initialized - for (auto& v : val) - v = init; - } - - Json::Value const& - select(auto&& selector) const - requires std::same_as - { - auto const index = selector(); - assert(index < size); - return val[index]; - } - - void - set(const char* key, - auto const& - v) requires std::constructible_from - { - for (auto& a : this->val) - a[key] = v; - } - - // Intentionally not using class enum here, MultivarJson is scope enough - enum IsMemberResult : int { none = 0, some, all }; - - [[nodiscard]] IsMemberResult - isMember(const char* key) const - { - int count = 0; - for (auto& a : this->val) - if (a.isMember(key)) - count += 1; - - return (count == 0 ? none : (count < size ? some : all)); - } -}; - -// Wrapper for Json for all supported API versions. -using MultiApiJson = MultivarJson<3>; - -/* - -NOTE: - -If a future API version change adds another possible format, change the size of -`MultiApiJson`, and update `apiVersionSelector()` to return the appropriate -selection value for the new `apiVersion` and higher. - -The more different JSON formats we support, the more CPU cycles we need to -prepare JSON for different API versions e.g. when publishing streams to -`subscribe` clients. Hence it is desirable to keep MultiApiJson small and -instead fully deprecate and remove support for old API versions. For example, if -we removed support for API version 1 and added a different format for API -version 3, the `apiVersionSelector` would change to -`static_cast(apiVersion > 2)` - -Such hypothetical change should correspond with change in RPCHelpers.h -`apiMinimumSupportedVersion = 2;` - -*/ - -// Helper to create appropriate selector for indexing MultiApiJson by apiVersion -constexpr auto -apiVersionSelector(unsigned int apiVersion) noexcept -{ - return [apiVersion]() constexpr - { - return static_cast( - apiVersion <= 1 // - ? 0 // - : (apiVersion <= 2 // - ? 1 // - : 2)); - }; -} - -// Helper to execute a callback for every version. Want both min and max version -// provided explicitly, so user will know to do update `size` when they change -template < - unsigned int minVer, - unsigned int maxVer, - std::size_t size, - typename Fn> - requires // - (maxVer >= minVer) && // - (size == maxVer + 1 - minVer) && // - (apiVersionSelector(minVer)() == 0) && // - (apiVersionSelector(maxVer)() + 1 == size) && // - requires(Json::Value& json, Fn fn) -{ - fn(json, static_cast(1)); -} -void -visit(MultivarJson& json, Fn fn) -{ - [&](std::index_sequence) - { - static_assert(((apiVersionSelector(minVer + offset)() >= 0) && ...)); - static_assert(((apiVersionSelector(minVer + offset)() < size) && ...)); - (fn(json.val[apiVersionSelector(minVer + offset)()], minVer + offset), - ...); - } - (std::make_index_sequence{}); -} - -} // namespace ripple - -#endif diff --git a/src/ripple/protocol/ApiVersion.h b/src/ripple/protocol/ApiVersion.h new file mode 100644 index 0000000000..1cd03b0651 --- /dev/null +++ b/src/ripple/protocol/ApiVersion.h @@ -0,0 +1,119 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2023 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_PROTOCOL_APIVERSION_H_INCLUDED +#define RIPPLE_PROTOCOL_APIVERSION_H_INCLUDED + +#include +#include +#include + +namespace ripple { + +/** + * API version numbers used in later API versions + * + * Requests with a version number in the range + * [apiMinimumSupportedVersion, apiMaximumSupportedVersion] + * are supported. + * + * If [beta_rpc_api] is enabled in config, the version numbers + * in the range [apiMinimumSupportedVersion, apiBetaVersion] + * are supported. + * + * Network Requests without explicit version numbers use + * apiVersionIfUnspecified. apiVersionIfUnspecified is 1, + * because all the RPC requests with a version >= 2 must + * explicitly specify the version in the requests. + * Note that apiVersionIfUnspecified will be lower than + * apiMinimumSupportedVersion when we stop supporting API + * version 1. + * + * Command line Requests use apiCommandLineVersion. + */ + +namespace RPC { + +template +constexpr static std::integral_constant apiVersion = {}; + +constexpr static auto apiInvalidVersion = apiVersion<0>; +constexpr static auto apiMinimumSupportedVersion = apiVersion<1>; +constexpr static auto apiMaximumSupportedVersion = apiVersion<2>; +constexpr static auto apiVersionIfUnspecified = apiVersion<1>; +constexpr static auto apiCommandLineVersion = + apiVersion<1>; // TODO Bump to 2 later +constexpr static auto apiBetaVersion = apiVersion<3>; +constexpr static auto apiMaximumValidVersion = apiBetaVersion; + +static_assert(apiInvalidVersion < apiMinimumSupportedVersion); +static_assert( + apiVersionIfUnspecified >= apiMinimumSupportedVersion && + apiVersionIfUnspecified <= apiMaximumSupportedVersion); +static_assert( + apiCommandLineVersion >= apiMinimumSupportedVersion && + apiCommandLineVersion <= apiMaximumSupportedVersion); +static_assert(apiMaximumSupportedVersion >= apiMinimumSupportedVersion); +static_assert(apiBetaVersion >= apiMaximumSupportedVersion); +static_assert(apiMaximumValidVersion >= apiMaximumSupportedVersion); + +} // namespace RPC + +template + void + forApiVersions(Fn const& fn, Args&&... args) requires // + (maxVer >= minVer) && // + (minVer >= RPC::apiMinimumSupportedVersion) && // + (RPC::apiMaximumValidVersion >= maxVer) && + requires +{ + fn(std::integral_constant{}, + std::forward(args)...); + fn(std::integral_constant{}, + std::forward(args)...); +} +{ + constexpr auto size = maxVer + 1 - minVer; + [&](std::index_sequence) + { + (((void)fn( + std::integral_constant{}, + std::forward(args)...)), + ...); + } + (std::make_index_sequence{}); +} + +template +void +forAllApiVersions(Fn const& fn, Args&&... args) requires requires +{ + forApiVersions< + RPC::apiMinimumSupportedVersion, + RPC::apiMaximumValidVersion>(fn, std::forward(args)...); +} +{ + forApiVersions< + RPC::apiMinimumSupportedVersion, + RPC::apiMaximumValidVersion>(fn, std::forward(args)...); +} + +} // namespace ripple + +#endif diff --git a/src/ripple/protocol/MultiApiJson.h b/src/ripple/protocol/MultiApiJson.h new file mode 100644 index 0000000000..b6d1843ae6 --- /dev/null +++ b/src/ripple/protocol/MultiApiJson.h @@ -0,0 +1,247 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2023 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_JSON_MULTIAPIJSON_H_INCLUDED +#define RIPPLE_JSON_MULTIAPIJSON_H_INCLUDED + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ripple { + +namespace detail { +template +constexpr bool is_integral_constant = false; +template +constexpr bool is_integral_constant&> = true; +template +constexpr bool is_integral_constant const&> = true; + +template +concept some_integral_constant = detail::is_integral_constant; + +// This class is designed to wrap a collection of _almost_ identical Json::Value +// objects, indexed by version (i.e. there is some mapping of version to object +// index). It is used e.g. when we need to publish JSON data to users supporting +// different API versions. We allow manipulation and inspection of all objects +// at once with `isMember` and `set`, and also individual inspection and updates +// of an object selected by the user by version, using `visitor_t` nested type. +template +struct MultiApiJson +{ + static_assert(MinVer <= MaxVer); + + static constexpr auto + valid(unsigned int v) noexcept -> bool + { + return v >= MinVer && v <= MaxVer; + } + + static constexpr auto + index(unsigned int v) noexcept -> std::size_t + { + return (v < MinVer) ? 0 : static_cast(v - MinVer); + } + + constexpr static std::size_t size = MaxVer + 1 - MinVer; + std::array val = {}; + + explicit MultiApiJson(Json::Value const& init = {}) + { + if (init == Json::Value{}) + return; // All elements are already default-initialized + for (auto& v : val) + v = init; + } + + void + set(const char* key, + auto const& + v) requires std::constructible_from + { + for (auto& a : this->val) + a[key] = v; + } + + // Intentionally not using class enum here, MultivarJson is scope enough + enum IsMemberResult : int { none = 0, some, all }; + + [[nodiscard]] IsMemberResult + isMember(const char* key) const + { + int count = 0; + for (auto& a : this->val) + if (a.isMember(key)) + count += 1; + + return (count == 0 ? none : (count < size ? some : all)); + } + + static constexpr struct visitor_t final + { + // integral_constant version, extra arguments + template < + typename Json, + unsigned int Version, + typename... Args, + typename Fn> + requires std::same_as, MultiApiJson> auto + operator()( + Json& json, + std::integral_constant const version, + Fn fn, + Args&&... args) const + -> std::invoke_result_t< + Fn, + decltype(json.val[0]), + std::integral_constant, + Args&&...> + { + static_assert( + valid(Version) && index(Version) >= 0 && index(Version) < size); + return std::invoke( + fn, + json.val[index(Version)], + version, + std::forward(args)...); + } + + // integral_constant version, Json only + template + requires std::same_as, MultiApiJson> auto + operator()( + Json& json, + std::integral_constant const, + Fn fn) const -> std::invoke_result_t + { + static_assert( + valid(Version) && index(Version) >= 0 && index(Version) < size); + return std::invoke(fn, json.val[index(Version)]); + } + + // unsigned int version, extra arguments + template < + typename Json, + typename Version, + typename... Args, + typename Fn> + requires(!some_integral_constant) && + std::convertible_to&& std::same_as< + std::remove_cvref_t, + MultiApiJson> auto + operator()(Json& json, Version version, Fn fn, Args&&... args) const + -> std:: + invoke_result_t + { + assert( + valid(version) && index(version) >= 0 && index(version) < size); + return std::invoke( + fn, + json.val[index(version)], + version, + std::forward(args)...); + } + + // unsigned int version, Json only + template + requires(!some_integral_constant) && + std::convertible_to&& std:: + same_as, MultiApiJson> auto + operator()(Json& json, Version version, Fn fn) const + -> std::invoke_result_t + { + assert( + valid(version) && index(version) >= 0 && index(version) < size); + return std::invoke(fn, json.val[index(version)]); + } + } visitor = {}; + + auto + visit() + { + return [self = this](auto... args) requires requires + { + visitor( + std::declval(), + std::declval()...); + } + { + return visitor(*self, std::forward(args)...); + }; + } + + auto + visit() const + { + return [self = this](auto... args) requires requires + { + visitor( + std::declval(), + std::declval()...); + } + { + return visitor(*self, std::forward(args)...); + }; + } + + template + auto + visit(Args... args) + -> std::invoke_result_t requires( + sizeof...(args) > 0) && + requires + { + visitor(*this, std::forward(args)...); + } + { + return visitor(*this, std::forward(args)...); + } + + template + auto + visit(Args... args) const -> std:: + invoke_result_t requires( + sizeof...(args) > 0) && + requires + { + visitor(*this, std::forward(args)...); + } + { + return visitor(*this, std::forward(args)...); + } +}; + +} // namespace detail + +// Wrapper for Json for all supported API versions. +using MultiApiJson = detail:: + MultiApiJson; + +} // namespace ripple + +#endif diff --git a/src/ripple/rpc/impl/RPCHelpers.h b/src/ripple/rpc/impl/RPCHelpers.h index 8b6460cd45..e003773e50 100644 --- a/src/ripple/rpc/impl/RPCHelpers.h +++ b/src/ripple/rpc/impl/RPCHelpers.h @@ -22,6 +22,7 @@ #include #include +#include #include #include @@ -30,6 +31,7 @@ #include #include #include + #include #include @@ -207,41 +209,6 @@ extern beast::SemanticVersion const firstVersion; extern beast::SemanticVersion const goodVersion; extern beast::SemanticVersion const lastVersion; -/** - * API version numbers used in later API versions - * - * Requests with a version number in the range - * [apiMinimumSupportedVersion, apiMaximumSupportedVersion] - * are supported. - * - * If [beta_rpc_api] is enabled in config, the version numbers - * in the range [apiMinimumSupportedVersion, apiBetaVersion] - * are supported. - * - * Network Requests without explicit version numbers use - * apiVersionIfUnspecified. apiVersionIfUnspecified is 1, - * because all the RPC requests with a version >= 2 must - * explicitly specify the version in the requests. - * Note that apiVersionIfUnspecified will be lower than - * apiMinimumSupportedVersion when we stop supporting API - * version 1. - * - * Command line Requests use apiMaximumSupportedVersion. - */ - -constexpr unsigned int apiInvalidVersion = 0; -constexpr unsigned int apiVersionIfUnspecified = 1; -constexpr unsigned int apiMinimumSupportedVersion = 1; -constexpr unsigned int apiMaximumSupportedVersion = 2; -constexpr unsigned int apiCommandLineVersion = 1; // TODO Bump to 2 later -constexpr unsigned int apiBetaVersion = 3; -constexpr unsigned int apiMaximumValidVersion = apiBetaVersion; - -static_assert(apiMinimumSupportedVersion >= apiVersionIfUnspecified); -static_assert(apiMaximumSupportedVersion >= apiMinimumSupportedVersion); -static_assert(apiBetaVersion >= apiMaximumSupportedVersion); -static_assert(apiMaximumValidVersion >= apiMaximumSupportedVersion); - template void setVersion(Object& parent, unsigned int apiVersion, bool betaEnabled) @@ -256,7 +223,7 @@ setVersion(Object& parent, unsigned int apiVersion, bool betaEnabled) } else { - object[jss::first] = apiMinimumSupportedVersion; + object[jss::first] = apiMinimumSupportedVersion.value; object[jss::last] = betaEnabled ? apiBetaVersion : apiMaximumSupportedVersion; } diff --git a/src/ripple/rpc/impl/ServerHandler.cpp b/src/ripple/rpc/impl/ServerHandler.cpp index cd18306fff..d643bdb633 100644 --- a/src/ripple/rpc/impl/ServerHandler.cpp +++ b/src/ripple/rpc/impl/ServerHandler.cpp @@ -643,7 +643,7 @@ ServerHandler::processRequest( continue; } - auto apiVersion = RPC::apiVersionIfUnspecified; + unsigned apiVersion = RPC::apiVersionIfUnspecified; if (jsonRPC.isMember(jss::params) && jsonRPC[jss::params].isArray() && jsonRPC[jss::params].size() > 0 && jsonRPC[jss::params][0u].isObject()) diff --git a/src/test/app/PayChan_test.cpp b/src/test/app/PayChan_test.cpp index 02a13de5c2..a479e43b17 100644 --- a/src/test/app/PayChan_test.cpp +++ b/src/test/app/PayChan_test.cpp @@ -1088,10 +1088,7 @@ struct PayChan_test : public beast::unit_test::suite args[jss::amount] = 51110000; // test for all api versions - for (auto apiVersion = RPC::apiMinimumSupportedVersion; - apiVersion <= RPC::apiBetaVersion; - ++apiVersion) - { + forAllApiVersions([&, this](unsigned apiVersion) { testcase( "PayChan Channel_Auth RPC Api " + std::to_string(apiVersion)); args[jss::api_version] = apiVersion; @@ -1101,7 +1098,7 @@ struct PayChan_test : public beast::unit_test::suite args.toStyledString())[jss::result]; auto const error = apiVersion < 2u ? "invalidParams" : "badKeyType"; BEAST_EXPECT(rs[jss::error] == error); - } + }); } void diff --git a/src/test/json/MultivarJson_test.cpp b/src/test/json/MultivarJson_test.cpp deleted file mode 100644 index 63b4f8ee39..0000000000 --- a/src/test/json/MultivarJson_test.cpp +++ /dev/null @@ -1,387 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/XRPLF/rippled/ - Copyright (c) 2023 Ripple Labs Inc. - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ -//============================================================================== - -#include -#include - -#include -#include "ripple/beast/unit_test/suite.hpp" -#include "ripple/json/json_value.h" -#include -#include -#include -#include - -namespace ripple { -namespace test { - -struct MultivarJson_test : beast::unit_test::suite -{ - static auto - makeJson(const char* key, int val) - { - Json::Value obj1(Json::objectValue); - obj1[key] = val; - return obj1; - }; - - void - run() override - { - Json::Value const obj1 = makeJson("value", 1); - Json::Value const obj2 = makeJson("value", 2); - Json::Value const obj3 = makeJson("value", 3); - Json::Value const jsonNull{}; - - MultivarJson<3> subject{}; - static_assert(sizeof(subject) == sizeof(subject.val)); - static_assert(subject.size == subject.val.size()); - static_assert( - std::is_same_v>); - - BEAST_EXPECT(subject.val.size() == 3); - BEAST_EXPECT( - (subject.val == - std::array{jsonNull, jsonNull, jsonNull})); - - subject.val[0] = obj1; - subject.val[1] = obj2; - - { - testcase("default copy construction / assignment"); - - MultivarJson<3> x{subject}; - - BEAST_EXPECT(x.val.size() == subject.val.size()); - BEAST_EXPECT(x.val[0] == subject.val[0]); - BEAST_EXPECT(x.val[1] == subject.val[1]); - BEAST_EXPECT(x.val[2] == subject.val[2]); - BEAST_EXPECT(x.val == subject.val); - BEAST_EXPECT(&x.val[0] != &subject.val[0]); - BEAST_EXPECT(&x.val[1] != &subject.val[1]); - BEAST_EXPECT(&x.val[2] != &subject.val[2]); - - MultivarJson<3> y; - BEAST_EXPECT((y.val == std::array{})); - y = subject; - BEAST_EXPECT(y.val == subject.val); - BEAST_EXPECT(&y.val[0] != &subject.val[0]); - BEAST_EXPECT(&y.val[1] != &subject.val[1]); - BEAST_EXPECT(&y.val[2] != &subject.val[2]); - - y = std::move(x); - BEAST_EXPECT(y.val == subject.val); - BEAST_EXPECT(&y.val[0] != &subject.val[0]); - BEAST_EXPECT(&y.val[1] != &subject.val[1]); - BEAST_EXPECT(&y.val[2] != &subject.val[2]); - } - - { - testcase("select"); - - BEAST_EXPECT( - subject.select([]() -> std::size_t { return 0; }) == obj1); - BEAST_EXPECT( - subject.select([]() -> std::size_t { return 1; }) == obj2); - BEAST_EXPECT( - subject.select([]() -> std::size_t { return 2; }) == jsonNull); - - // Tests of requires clause - these are expected to match - static_assert([](auto&& v) { - return requires - { - v.select([]() -> std::size_t { return 0; }); - }; - }(subject)); - static_assert([](auto&& v) { - return requires - { - v.select([]() constexpr->std::size_t { return 0; }); - }; - }(subject)); - static_assert([](auto&& v) { - return requires - { - v.select([]() mutable -> std::size_t { return 0; }); - }; - }(subject)); - - // Tests of requires clause - these are expected NOT to match - static_assert([](auto&& a) { - return !requires - { - subject.select([]() -> int { return 0; }); - }; - }(subject)); - static_assert([](auto&& v) { - return !requires - { - v.select([]() -> void {}); - }; - }(subject)); - static_assert([](auto&& v) { - return !requires - { - v.select([]() -> bool { return false; }); - }; - }(subject)); - } - - { - testcase("set"); - - auto x = MultivarJson<2>{Json::objectValue}; - x.set("name1", 42); - BEAST_EXPECT(x.val[0].isMember("name1")); - BEAST_EXPECT(x.val[1].isMember("name1")); - BEAST_EXPECT(x.val[0]["name1"].isInt()); - BEAST_EXPECT(x.val[1]["name1"].isInt()); - BEAST_EXPECT(x.val[0]["name1"].asInt() == 42); - BEAST_EXPECT(x.val[1]["name1"].asInt() == 42); - - x.set("name2", "bar"); - BEAST_EXPECT(x.val[0].isMember("name2")); - BEAST_EXPECT(x.val[1].isMember("name2")); - BEAST_EXPECT(x.val[0]["name2"].isString()); - BEAST_EXPECT(x.val[1]["name2"].isString()); - BEAST_EXPECT(x.val[0]["name2"].asString() == "bar"); - BEAST_EXPECT(x.val[1]["name2"].asString() == "bar"); - - // Tests of requires clause - these are expected to match - static_assert([](auto&& v) { - return requires - { - v.set("name", Json::nullValue); - }; - }(x)); - static_assert([](auto&& v) { - return requires - { - v.set("name", "value"); - }; - }(x)); - static_assert([](auto&& v) { - return requires - { - v.set("name", true); - }; - }(x)); - static_assert([](auto&& v) { - return requires - { - v.set("name", 42); - }; - }(x)); - - // Tests of requires clause - these are expected NOT to match - struct foo_t final - { - }; - static_assert([](auto&& v) { - return !requires - { - v.set("name", foo_t{}); - }; - }(x)); - static_assert([](auto&& v) { - return !requires - { - v.set("name", std::nullopt); - }; - }(x)); - } - - { - testcase("isMember"); - - // Well defined behaviour even if we have different types of members - BEAST_EXPECT(subject.isMember("foo") == decltype(subject)::none); - - { - // All variants have element "One", none have element "Two" - MultivarJson<2> s1{}; - s1.val[0] = makeJson("One", 12); - s1.val[1] = makeJson("One", 42); - BEAST_EXPECT(s1.isMember("One") == decltype(s1)::all); - BEAST_EXPECT(s1.isMember("Two") == decltype(s1)::none); - } - - { - // Some variants have element "One" and some have "Two" - MultivarJson<2> s2{}; - s2.val[0] = makeJson("One", 12); - s2.val[1] = makeJson("Two", 42); - BEAST_EXPECT(s2.isMember("One") == decltype(s2)::some); - BEAST_EXPECT(s2.isMember("Two") == decltype(s2)::some); - } - - { - // Not all variants have element "One", because last one is null - MultivarJson<3> s3{}; - s3.val[0] = makeJson("One", 12); - s3.val[1] = makeJson("One", 42); - BEAST_EXPECT(s3.isMember("One") == decltype(s3)::some); - BEAST_EXPECT(s3.isMember("Two") == decltype(s3)::none); - } - } - - { - // NOTE It's fine to change this test when we change API versions - testcase("apiVersionSelector"); - - static_assert(MultiApiJson::size == 3); - static MultiApiJson x{obj1}; - x.val[1] = obj2; - x.val[2] = obj3; - - static_assert( - std::is_same_v); - static_assert([](auto&& v) { - return requires - { - v.select(apiVersionSelector(1)); - }; - }(x)); - - BEAST_EXPECT(x.select(apiVersionSelector(0)) == obj1); - BEAST_EXPECT(x.select(apiVersionSelector(2)) == obj2); - - static_assert(apiVersionSelector(0)() == 0); - static_assert(apiVersionSelector(1)() == 0); - static_assert(apiVersionSelector(2)() == 1); - static_assert(apiVersionSelector(3)() == 2); - static_assert(apiVersionSelector(4)() == 2); - static_assert( - apiVersionSelector( - std::numeric_limits::max())() == 2); - } - - { - // There should be no reson to change this test - testcase("apiVersionSelector invariants"); - - static_assert( - apiVersionSelector(RPC::apiMinimumSupportedVersion)() == 0); - static_assert( - apiVersionSelector(RPC::apiBetaVersion)() + 1 // - == MultiApiJson::size); - - BEAST_EXPECT(MultiApiJson::size >= 1); - } - - { - testcase("visit"); - - MultivarJson<3> s1{}; - s1.val[0] = makeJson("value", 2); - s1.val[1] = makeJson("value", 3); - s1.val[2] = makeJson("value", 5); - - int result = 1; - ripple::visit<1, 3>( - s1, [&](Json::Value& json, unsigned int i) -> void { - if (BEAST_EXPECT(json.isObject() && json.isMember("value"))) - { - auto const value = json["value"].asInt(); - BEAST_EXPECT( - (value == 2 && i == 1) || // - (value == 3 && i == 2) || // - (value == 5 && i == 3)); - result *= value; - } - }); - BEAST_EXPECT(result == 30); - - // Can use fn with constexpr functor - static_assert([](auto&& v) { - return requires - { - ripple::visit<1, 3>( - v, [](Json::Value&, unsigned int) constexpr {}); - }; - }(s1)); - - // Can use fn with deduction over all parameters - static_assert([](auto&& v) { - return requires - { - ripple::visit<1, 3>(v, [](auto&, auto) constexpr {}); - }; - }(s1)); - - // Can use fn with conversion of version parameter - static_assert([](auto&& v) { - return requires - { - ripple::visit<1, 3>(v, [](auto&, std::size_t) constexpr {}); - }; - }(s1)); - - // Cannot use fn with const parameter - static_assert([](auto&& v) { - return !requires - { - ripple::visit<1, 3>( - v, [](Json::Value const&, auto) constexpr {}); - }; - }(const_cast const&>(s1))); - - // Cannot call visit with size mismatch - static_assert([](auto&& v) { - return !requires - { - ripple::visit<1, 2>( - v, [](Json::Value&, unsigned int) constexpr {}); - }; - }(s1)); - - // Cannot call visit with version offset - static_assert([](auto&& v) { - return !requires - { - ripple::visit<0, 2>( - v, [](Json::Value&, unsigned int) constexpr {}); - }; - }(s1)); - - // Cannot call visit with size mismatch - static_assert([](auto&& v) { - return !requires - { - ripple::visit<1, 4>( - v, [](Json::Value&, unsigned int) constexpr {}); - }; - }(s1)); - - // Cannot call visit with wrong order of versions - static_assert([](auto&& v) { - return !requires - { - ripple::visit<3, 1>( - v, [](Json::Value&, unsigned int) constexpr {}); - }; - }(s1)); - } - } -}; - -BEAST_DEFINE_TESTSUITE(MultivarJson, ripple_basics, ripple); - -} // namespace test -} // namespace ripple diff --git a/src/test/jtx/Env.h b/src/test/jtx/Env.h index fd05d1c6c5..72cac29040 100644 --- a/src/test/jtx/Env.h +++ b/src/test/jtx/Env.h @@ -758,40 +758,6 @@ Env::rpc(std::string const& cmd, Args&&... args) std::forward(args)...); } -/** - * The SingleVersionedTestCallable concept checks for a callable that takes - * an unsigned integer as its argument and returns void. - */ -template -concept SingleVersionedTestCallable = requires(T callable, unsigned int version) -{ - { - callable(version) - } - ->std::same_as; -}; - -/** - * The VersionedTestCallable concept checks if a set of callables all satisfy - * the SingleVersionedTestCallable concept. This allows forAllApiVersions to - * accept any number of functions. It executes a set of provided functions over - * a range of versions from RPC::apiMinimumSupportedVersion to - * RPC::apiBetaVersion. This is useful for running a series of tests or - * operations that need to be performed on multiple versions of an API. - */ -template -concept VersionedTestCallable = (... && SingleVersionedTestCallable); -void -forAllApiVersions(VersionedTestCallable auto... testCallable) -{ - for (auto testVersion = RPC::apiMinimumSupportedVersion; - testVersion <= RPC::apiMaximumValidVersion; - ++testVersion) - { - (..., testCallable(testVersion)); - } -} - } // namespace jtx } // namespace test } // namespace ripple diff --git a/src/test/protocol/ApiVersion_test.cpp b/src/test/protocol/ApiVersion_test.cpp new file mode 100644 index 0000000000..f4aba9e996 --- /dev/null +++ b/src/test/protocol/ApiVersion_test.cpp @@ -0,0 +1,75 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/XRPLF/rippled/ + Copyright (c) 2023 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace ripple { +namespace test { +struct ApiVersion_test : beast::unit_test::suite +{ + void + run() override + { + { + testcase("API versions invariants"); + + static_assert( + RPC::apiMinimumSupportedVersion <= + RPC::apiMaximumSupportedVersion); + static_assert( + RPC::apiMinimumSupportedVersion <= RPC::apiMaximumValidVersion); + static_assert( + RPC::apiMaximumSupportedVersion <= RPC::apiMaximumValidVersion); + static_assert(RPC::apiBetaVersion <= RPC::apiMaximumValidVersion); + + BEAST_EXPECT(true); + } + + { + // Update when we change versions + testcase("API versions"); + + static_assert(RPC::apiMinimumSupportedVersion >= 1); + static_assert(RPC::apiMinimumSupportedVersion < 2); + static_assert(RPC::apiMaximumSupportedVersion >= 2); + static_assert(RPC::apiMaximumSupportedVersion < 3); + static_assert(RPC::apiMaximumValidVersion >= 3); + static_assert(RPC::apiMaximumValidVersion < 4); + static_assert(RPC::apiBetaVersion >= 3); + static_assert(RPC::apiBetaVersion < 4); + + BEAST_EXPECT(true); + } + } +}; + +BEAST_DEFINE_TESTSUITE(ApiVersion, protocol, ripple); + +} // namespace test +} // namespace ripple diff --git a/src/test/protocol/MultiApiJson_test.cpp b/src/test/protocol/MultiApiJson_test.cpp new file mode 100644 index 0000000000..a7d809bccb --- /dev/null +++ b/src/test/protocol/MultiApiJson_test.cpp @@ -0,0 +1,1202 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/XRPLF/rippled/ + Copyright (c) 2023 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include + +#include +#include +#include +#include +#include + +namespace ripple { +namespace test { + +namespace { + +// This needs to be in a namespace because of deduction guide +template +struct Overload : Ts... +{ + using Ts::operator()...; +}; +template +Overload(Ts...) -> Overload; + +} // namespace + +struct MultiApiJson_test : beast::unit_test::suite +{ + static auto + makeJson(const char* key, int val) + { + Json::Value obj1(Json::objectValue); + obj1[key] = val; + return obj1; + } + + constexpr static auto index = + [](unsigned int v) constexpr noexcept -> std::size_t + { + return v - 1; + }; + + template + constexpr static auto valid = [](unsigned int v) constexpr noexcept -> bool + { + return v > 0 && v <= size; + }; + + void + run() override + { + using ripple::detail::MultiApiJson; + + Json::Value const obj1 = makeJson("value", 1); + Json::Value const obj2 = makeJson("value", 2); + Json::Value const obj3 = makeJson("value", 3); + Json::Value const jsonNull{}; + + MultiApiJson<1, 3> subject{}; + static_assert(sizeof(subject) == sizeof(subject.val)); + static_assert(subject.size == subject.val.size()); + static_assert( + std::is_same_v>); + + BEAST_EXPECT(subject.val.size() == 3); + BEAST_EXPECT( + (subject.val == + std::array{jsonNull, jsonNull, jsonNull})); + + subject.val[0] = obj1; + subject.val[1] = obj2; + + { + testcase("forApiVersions, forAllApiVersions"); + + // Some static data for test inputs + static const int primes[] = {2, 3, 5, 7, 11, 13, 17, 19, 23, + 29, 31, 37, 41, 43, 47, 53, 59, 61, + 67, 71, 73, 79, 83, 89, 97}; + static_assert(std::size(primes) > RPC::apiMaximumValidVersion); + + MultiApiJson<1, 3> s1{}; + static_assert( + s1.size == + RPC::apiMaximumValidVersion + 1 - + RPC::apiMinimumSupportedVersion); + + int productAllVersions = 1; + for (unsigned i = RPC::apiMinimumSupportedVersion; + i <= RPC::apiMaximumValidVersion; + ++i) + { + auto const index = i - RPC::apiMinimumSupportedVersion; + BEAST_EXPECT(index == s1.index(i)); + BEAST_EXPECT(s1.valid(i)); + s1.val[index] = makeJson("value", primes[i]); + productAllVersions *= primes[i]; + } + BEAST_EXPECT(!s1.valid(0)); + BEAST_EXPECT(!s1.valid(RPC::apiMaximumValidVersion + 1)); + BEAST_EXPECT( + !s1.valid(std::numeric_limits::max())); + + int result = 1; + static_assert( + RPC::apiMinimumSupportedVersion + 1 <= + RPC::apiMaximumValidVersion); + forApiVersions< + RPC::apiMinimumSupportedVersion, + RPC::apiMinimumSupportedVersion + 1>( + std::as_const(s1).visit(), + [this]( + Json::Value const& json, + unsigned int version, + int* result) { + BEAST_EXPECT( + version >= RPC::apiMinimumSupportedVersion && + version <= RPC::apiMinimumSupportedVersion + 1); + if (BEAST_EXPECT(json.isMember("value"))) + { + *result *= json["value"].asInt(); + } + }, + &result); + BEAST_EXPECT( + result == + primes[RPC::apiMinimumSupportedVersion] * + primes[RPC::apiMinimumSupportedVersion + 1]); + + // Check all the values with mutable data + forAllApiVersions( + s1.visit(), [&s1, this](Json::Value& json, auto version) { + BEAST_EXPECT(s1.val[s1.index(version)] == json); + if (BEAST_EXPECT(json.isMember("value"))) + { + BEAST_EXPECT(json["value"].asInt() == primes[version]); + } + }); + + result = 1; + forAllApiVersions( + std::as_const(s1).visit(), + [this]( + Json::Value const& json, + unsigned int version, + int* result) { + BEAST_EXPECT( + version >= RPC::apiMinimumSupportedVersion && + version <= RPC::apiMaximumValidVersion); + if (BEAST_EXPECT(json.isMember("value"))) + { + *result *= json["value"].asInt(); + } + }, + &result); + + BEAST_EXPECT(result == productAllVersions); + + // Several overloads we want to fail + static_assert([](auto&& v) { + return !requires + { + forAllApiVersions( + std::forward(v).visit(), // + [](Json::Value&, auto) {}); // missing const + }; + }(std::as_const(s1))); + static_assert([](auto&& v) { + return !requires + { + forAllApiVersions( + std::forward(v).visit(), // + [](Json::Value&) {}); // missing const + }; + }(std::as_const(s1))); + static_assert([](auto&& v) { + return !requires + { + forAllApiVersions( + std::forward(v).visit(), // + []() {}); // missing parameters + }; + }(std::as_const(s1))); + static_assert([](auto&& v) { + return !requires + { + forAllApiVersions( + std::forward(v).visit(), // + [](auto) {}, + 1); // missing parameters + }; + }(std::as_const(s1))); + static_assert([](auto&& v) { + return !requires + { + forAllApiVersions( + std::forward(v).visit(), // + [](auto, auto) {}, + 1); // missing parameters + }; + }(std::as_const(s1))); + static_assert([](auto&& v) { + return !requires + { + forAllApiVersions( + std::forward(v).visit(), // + [](auto, auto, const char*) {}, + 1); // parameter type mismatch + }; + }(std::as_const(s1))); + + // Sanity checks + static_assert([](auto&& v) { + return requires + { + forAllApiVersions( + std::forward(v).visit(), // + [](auto) {}); + }; + }(s1)); + static_assert([](auto&& v) { + return requires + { + forAllApiVersions( + std::forward(v).visit(), // + [](Json::Value const&) {}); + }; + }(std::as_const(s1))); + static_assert([](auto&& v) { + return requires + { + forAllApiVersions( + std::forward(v).visit(), // + [](auto...) {}); + }; + }(s1)); + static_assert([](auto&& v) { + return requires + { + forAllApiVersions( + std::forward(v).visit(), // + [](Json::Value const&, auto...) {}); + }; + }(std::as_const(s1))); + static_assert([](auto&& v) { + return requires + { + forAllApiVersions( + std::forward(v).visit(), // + [](Json::Value&, auto, auto, auto...) {}, + 0, + ""); + }; + }(s1)); + static_assert([](auto&& v) { + return requires + { + forAllApiVersions( + std::forward(v).visit(), // + []( + Json::Value const&, + std::integral_constant, + int, + const char*) {}, + 0, + ""); + }; + }(std::as_const(s1))); + static_assert([](auto&& v) { + return requires + { + forAllApiVersions( + std::forward(v).visit(), // + [](auto...) {}); + }; + }(std::move(s1))); + static_assert([](auto&& v) { + return requires + { + forAllApiVersions( + std::forward(v).visit(), // + [](auto...) {}); + }; + }(std::move(std::as_const(s1)))); + } + + { + testcase("default copy construction / assignment"); + + MultiApiJson<1, 3> x{subject}; + + BEAST_EXPECT(x.val.size() == subject.val.size()); + BEAST_EXPECT(x.val[0] == subject.val[0]); + BEAST_EXPECT(x.val[1] == subject.val[1]); + BEAST_EXPECT(x.val[2] == subject.val[2]); + BEAST_EXPECT(x.val == subject.val); + BEAST_EXPECT(&x.val[0] != &subject.val[0]); + BEAST_EXPECT(&x.val[1] != &subject.val[1]); + BEAST_EXPECT(&x.val[2] != &subject.val[2]); + + MultiApiJson<1, 3> y; + BEAST_EXPECT((y.val == std::array{})); + y = subject; + BEAST_EXPECT(y.val == subject.val); + BEAST_EXPECT(&y.val[0] != &subject.val[0]); + BEAST_EXPECT(&y.val[1] != &subject.val[1]); + BEAST_EXPECT(&y.val[2] != &subject.val[2]); + + y = std::move(x); + BEAST_EXPECT(y.val == subject.val); + BEAST_EXPECT(&y.val[0] != &subject.val[0]); + BEAST_EXPECT(&y.val[1] != &subject.val[1]); + BEAST_EXPECT(&y.val[2] != &subject.val[2]); + } + + { + testcase("set"); + + auto x = MultiApiJson<1, 2>{Json::objectValue}; + x.set("name1", 42); + BEAST_EXPECT(x.val[0].isMember("name1")); + BEAST_EXPECT(x.val[1].isMember("name1")); + BEAST_EXPECT(x.val[0]["name1"].isInt()); + BEAST_EXPECT(x.val[1]["name1"].isInt()); + BEAST_EXPECT(x.val[0]["name1"].asInt() == 42); + BEAST_EXPECT(x.val[1]["name1"].asInt() == 42); + + x.set("name2", "bar"); + BEAST_EXPECT(x.val[0].isMember("name2")); + BEAST_EXPECT(x.val[1].isMember("name2")); + BEAST_EXPECT(x.val[0]["name2"].isString()); + BEAST_EXPECT(x.val[1]["name2"].isString()); + BEAST_EXPECT(x.val[0]["name2"].asString() == "bar"); + BEAST_EXPECT(x.val[1]["name2"].asString() == "bar"); + + // Tests of requires clause - these are expected to match + static_assert([](auto&& v) { + return requires + { + v.set("name", Json::nullValue); + }; + }(x)); + static_assert([](auto&& v) { + return requires + { + v.set("name", "value"); + }; + }(x)); + static_assert([](auto&& v) { + return requires + { + v.set("name", true); + }; + }(x)); + static_assert([](auto&& v) { + return requires + { + v.set("name", 42); + }; + }(x)); + + // Tests of requires clause - these are expected NOT to match + struct foo_t final + { + }; + static_assert([](auto&& v) { + return !requires + { + v.set("name", foo_t{}); + }; + }(x)); + static_assert([](auto&& v) { + return !requires + { + v.set("name", std::nullopt); + }; + }(x)); + } + + { + testcase("isMember"); + + // Well defined behaviour even if we have different types of members + BEAST_EXPECT(subject.isMember("foo") == decltype(subject)::none); + + { + // All variants have element "One", none have element "Two" + MultiApiJson<1, 2> s1{}; + s1.val[0] = makeJson("One", 12); + s1.val[1] = makeJson("One", 42); + BEAST_EXPECT(s1.isMember("One") == decltype(s1)::all); + BEAST_EXPECT(s1.isMember("Two") == decltype(s1)::none); + } + + { + // Some variants have element "One" and some have "Two" + MultiApiJson<1, 2> s2{}; + s2.val[0] = makeJson("One", 12); + s2.val[1] = makeJson("Two", 42); + BEAST_EXPECT(s2.isMember("One") == decltype(s2)::some); + BEAST_EXPECT(s2.isMember("Two") == decltype(s2)::some); + } + + { + // Not all variants have element "One", because last one is null + MultiApiJson<1, 3> s3{}; + s3.val[0] = makeJson("One", 12); + s3.val[1] = makeJson("One", 42); + BEAST_EXPECT(s3.isMember("One") == decltype(s3)::some); + BEAST_EXPECT(s3.isMember("Two") == decltype(s3)::none); + } + } + + { + testcase("visitor"); + + MultiApiJson<1, 3> s1{}; + s1.val[0] = makeJson("value", 2); + s1.val[1] = makeJson("value", 3); + s1.val[2] = makeJson("value", 5); + + BEAST_EXPECT(not s1.valid(0)); + BEAST_EXPECT(s1.index(0) == 0); + + BEAST_EXPECT(s1.valid(1)); + BEAST_EXPECT(s1.index(1) == 0); + + BEAST_EXPECT(not s1.valid(4)); + + // Test different overloads + static_assert([](auto&& v) { + return requires + { + v.visitor( + v, + std::integral_constant{}, + [](Json::Value&, std::integral_constant) { + }); + }; + }(s1)); + BEAST_EXPECT( + s1.visitor( + s1, + std::integral_constant{}, + Overload{ + [](Json::Value& v, + std::integral_constant) { + return v["value"].asInt(); + }, + [](Json::Value const&, auto) { return 0; }, + [](auto, auto) { return 0; }}) == 2); + + static_assert([](auto&& v) { + return requires + { + v.visitor( + v, + std::integral_constant{}, + [](Json::Value&) {}); + }; + }(s1)); + BEAST_EXPECT( + s1.visitor( + s1, + std::integral_constant{}, + Overload{ + [](Json::Value& v) { return v["value"].asInt(); }, + [](Json::Value const&) { return 0; }, + [](auto...) { return 0; }}) == 2); + + static_assert([](auto&& v) { + return requires + { + v.visitor( + v, + std::integral_constant{}, + [](Json::Value const&, + std::integral_constant) {}); + }; + }(std::as_const(s1))); + BEAST_EXPECT( + s1.visitor( + std::as_const(s1), + std::integral_constant{}, + Overload{ + [](Json::Value const& v, + std::integral_constant) { + return v["value"].asInt(); + }, + [](Json::Value&, auto) { return 0; }, + [](auto, auto) { return 0; }}) == 3); + + static_assert([](auto&& v) { + return requires + { + v.visitor( + v, + std::integral_constant{}, + [](Json::Value const&) {}); + }; + }(std::as_const(s1))); + BEAST_EXPECT( + s1.visitor( + std::as_const(s1), + std::integral_constant{}, + Overload{ + [](Json::Value const& v) { return v["value"].asInt(); }, + [](Json::Value&) { return 0; }, + [](auto...) { return 0; }}) == 3); + + static_assert([](auto&& v) { + return requires + { + v.visitor(v, 1, [](Json::Value&, unsigned) {}); + }; + }(s1)); + BEAST_EXPECT( + s1.visitor( + s1, // + 3u, + Overload{ + [](Json::Value& v, unsigned) { + return v["value"].asInt(); + }, + [](Json::Value const&, unsigned) { return 0; }, + [](auto, auto) { return 0; }}) == 5); + + static_assert([](auto&& v) { + return requires + { + v.visitor(v, 1, [](Json::Value&) {}); + }; + }(s1)); + BEAST_EXPECT( + s1.visitor( + s1, // + 3, + Overload{ + [](Json::Value& v) { return v["value"].asInt(); }, + [](Json::Value const&) { return 0; }, + [](auto...) { return 0; }}) == 5); + + static_assert([](auto&& v) { + return requires + { + v.visitor(v, 1, [](Json::Value const&, unsigned) {}); + }; + }(std::as_const(s1))); + BEAST_EXPECT( + s1.visitor( + std::as_const(s1), // + 2u, + Overload{ + [](Json::Value const& v, unsigned) { + return v["value"].asInt(); + }, + [](Json::Value const&, auto) { return 0; }, + [](auto, auto) { return 0; }}) == 3); + + static_assert([](auto&& v) { + return requires + { + v.visitor(v, 1, [](Json::Value const&) {}); + }; + }(std::as_const(s1))); + BEAST_EXPECT( + s1.visitor( + std::as_const(s1), // + 2, + Overload{ + [](Json::Value const& v) { return v["value"].asInt(); }, + [](Json::Value&) { return 0; }, + [](auto...) { return 0; }}) == 3); + + // Test type conversions + BEAST_EXPECT( + s1.visitor( + s1, + std::integral_constant{}, // to unsigned + [](Json::Value& v, unsigned) { + return v["value"].asInt(); + }) == 2); + BEAST_EXPECT( + s1.visitor( + std::as_const(s1), + std::integral_constant{}, // to unsigned + [](Json::Value const& v, unsigned) { + return v["value"].asInt(); + }) == 3); + BEAST_EXPECT( + s1.visitor( + s1, // to const + std::integral_constant{}, + [](Json::Value const& v, auto) { + return v["value"].asInt(); + }) == 5); + BEAST_EXPECT( + s1.visitor( + s1, // to const + std::integral_constant{}, + [](Json::Value const& v) { return v["value"].asInt(); }) == + 5); + BEAST_EXPECT( + s1.visitor( + s1, + 3, // to long + [](Json::Value& v, long) { return v["value"].asInt(); }) == + 5); + BEAST_EXPECT( + s1.visitor( + std::as_const(s1), + 1, // to long + [](Json::Value const& v, long) { + return v["value"].asInt(); + }) == 2); + BEAST_EXPECT( + s1.visitor( + s1, // to const + 2, + [](Json::Value const& v, auto) { + return v["value"].asInt(); + }) == 3); + BEAST_EXPECT( + s1.visitor( + s1, // type deduction + 2, + [](auto& v, auto) { return v["value"].asInt(); }) == 3); + BEAST_EXPECT( + s1.visitor( + s1, // to const, type deduction + 2, + [](auto const& v, auto) { return v["value"].asInt(); }) == + 3); + BEAST_EXPECT( + s1.visitor( + s1, // type deduction + 2, + [](auto& v) { return v["value"].asInt(); }) == 3); + BEAST_EXPECT( + s1.visitor( + s1, // to const, type deduction + 2, + [](auto const& v) { return v["value"].asInt(); }) == 3); + + // Test passing of additional arguments + BEAST_EXPECT( + s1.visitor( + s1, + std::integral_constant{}, + [](Json::Value& v, auto ver, auto a1, auto a2) { + return ver * a1 * a2 * v["value"].asInt(); + }, + 5, + 7) == 2 * 5 * 7 * 3); + BEAST_EXPECT( + s1.visitor( + s1, + std::integral_constant{}, + [](Json::Value& v, auto ver, auto... args) { + return ver * (1 * ... * args) * v["value"].asInt(); + }, + 5, + 7) == 2 * 5 * 7 * 3); + + // Several overloads we want to fail + static_assert([](auto&& v) { + return !requires + { + v.visitor( + v, + 1, // + [](Json::Value&, auto) {}); // missing const + }; + }(std::as_const(s1))); + + static_assert([](auto&& v) { + return !requires + { + v.visitor( + std::move(v), // cannot bind rvalue + 1, + [](Json::Value&, auto) {}); + }; + }(s1)); + + static_assert([](auto&& v) { + return !requires + { + v.visitor( + v, + 1, // + []() {}); // missing parameter + }; + }(s1)); + + static_assert([](auto&& v) { + return !requires + { + v.visitor( + v, + 1, // + [](Json::Value&, int, int) {}); // too many parameters + }; + }(s1)); + + // Want these to be unambiguous + static_assert([](auto&& v) { + return requires + { + v.visitor(v, 1, [](auto) {}); + }; + }(s1)); + + static_assert([](auto&& v) { + return requires + { + v.visitor(v, 1, [](Json::Value&) {}); + }; + }(s1)); + + static_assert([](auto&& v) { + return requires + { + v.visitor(v, 1, [](Json::Value&, auto...) {}); + }; + }(s1)); + + static_assert([](auto&& v) { + return requires + { + v.visitor(v, 1, [](Json::Value const&) {}); + }; + }(s1)); + + static_assert([](auto&& v) { + return requires + { + v.visitor(v, 1, [](Json::Value const&, auto...) {}); + }; + }(s1)); + + static_assert([](auto&& v) { + return requires + { + v.visitor(v, 1, [](auto...) {}); + }; + }(s1)); + + static_assert([](auto&& v) { + return requires + { + v.visitor(v, 1, [](auto, auto...) {}); + }; + }(s1)); + + static_assert([](auto&& v) { + return requires + { + v.visitor(v, 1, [](auto, auto, auto...) {}); + }; + }(s1)); + + static_assert([](auto&& v) { + return requires + { + v.visitor( + v, 1, [](auto, auto, auto...) {}, ""); + }; + }(s1)); + + static_assert([](auto&& v) { + return requires + { + v.visitor( + v, 1, [](auto, auto, auto, auto...) {}, ""); + }; + }(s1)); + } + + { + testcase("visit"); + + MultiApiJson<1, 3> s1{}; + s1.val[0] = makeJson("value", 2); + s1.val[1] = makeJson("value", 3); + s1.val[2] = makeJson("value", 5); + + // Test different overloads + static_assert([](auto&& v) { + return requires + { + v.visit( + std::integral_constant{}, + [](Json::Value&, std::integral_constant) { + }); + }; + }(s1)); + BEAST_EXPECT( + s1.visit( + std::integral_constant{}, + Overload{ + [](Json::Value& v, + std::integral_constant) { + return v["value"].asInt(); + }, + [](Json::Value const&, auto) { return 0; }, + [](auto, auto) { return 0; }}) == 2); + static_assert([](auto&& v) { + return requires + { + v.visit()( + std::integral_constant{}, + [](Json::Value&, std::integral_constant) { + }); + }; + }(s1)); + BEAST_EXPECT( + s1.visit()( + std::integral_constant{}, + Overload{ + [](Json::Value& v, + std::integral_constant) { + return v["value"].asInt(); + }, + [](Json::Value const&, auto) { return 0; }, + [](auto, auto) { return 0; }}) == 2); + + static_assert([](auto&& v) { + return requires + { + v.visit( + std::integral_constant{}, + [](Json::Value&) {}); + }; + }(s1)); + BEAST_EXPECT( + s1.visit( + std::integral_constant{}, + Overload{ + [](Json::Value& v) { return v["value"].asInt(); }, + [](Json::Value const&) { return 0; }, + [](auto...) { return 0; }}) == 2); + static_assert([](auto&& v) { + return requires + { + v.visit()( + std::integral_constant{}, + [](Json::Value&) {}); + }; + }(s1)); + BEAST_EXPECT( + s1.visit()( + std::integral_constant{}, + Overload{ + [](Json::Value& v) { return v["value"].asInt(); }, + [](Json::Value const&) { return 0; }, + [](auto...) { return 0; }}) == 2); + + static_assert([](auto&& v) { + return requires + { + v.visit( + std::integral_constant{}, + [](Json::Value const&, + std::integral_constant) {}); + }; + }(std::as_const(s1))); + BEAST_EXPECT( + std::as_const(s1).visit( + std::integral_constant{}, + Overload{ + [](Json::Value const& v, + std::integral_constant) { + return v["value"].asInt(); + }, + [](Json::Value&, auto) { return 0; }, + [](auto, auto) { return 0; }}) == 3); + static_assert([](auto&& v) { + return requires + { + v.visit()( + std::integral_constant{}, + [](Json::Value const&, + std::integral_constant) {}); + }; + }(std::as_const(s1))); + BEAST_EXPECT( + std::as_const(s1).visit()( + std::integral_constant{}, + Overload{ + [](Json::Value const& v, + std::integral_constant) { + return v["value"].asInt(); + }, + [](Json::Value&, auto) { return 0; }, + [](auto, auto) { return 0; }}) == 3); + + static_assert([](auto&& v) { + return requires + { + v.visit( + std::integral_constant{}, + [](Json::Value const&) {}); + }; + }(std::as_const(s1))); + BEAST_EXPECT( + std::as_const(s1).visit( + std::integral_constant{}, + Overload{ + [](Json::Value const& v) { return v["value"].asInt(); }, + [](Json::Value&) { return 0; }, + [](auto...) { return 0; }}) == 3); + static_assert([](auto&& v) { + return requires + { + v.visit()( + std::integral_constant{}, + [](Json::Value const&) {}); + }; + }(std::as_const(s1))); + BEAST_EXPECT( + std::as_const(s1).visit()( + std::integral_constant{}, + Overload{ + [](Json::Value const& v) { return v["value"].asInt(); }, + [](Json::Value&) { return 0; }, + [](auto...) { return 0; }}) == 3); + + static_assert([](auto&& v) { + return requires + { + v.visit(1, [](Json::Value&, unsigned) {}); + }; + }(s1)); + BEAST_EXPECT( + s1.visit( + 3u, + Overload{ + [](Json::Value& v, unsigned) { + return v["value"].asInt(); + }, + [](Json::Value const&, unsigned) { return 0; }, + [](Json::Value&, auto) { return 0; }, + [](auto, auto) { return 0; }}) == 5); + static_assert([](auto&& v) { + return requires + { + v.visit()(1, [](Json::Value&, unsigned) {}); + }; + }(s1)); + BEAST_EXPECT( + s1.visit()( + 3u, + Overload{ + [](Json::Value& v, unsigned) { + return v["value"].asInt(); + }, + [](Json::Value const&, unsigned) { return 0; }, + [](Json::Value&, auto) { return 0; }, + [](auto, auto) { return 0; }}) == 5); + + static_assert([](auto&& v) { + return requires + { + v.visit(1, [](Json::Value&) {}); + }; + }(s1)); + BEAST_EXPECT( + s1.visit( + 3, + Overload{ + [](Json::Value& v) { return v["value"].asInt(); }, + [](Json::Value const&) { return 0; }, + [](auto...) { return 0; }}) == 5); + static_assert([](auto&& v) { + return requires + { + v.visit()(1, [](Json::Value&) {}); + }; + }(s1)); + BEAST_EXPECT( + s1.visit()( + 3, + Overload{ + [](Json::Value& v) { return v["value"].asInt(); }, + [](Json::Value const&) { return 0; }, + [](auto...) { return 0; }}) == 5); + + static_assert([](auto&& v) { + return requires + { + v.visit(1, [](Json::Value const&, unsigned) {}); + }; + }(std::as_const(s1))); + BEAST_EXPECT( + std::as_const(s1).visit( + 2u, + Overload{ + [](Json::Value const& v, unsigned) { + return v["value"].asInt(); + }, + [](Json::Value const&, auto) { return 0; }, + [](Json::Value&, unsigned) { return 0; }, + [](auto, auto) { return 0; }}) == 3); + static_assert([](auto&& v) { + return requires + { + v.visit()(1, [](Json::Value const&, unsigned) {}); + }; + }(std::as_const(s1))); + BEAST_EXPECT( + std::as_const(s1).visit()( + 2u, + Overload{ + [](Json::Value const& v, unsigned) { + return v["value"].asInt(); + }, + [](Json::Value const&, auto) { return 0; }, + [](Json::Value&, unsigned) { return 0; }, + [](auto, auto) { return 0; }}) == 3); + + static_assert([](auto&& v) { + return requires + { + v.visit(1, [](Json::Value const&) {}); + }; + }(std::as_const(s1))); + BEAST_EXPECT( + std::as_const(s1).visit( + 2, + Overload{ + [](Json::Value const& v) { return v["value"].asInt(); }, + [](Json::Value&) { return 0; }, + [](auto...) { return 0; }}) == 3); + static_assert([](auto&& v) { + return requires + { + v.visit()(1, [](Json::Value const&) {}); + }; + }(std::as_const(s1))); + BEAST_EXPECT( + std::as_const(s1).visit()( + 2, + Overload{ + [](Json::Value const& v) { return v["value"].asInt(); }, + [](Json::Value&) { return 0; }, + [](auto...) { return 0; }}) == 3); + + // Rvalue MultivarJson visitor only binds to regular reference + static_assert([](auto&& v) { + return !requires + { + std::forward(v).visit(1, [](Json::Value&&) {}); + }; + }(std::move(s1))); + static_assert([](auto&& v) { + return !requires + { + std::forward(v).visit( + 1, [](Json::Value const&&) {}); + }; + }(std::move(s1))); + static_assert([](auto&& v) { + return requires + { + std::forward(v).visit(1, [](Json::Value&) {}); + }; + }(std::move(s1))); + static_assert([](auto&& v) { + return requires + { + std::forward(v).visit( + 1, [](Json::Value const&) {}); + }; + }(std::move(s1))); + static_assert([](auto&& v) { + return !requires + { + std::forward(v).visit()( + 1, [](Json::Value&&) {}); + }; + }(std::move(s1))); + static_assert([](auto&& v) { + return !requires + { + std::forward(v).visit()( + 1, [](Json::Value const&&) {}); + }; + }(std::move(s1))); + static_assert([](auto&& v) { + return requires + { + std::forward(v).visit()( + 1, [](Json::Value&) {}); + }; + }(std::move(s1))); + static_assert([](auto&& v) { + return requires + { + std::forward(v).visit()( + 1, [](Json::Value const&) {}); + }; + }(std::move(s1))); + static_assert([](auto&& v) { + return !requires + { + std::forward(v).visit( + 1, [](Json::Value const&&) {}); + }; + }(std::move(std::as_const(s1)))); + static_assert([](auto&& v) { + return requires + { + std::forward(v).visit( + 1, [](Json::Value const&) {}); + }; + }(std::move(std::as_const(s1)))); + static_assert([](auto&& v) { + return !requires + { + std::forward(v).visit()( + 1, [](Json::Value const&&) {}); + }; + }(std::move(std::as_const(s1)))); + static_assert([](auto&& v) { + return requires + { + std::forward(v).visit()( + 1, [](Json::Value const&) {}); + }; + }(std::move(std::as_const(s1)))); + + // Missing const + static_assert([](auto&& v) { + return !requires + { + std::forward(v).visit( + 1, [](Json::Value&, auto) {}); + }; + }(std::as_const(s1))); + static_assert([](auto&& v) { + return !requires + { + std::forward(v).visit()( + 1, [](Json::Value&, auto) {}); + }; + }(std::as_const(s1))); + + // Missing parameter + static_assert([](auto&& v) { + return !requires + { + std::forward(v).visit(1, []() {}); + }; + }(s1)); + static_assert([](auto&& v) { + return !requires + { + std::forward(v).visit()(1, []() {}); + }; + }(s1)); + + // Sanity checks + static_assert([](auto&& v) { + return requires + { + std::forward(v).visit(1, [](auto...) {}); + }; + }(std::as_const(s1))); + static_assert([](auto&& v) { + return requires + { + std::forward(v).visit()(1, [](auto...) {}); + }; + }(std::as_const(s1))); + } + } +}; + +BEAST_DEFINE_TESTSUITE(MultiApiJson, protocol, ripple); + +} // namespace test +} // namespace ripple diff --git a/src/test/rpc/AccountTx_test.cpp b/src/test/rpc/AccountTx_test.cpp index 640b774f52..6601996925 100644 --- a/src/test/rpc/AccountTx_test.cpp +++ b/src/test/rpc/AccountTx_test.cpp @@ -734,7 +734,7 @@ public: void run() override { - test::jtx::forAllApiVersions( + forAllApiVersions( std::bind_front(&AccountTx_test::testParameters, this)); testContents(); testAccountDelete(); diff --git a/src/test/rpc/GatewayBalances_test.cpp b/src/test/rpc/GatewayBalances_test.cpp index 091b9f5168..4c6ec9aaf1 100644 --- a/src/test/rpc/GatewayBalances_test.cpp +++ b/src/test/rpc/GatewayBalances_test.cpp @@ -172,10 +172,7 @@ public: qry2[jss::account] = alice.human(); qry2[jss::hotwallet] = "asdf"; - for (auto apiVersion = RPC::apiMinimumSupportedVersion; - apiVersion <= RPC::apiBetaVersion; - ++apiVersion) - { + forAllApiVersions([&, this](unsigned apiVersion) { qry2[jss::api_version] = apiVersion; auto jv = wsc->invoke("gateway_balances", qry2); expect(jv[jss::status] == "error"); @@ -184,7 +181,7 @@ public: auto const error = apiVersion < 2u ? "invalidHotWallet" : "invalidParams"; BEAST_EXPECT(response[jss::error] == error); - } + }); } void diff --git a/src/test/rpc/LedgerRPC_test.cpp b/src/test/rpc/LedgerRPC_test.cpp index 2b4d8527a6..877b8ef2a0 100644 --- a/src/test/rpc/LedgerRPC_test.cpp +++ b/src/test/rpc/LedgerRPC_test.cpp @@ -2305,7 +2305,7 @@ public: testLedgerAccountsOption(); testLedgerEntryDID(); - test::jtx::forAllApiVersions(std::bind_front( + forAllApiVersions(std::bind_front( &LedgerRPC_test::testLedgerEntryInvalidParams, this)); } }; diff --git a/src/test/rpc/LedgerRequestRPC_test.cpp b/src/test/rpc/LedgerRequestRPC_test.cpp index a1f3daafb3..6c59e72c4b 100644 --- a/src/test/rpc/LedgerRequestRPC_test.cpp +++ b/src/test/rpc/LedgerRequestRPC_test.cpp @@ -359,7 +359,7 @@ public: { testLedgerRequest(); testEvolution(); - test::jtx::forAllApiVersions( + forAllApiVersions( std::bind_front(&LedgerRequestRPC_test::testBadInput, this)); testMoreThan256Closed(); testNonAdmin(); diff --git a/src/test/rpc/NoRipple_test.cpp b/src/test/rpc/NoRipple_test.cpp index 3077b06f8a..8da80e6483 100644 --- a/src/test/rpc/NoRipple_test.cpp +++ b/src/test/rpc/NoRipple_test.cpp @@ -284,12 +284,9 @@ public: testSetAndClear(); auto withFeatsTests = [this](FeatureBitset features) { - for (auto testVersion = RPC::apiMinimumSupportedVersion; - testVersion <= RPC::apiBetaVersion; - ++testVersion) - { + forAllApiVersions([&, this](unsigned testVersion) { testDefaultRipple(features, testVersion); - } + }); testNegativeBalance(features); testPairwise(features); }; diff --git a/src/test/rpc/RPCCall_test.cpp b/src/test/rpc/RPCCall_test.cpp index 5f66250b10..09096ba76a 100644 --- a/src/test/rpc/RPCCall_test.cpp +++ b/src/test/rpc/RPCCall_test.cpp @@ -6219,8 +6219,7 @@ public: void run() override { - test::jtx::forAllApiVersions( - std::bind_front(&RPCCall_test::testRPCCall, this)); + forAllApiVersions(std::bind_front(&RPCCall_test::testRPCCall, this)); } }; diff --git a/src/test/rpc/TransactionEntry_test.cpp b/src/test/rpc/TransactionEntry_test.cpp index 90c643700c..5eddb640cb 100644 --- a/src/test/rpc/TransactionEntry_test.cpp +++ b/src/test/rpc/TransactionEntry_test.cpp @@ -388,7 +388,7 @@ public: run() override { testBadInput(); - test::jtx::forAllApiVersions( + forAllApiVersions( std::bind_front(&TransactionEntry_test::testRequest, this)); } }; diff --git a/src/test/rpc/Transaction_test.cpp b/src/test/rpc/Transaction_test.cpp index 9fbda07c78..ac02dd11cd 100644 --- a/src/test/rpc/Transaction_test.cpp +++ b/src/test/rpc/Transaction_test.cpp @@ -849,7 +849,7 @@ public: run() override { using namespace test::jtx; - test::jtx::forAllApiVersions( + forAllApiVersions( std::bind_front(&Transaction_test::testBinaryRequest, this)); FeatureBitset const all{supported_amendments()}; @@ -863,7 +863,7 @@ public: testRangeCTIDRequest(features); testCTIDValidation(features); testCTIDRPC(features); - test::jtx::forAllApiVersions( + forAllApiVersions( std::bind_front(&Transaction_test::testRequest, this, features)); } }; diff --git a/src/test/rpc/Version_test.cpp b/src/test/rpc/Version_test.cpp index 60ffd30fcf..34e55b2be9 100644 --- a/src/test/rpc/Version_test.cpp +++ b/src/test/rpc/Version_test.cpp @@ -83,7 +83,8 @@ class Version_test : public beast::unit_test::suite "{\"api_version\": " + std::to_string( std::max( - RPC::apiMaximumSupportedVersion, RPC::apiBetaVersion) + + RPC::apiMaximumSupportedVersion.value, + RPC::apiBetaVersion.value) + 1) + "}"); BEAST_EXPECT(badVersion(re)); @@ -112,15 +113,15 @@ class Version_test : public beast::unit_test::suite Json::Value j_object = Json::Value(Json::objectValue); BEAST_EXPECT( RPC::getAPIVersionNumber(j_object, false) == versionIfUnspecified); - j_object[jss::api_version] = RPC::apiVersionIfUnspecified; + j_object[jss::api_version] = RPC::apiVersionIfUnspecified.value; BEAST_EXPECT( RPC::getAPIVersionNumber(j_object, false) == versionIfUnspecified); - j_object[jss::api_version] = RPC::apiMinimumSupportedVersion; + j_object[jss::api_version] = RPC::apiMinimumSupportedVersion.value; BEAST_EXPECT( RPC::getAPIVersionNumber(j_object, false) == RPC::apiMinimumSupportedVersion); - j_object[jss::api_version] = RPC::apiMaximumSupportedVersion; + j_object[jss::api_version] = RPC::apiMaximumSupportedVersion.value; BEAST_EXPECT( RPC::getAPIVersionNumber(j_object, false) == RPC::apiMaximumSupportedVersion); @@ -133,14 +134,14 @@ class Version_test : public beast::unit_test::suite BEAST_EXPECT( RPC::getAPIVersionNumber(j_object, false) == RPC::apiInvalidVersion); - j_object[jss::api_version] = RPC::apiBetaVersion; + j_object[jss::api_version] = RPC::apiBetaVersion.value; BEAST_EXPECT( RPC::getAPIVersionNumber(j_object, true) == RPC::apiBetaVersion); j_object[jss::api_version] = RPC::apiBetaVersion + 1; BEAST_EXPECT( RPC::getAPIVersionNumber(j_object, true) == RPC::apiInvalidVersion); - j_object[jss::api_version] = RPC::apiInvalidVersion; + j_object[jss::api_version] = RPC::apiInvalidVersion.value; BEAST_EXPECT( RPC::getAPIVersionNumber(j_object, false) == RPC::apiInvalidVersion); @@ -202,17 +203,17 @@ class Version_test : public beast::unit_test::suite "\"id\": 5, " "\"method\": \"version\", " "\"params\": {}}"; - auto const with_wrong_api_verion = - std::string("{ ") + + auto const with_wrong_api_verion = std::string("{ ") + "\"jsonrpc\": \"2.0\", " "\"ripplerpc\": \"2.0\", " "\"id\": 6, " "\"method\": \"version\", " "\"params\": { " "\"api_version\": " + - std::to_string( - std::max(RPC::apiMaximumSupportedVersion, RPC::apiBetaVersion) + - 1) + + std::to_string(std::max( + RPC::apiMaximumSupportedVersion.value, + RPC::apiBetaVersion.value) + + 1) + "}}"; auto re = env.rpc( "json2", @@ -275,8 +276,9 @@ class Version_test : public beast::unit_test::suite jrr[jss::version].isMember(jss::last)) return; BEAST_EXPECT( - jrr[jss::version][jss::first] == RPC::apiMinimumSupportedVersion); - BEAST_EXPECT(jrr[jss::version][jss::last] == RPC::apiBetaVersion); + jrr[jss::version][jss::first] == + RPC::apiMinimumSupportedVersion.value); + BEAST_EXPECT(jrr[jss::version][jss::last] == RPC::apiBetaVersion.value); } public: