From 2601f8d688490ce43d630557bfa3c0da701b75dd Mon Sep 17 00:00:00 2001 From: Alex Kremer Date: Tue, 11 Aug 2026 18:13:47 +0100 Subject: [PATCH 01/24] Introduce rpcspec 0.1.3 dependency --- CMakeLists.txt | 3 +++ conan.lock | 1 + conanfile.py | 1 + src/rpc/common/JsonBool.hpp | 51 +++---------------------------------- 4 files changed, 8 insertions(+), 48 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b68eca3630..f569c9217b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -84,6 +84,9 @@ include(deps/cassandra) include(deps/libbacktrace) include(deps/spdlog) +include(deps/rpcspec) +target_link_libraries(clio_options INTERFACE rpcspec::rpcspec) + add_subdirectory(src) add_subdirectory(tests) diff --git a/conan.lock b/conan.lock index 3d063ec20b..f74914b54c 100644 --- a/conan.lock +++ b/conan.lock @@ -3,6 +3,7 @@ "requires": [ "zlib/1.3.2#1cb806da49011867778ffb6ac7190fcb%1782392402.122708", "xxhash/0.8.3#681d36a0a6111fc56e5e45ea182c19cc%1782392402.420688", + "xrpl-rpc-spec/0.1.3#e0552b7fdcb26d9249498fa4273474f1%1786463175.210094", "xrpl/3.3.0-rc1-custom#cdfb12fc2671eaea72d5920bea800fd2%1785426802.141277", "sqlite3/3.53.0#324ada52333108388a9a6108bfa96734%1782392403.185447", "spdlog/1.17.0#bcbaaf7147bda6ad24ffbd1ac3d7142c%1782736610.443882", diff --git a/conanfile.py b/conanfile.py index a4139086ac..e039733435 100644 --- a/conanfile.py +++ b/conanfile.py @@ -17,6 +17,7 @@ class ClioConan(ConanFile): "fmt/12.1.0", "libbacktrace/cci.20210118", "spdlog/1.17.0", + "xrpl-rpc-spec/0.1.3", "xrpl/3.3.0-rc1-custom", ] diff --git a/src/rpc/common/JsonBool.hpp b/src/rpc/common/JsonBool.hpp index 2962abaa39..964238915f 100644 --- a/src/rpc/common/JsonBool.hpp +++ b/src/rpc/common/JsonBool.hpp @@ -1,58 +1,13 @@ #pragma once -#include -#include -#include -#include - -#include +#include namespace rpc { /** - * @brief A wrapper around bool that allows to convert from any JSON value - */ -struct JsonBool { - bool value = false; - - /** @cond */ - operator bool() const - { - return value; - } - /** @endcond */ -}; - -/** - * @brief Convert a JSON value to a JsonBool + * @brief A wrapper around bool that allows conversion from any JSON value. * - * @param jsonValue The JSON value to convert - * @return The converted JsonBool */ -inline JsonBool -tag_invoke(boost::json::value_to_tag const&, boost::json::value const& jsonValue) -{ - switch (jsonValue.kind()) { - case boost::json::kind::null: - return JsonBool{false}; - case boost::json::kind::bool_: - return JsonBool{jsonValue.as_bool()}; - case boost::json::kind::uint64: - [[fallthrough]]; - case boost::json::kind::int64: - return JsonBool{jsonValue.as_int64() != 0}; - case boost::json::kind::double_: - return JsonBool{jsonValue.as_double() != 0.0}; - case boost::json::kind::string: - // Also should be `jsonValue.as_string() != "false"` but rippled doesn't do - // that. Anyway for v2 api we have bool validation - return JsonBool{!jsonValue.as_string().empty() && jsonValue.as_string()[0] != 0}; - case boost::json::kind::array: - return JsonBool{!jsonValue.as_array().empty()}; - case boost::json::kind::object: - return JsonBool{!jsonValue.as_object().empty()}; - } - throw std::runtime_error("Invalid json value"); -} +using JsonBool = spec::JsonBool; } // namespace rpc From 8ce6b04867e00ae45d4ac3d78c7f75daa8a6e1d7 Mon Sep 17 00:00:00 2001 From: Alex Kremer Date: Tue, 11 Aug 2026 18:22:20 +0100 Subject: [PATCH 02/24] Add the missing cmake file back --- cmake/deps/rpcspec.cmake | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 cmake/deps/rpcspec.cmake diff --git a/cmake/deps/rpcspec.cmake b/cmake/deps/rpcspec.cmake new file mode 100644 index 0000000000..225bb6a05b --- /dev/null +++ b/cmake/deps/rpcspec.cmake @@ -0,0 +1,5 @@ +find_package(xrpl-rpc-spec REQUIRED CONFIG) + +if(NOT DEFINED RPCSPEC_IS_CLIO) + add_compile_definitions(RPCSPEC_IS_CLIO=1) +endif() From df881829106406a896a9a3ed763fbfcb2b455d8c Mon Sep 17 00:00:00 2001 From: Alex Kremer Date: Wed, 12 Aug 2026 11:49:12 +0100 Subject: [PATCH 03/24] Fix precommit --- cmake/deps/rpcspec.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/deps/rpcspec.cmake b/cmake/deps/rpcspec.cmake index 225bb6a05b..b77e67e004 100644 --- a/cmake/deps/rpcspec.cmake +++ b/cmake/deps/rpcspec.cmake @@ -1,5 +1,5 @@ find_package(xrpl-rpc-spec REQUIRED CONFIG) if(NOT DEFINED RPCSPEC_IS_CLIO) - add_compile_definitions(RPCSPEC_IS_CLIO=1) + add_compile_definitions(RPCSPEC_IS_CLIO=1) endif() From 3196ef59c1b350ab5f831cbd166d0ffd3d114aea Mon Sep 17 00:00:00 2001 From: Alex Kremer Date: Wed, 12 Aug 2026 13:47:44 +0100 Subject: [PATCH 04/24] Use errors from rpcspec --- src/etl/Errors.hpp | 11 + src/etl/LoadBalancer.cpp | 3 +- src/etl/Source.hpp | 4 +- src/etl/impl/ForwardingSource.cpp | 14 +- src/etl/impl/ForwardingSource.hpp | 4 +- src/etl/impl/SourceImpl.hpp | 6 +- src/rpc/Errors.cpp | 199 ++++++++++-------- src/rpc/Errors.hpp | 218 +------------------- src/web/impl/ErrorHandling.hpp | 6 +- src/web/ng/impl/ErrorHandling.cpp | 6 +- tests/common/util/MockSource.hpp | 6 +- tests/unit/etl/ETLStateTests.cpp | 4 +- tests/unit/etl/ForwardingSourceTests.cpp | 14 +- tests/unit/etl/LoadBalancerTests.cpp | 49 ++--- tests/unit/etl/SourceImplTests.cpp | 3 +- tests/unit/rpc/ErrorTests.cpp | 6 +- tests/unit/rpc/ForwardingProxyTests.cpp | 4 +- tests/unit/rpc/handlers/ServerInfoTests.cpp | 8 +- tests/unit/web/RPCServerHandlerTests.cpp | 20 +- 19 files changed, 216 insertions(+), 369 deletions(-) create mode 100644 src/etl/Errors.hpp diff --git a/src/etl/Errors.hpp b/src/etl/Errors.hpp new file mode 100644 index 0000000000..55a360e2d1 --- /dev/null +++ b/src/etl/Errors.hpp @@ -0,0 +1,11 @@ +/** @file */ +#pragma once + +#include + +namespace etl { + +/** @brief Error codes produced by ETL source operations. Aliased from rpc::EtlError. */ +using EtlError = rpc::EtlError; + +} // namespace etl diff --git a/src/etl/LoadBalancer.cpp b/src/etl/LoadBalancer.cpp index 9bb14b7776..f19bc07883 100644 --- a/src/etl/LoadBalancer.cpp +++ b/src/etl/LoadBalancer.cpp @@ -1,6 +1,7 @@ #include "etl/LoadBalancer.hpp" #include "data/BackendInterface.hpp" +#include "etl/Errors.hpp" #include "etl/ETLState.hpp" #include "etl/InitialLoadObserverInterface.hpp" #include "etl/LoadBalancerInterface.hpp" @@ -292,7 +293,7 @@ LoadBalancer::forwardToRippled( auto xUserValue = isAdmin ? kAdminForwardingXUserValue : kUserForwardingXUserValue; std::optional response; - rpc::ClioError error = rpc::ClioError::EtlConnectionError; + etl::EtlError error = etl::EtlError::ConnectionError; while (numAttempts < sources_.size()) { auto [res, duration] = util::timed([&]() { return sources_[sourceIdx]->forwardToRippled(request, clientIp, xUserValue, yield); diff --git a/src/etl/Source.hpp b/src/etl/Source.hpp index ca7acb9b80..58659c0f46 100644 --- a/src/etl/Source.hpp +++ b/src/etl/Source.hpp @@ -4,7 +4,7 @@ #include "etl/LoadBalancerInterface.hpp" #include "etl/NetworkValidatedLedgersInterface.hpp" #include "feed/SubscriptionManagerInterface.hpp" -#include "rpc/Errors.hpp" +#include "etl/Errors.hpp" #include "util/config/ObjectView.hpp" #include @@ -131,7 +131,7 @@ class SourceBase { * @param yield The coroutine context * @return Response on success or error on failure */ - [[nodiscard]] virtual std::expected + [[nodiscard]] virtual std::expected forwardToRippled( boost::json::object const& request, std::optional const& forwardToRippledClientIp, diff --git a/src/etl/impl/ForwardingSource.cpp b/src/etl/impl/ForwardingSource.cpp index 1ea8512efb..df997507e5 100644 --- a/src/etl/impl/ForwardingSource.cpp +++ b/src/etl/impl/ForwardingSource.cpp @@ -1,6 +1,6 @@ #include "etl/impl/ForwardingSource.hpp" -#include "rpc/Errors.hpp" +#include "etl/Errors.hpp" #include "util/log/Logger.hpp" #include @@ -38,7 +38,7 @@ ForwardingSource::ForwardingSource( ); } -std::expected +std::expected ForwardingSource::forwardToRippled( boost::json::object const& request, std::optional const& forwardToRippledClientIp, @@ -58,14 +58,14 @@ ForwardingSource::forwardToRippled( auto expectedConnection = connectionBuilder.connect(yield); if (not expectedConnection) { LOG(log_.debug()) << "Couldn't connect to rippled to forward request."; - return std::unexpected{rpc::ClioError::EtlConnectionError}; + return std::unexpected{EtlError::ConnectionError}; } auto& connection = expectedConnection.value(); auto writeError = connection->write(boost::json::serialize(request), yield, forwardingTimeout_); if (writeError) { LOG(log_.debug()) << "Error sending request to rippled to forward request."; - return std::unexpected{rpc::ClioError::EtlRequestError}; + return std::unexpected{EtlError::RequestError}; } auto response = connection->read(yield, forwardingTimeout_); @@ -73,10 +73,10 @@ ForwardingSource::forwardToRippled( if (auto errorCode = response.error().errorCode(); errorCode.has_value() and errorCode->value() == boost::system::errc::timed_out) { LOG(log_.debug()) << "Request to rippled timed out"; - return std::unexpected{rpc::ClioError::EtlRequestTimeout}; + return std::unexpected{EtlError::RequestTimeout}; } LOG(log_.debug()) << "Error sending request to rippled to forward request."; - return std::unexpected{rpc::ClioError::EtlRequestError}; + return std::unexpected{EtlError::RequestError}; } boost::json::value parsedResponse; @@ -87,7 +87,7 @@ ForwardingSource::forwardToRippled( } catch (std::exception const& e) { LOG(log_.debug()) << "Error parsing response from rippled: " << e.what() << ". Response: " << *response; - return std::unexpected{rpc::ClioError::EtlInvalidResponse}; + return std::unexpected{EtlError::InvalidResponse}; } auto responseObject = parsedResponse.as_object(); diff --git a/src/etl/impl/ForwardingSource.hpp b/src/etl/impl/ForwardingSource.hpp index 3806b9b71c..0f7114e686 100644 --- a/src/etl/impl/ForwardingSource.hpp +++ b/src/etl/impl/ForwardingSource.hpp @@ -1,6 +1,6 @@ #pragma once -#include "rpc/Errors.hpp" +#include "etl/Errors.hpp" #include "util/log/Logger.hpp" #include "util/requests/WsConnection.hpp" @@ -39,7 +39,7 @@ class ForwardingSource { * @param yield The coroutine context * @return Response on success or error on failure */ - [[nodiscard]] std::expected + [[nodiscard]] std::expected forwardToRippled( boost::json::object const& request, std::optional const& forwardToRippledClientIp, diff --git a/src/etl/impl/SourceImpl.hpp b/src/etl/impl/SourceImpl.hpp index 211c65d118..34d3cc8e69 100644 --- a/src/etl/impl/SourceImpl.hpp +++ b/src/etl/impl/SourceImpl.hpp @@ -6,7 +6,7 @@ #include "etl/impl/ForwardingSource.hpp" #include "etl/impl/GrpcSource.hpp" #include "etl/impl/SubscriptionSource.hpp" -#include "rpc/Errors.hpp" +#include "etl/Errors.hpp" #include #include @@ -208,9 +208,9 @@ class SourceImpl : public SourceBase { * @param forwardToRippledClientIp IP of the client forwarding this request if known * @param xUserValue Optional value of the X-User header * @param yield The coroutine context - * @return Response or ClioError + * @return Response or EtlError */ - [[nodiscard]] std::expected + [[nodiscard]] std::expected forwardToRippled( boost::json::object const& request, std::optional const& forwardToRippledClientIp, diff --git a/src/rpc/Errors.cpp b/src/rpc/Errors.cpp index f8ec37cfa4..807eb842a3 100644 --- a/src/rpc/Errors.cpp +++ b/src/rpc/Errors.cpp @@ -4,10 +4,12 @@ #include "util/OverloadSet.hpp" #include +#include #include #include #include +#include #include #include #include @@ -46,6 +48,16 @@ operator<<(std::ostream& stream, Status const& status) } else { stream << ", Message: " << getErrorInfo(err).message; } + }, + [&stream, &status](EtlError err) { + stream << "Code: " << static_cast>(err); + if (!status.error.empty()) + stream << ", Error: " << status.error; + if (!status.message.empty()) { + stream << ", Message: " << status.message; + } else { + stream << ", Message: " << getEtlErrorInfo(err).message; + } } }, status.code @@ -57,100 +69,111 @@ operator<<(std::ostream& stream, Status const& status) return stream; } -WarningInfo const& -getWarningInfo(WarningCode code) +EtlErrorInfo const& +getEtlErrorInfo(EtlError code) { - static constexpr WarningInfo kInfos[]{ - {WarningCode::WarnUnknown, "Unknown warning"}, - {WarningCode::WarnRpcClio, - "This is a clio server. clio only serves validated data. If you want to talk to rippled, " - "include " - "'ledger_index':'current' in your request"}, - {WarningCode::WarnRpcOutdated, "This server may be out of date"}, - {WarningCode::WarnRpcRateLimit, "You are about to be rate limited"}, - {WarningCode::WarnRpcDeprecated, - "Some fields from your request are deprecated. Please check the documentation at " - "https://xrpl.org/docs/references/http-websocket-apis/ and update your request."} - }; + static constexpr auto kInfos = std::to_array({ + { + .code = EtlError::ConnectionError, + .error = "connectionError", + .message = "Couldn't connect to rippled.", + }, + { + .code = EtlError::RequestError, + .error = "requestError", + .message = "Error sending request to rippled.", + }, + { + .code = EtlError::RequestTimeout, + .error = "timeout", + .message = "Request to rippled timed out.", + }, + { + .code = EtlError::InvalidResponse, + .error = "invalidResponse", + .message = "Rippled returned an invalid response.", + }, + }); - auto matchByCode = [code](auto const& info) { return info.code == code; }; - if (auto it = ranges::find_if(kInfos, matchByCode); it != end(kInfos)) + if (auto it = ranges::find(kInfos, code, &EtlErrorInfo::code); it != end(kInfos)) return *it; - throw(out_of_range("Invalid WarningCode")); -} - -boost::json::object -makeWarning(WarningCode code) -{ - auto json = boost::json::object{}; - auto const& info = getWarningInfo(code); - json["id"] = static_cast(code); - json["message"] = info.message; - return json; + throw(out_of_range("Invalid EtlError code")); } ClioErrorInfo const& getErrorInfo(ClioError code) { - static constexpr ClioErrorInfo kInfos[]{ - {.code = ClioError::RpcMalformedCurrency, - .error = "malformedCurrency", - .message = "Malformed currency."}, - {.code = ClioError::RpcMalformedRequest, - .error = "malformedRequest", - .message = "Malformed request."}, - {.code = ClioError::RpcMalformedOwner, - .error = "malformedOwner", - .message = "Malformed owner."}, - {.code = ClioError::RpcMalformedAddress, - .error = "malformedAddress", - .message = "Malformed address."}, - {.code = ClioError::RpcUnknownOption, - .error = "unknownOption", - .message = "Unknown option."}, - {.code = ClioError::RpcFieldNotFoundTransaction, - .error = "fieldNotFoundTransaction", - .message = "Missing field."}, - {.code = ClioError::RpcMalformedOracleDocumentId, - .error = "malformedDocumentID", - .message = "Malformed oracle_document_id."}, - {.code = ClioError::RpcMalformedAuthorizedCredentials, - .error = "malformedAuthorizedCredentials", - .message = "Malformed authorized credentials."}, + static constexpr auto kInfos = std::to_array({ + { + .code = ClioError::RpcMalformedCurrency, + .error = "malformedCurrency", + .message = "Malformed currency.", + }, + { + .code = ClioError::RpcMalformedRequest, + .error = "malformedRequest", + .message = "Malformed request.", + }, + { + .code = ClioError::RpcMalformedOwner, + .error = "malformedOwner", + .message = "Malformed owner.", + }, + { + .code = ClioError::RpcMalformedAddress, + .error = "malformedAddress", + .message = "Malformed address.", + }, + { + .code = ClioError::RpcUnknownOption, + .error = "unknownOption", + .message = "Unknown option.", + }, + { + .code = ClioError::RpcFieldNotFoundTransaction, + .error = "fieldNotFoundTransaction", + .message = "Missing field.", + }, + { + .code = ClioError::RpcMalformedOracleDocumentId, + .error = "malformedDocumentID", + .message = "Malformed oracle_document_id.", + }, + { + .code = ClioError::RpcMalformedAuthorizedCredentials, + .error = "malformedAuthorizedCredentials", + .message = "Malformed authorized credentials.", + }, // special system errors - {.code = ClioError::RpcInvalidApiVersion, - .error = JS(invalid_API_version), - .message = "Invalid API version."}, - {.code = ClioError::RpcCommandIsMissing, - .error = JS(missingCommand), - .message = "Method is not specified or is not a string."}, - {.code = ClioError::RpcCommandNotString, - .error = "commandNotString", - .message = "Method is not a string."}, - {.code = ClioError::RpcCommandIsEmpty, - .error = "emptyCommand", - .message = "Method is an empty string."}, - {.code = ClioError::RpcParamsUnparsable, - .error = "paramsUnparsable", - .message = "Params must be an array holding exactly one object."}, - // etl related errors - {.code = ClioError::EtlConnectionError, - .error = "connectionError", - .message = "Couldn't connect to rippled."}, - {.code = ClioError::EtlRequestError, - .error = "requestError", - .message = "Error sending request to rippled."}, - {.code = ClioError::EtlRequestTimeout, - .error = "timeout", - .message = "Request to rippled timed out."}, - {.code = ClioError::EtlInvalidResponse, - .error = "invalidResponse", - .message = "Rippled returned an invalid response."} - }; + { + .code = ClioError::RpcInvalidApiVersion, + .error = JS(invalid_API_version), + .message = "Invalid API version.", + }, + { + .code = ClioError::RpcCommandIsMissing, + .error = JS(missingCommand), + .message = "Method is not specified or is not a string.", + }, + { + .code = ClioError::RpcCommandNotString, + .error = "commandNotString", + .message = "Method is not a string.", + }, + { + .code = ClioError::RpcCommandIsEmpty, + .error = "emptyCommand", + .message = "Method is an empty string.", + }, + { + .code = ClioError::RpcParamsUnparsable, + .error = "paramsUnparsable", + .message = "Params must be an array holding exactly one object.", + }, + }); - auto matchByCode = [code](auto const& info) { return info.code == code; }; - if (auto it = ranges::find_if(kInfos, matchByCode); it != end(kInfos)) + if (auto it = ranges::find(kInfos, code, &ClioErrorInfo::code); it != end(kInfos)) return *it; throw(out_of_range("Invalid error code")); @@ -215,6 +238,16 @@ makeError(Status const& status) [&status, &wrapOptional](ClioError err) { return makeError(err, wrapOptional(status.error), wrapOptional(status.message)); }, + [](EtlError err) { + auto const& info = getEtlErrorInfo(err); + return boost::json::object{ + {"error", info.error}, + {"error_code", static_cast(err)}, + {"error_message", info.message}, + {"status", "error"}, + {"type", "response"} + }; + }, }, status.code ); diff --git a/src/rpc/Errors.hpp b/src/rpc/Errors.hpp index 5b06c365a4..d05e3f05c2 100644 --- a/src/rpc/Errors.hpp +++ b/src/rpc/Errors.hpp @@ -2,49 +2,16 @@ #pragma once #include -#include +#include #include #include #include #include #include -#include namespace rpc { -/** - * @brief Custom clio RPC Errors. - */ -enum class ClioError { - // normal clio errors start with 5000 - RpcMalformedCurrency = 5000, - RpcMalformedRequest = 5001, - RpcMalformedOwner = 5002, - RpcMalformedAddress = 5003, - RpcUnknownOption = 5005, - RpcFieldNotFoundTransaction = 5006, - RpcMalformedOracleDocumentId = 5007, - RpcMalformedAuthorizedCredentials = 5008, - // NOTE: RpcEntryNotFound is replaced with RippledError::RpcEntryNotFound - // RpcEntryNotFound = 5009, - - // special system errors start with 6000 - RpcInvalidApiVersion = 6000, - RpcCommandIsMissing = 6001, - RpcCommandNotString = 6002, - RpcCommandIsEmpty = 6003, - RpcParamsUnparsable = 6004, - - // TODO: Since it is not only rpc errors here now, we should move it to util - // etl related errors start with 7000 - // Higher value in this errors means better progress in the forwarding - EtlConnectionError = 7000, - EtlRequestError = 7001, - EtlRequestTimeout = 7002, - EtlInvalidResponse = 7003, -}; - /** * @brief Holds info about a particular @ref ClioError. */ @@ -55,166 +22,12 @@ struct ClioErrorInfo { }; /** - * @brief Clio uses compatible Rippled error codes for most RPC errors. - */ -using RippledError = xrpl::ErrorCodeI; - -/** - * @brief Clio operates on a combination of Rippled and Custom Clio error codes. - * - * @see RippledError For rippled error codes - * @see ClioError For custom clio error codes - */ -using CombinedError = std::variant; - -/** - * @brief A status returned from any RPC handler. - */ -struct Status { - CombinedError code = RippledError::RpcSuccess; - std::string error; - std::string message; - std::optional extraInfo; - - Status() = default; - - /** - * @brief Construct a new Status object - * - * @param code The error code - */ - /* implicit */ Status(CombinedError code) : code(code) {}; - - /** - * @brief Construct a new Status object - * - * @param code The error code - * @param extraInfo The extra info - */ - Status(CombinedError code, boost::json::object&& extraInfo) - : code(code), extraInfo(std::move(extraInfo)) {}; - - /** - * @brief Construct a new Status object with a custom message - * - * @note HACK. Some rippled handlers explicitly specify errors. This means that we have to be - * able to duplicate this functionality. - * - * @param message The message - */ - explicit Status(std::string message) : code(xrpl::RpcUnknown), message(std::move(message)) - { - } - - /** - * @brief Construct a new Status object - * - * @param code The error code - * @param message The message - */ - Status(CombinedError code, std::string message) : code(code), message(std::move(message)) - { - } - - /** - * @brief Construct a new Status object - * - * @param code The error code - * @param error The error - * @param message The message - */ - Status(CombinedError code, std::string error, std::string message) - : code(code), error(std::move(error)), message(std::move(message)) - { - } - - bool - operator==(Status const& other) const = default; - - /** - * @brief Check if the status is not OK - * - * @return true if the status is not OK; false otherwise - */ - operator bool() const - { - if (auto err = std::get_if(&code)) - return *err != RippledError::RpcSuccess; - - return true; - } - - /** - * @brief Returns true if the @ref rpc::Status contains the desired @ref rpc::RippledError - * - * @param other The @ref rpc::RippledError to match - * @return true if status matches given error; false otherwise - */ - bool - operator==(RippledError other) const - { - if (auto err = std::get_if(&code)) - return *err == other; - - return false; - } - - /** - * @brief Returns true if the Status contains the desired @ref ClioError - * - * @param other The RippledError to match - * @return true if status matches given error; false otherwise - */ - bool - operator==(ClioError other) const - { - if (auto err = std::get_if(&code)) - return *err == other; - - return false; - } - - /** - * @brief Custom output stream for Status - * - * @param stream The output stream - * @param status The Status - * @return The same ostream we were given - */ - friend std::ostream& - operator<<(std::ostream& stream, Status const& status); -}; - -/** - * @brief Warning codes that can be returned by clio. - */ -// NOLINTNEXTLINE(cppcoreguidelines-use-enum-class) -enum WarningCode { - WarnUnknown = -1, - WarnRpcClio = 2001, - WarnRpcOutdated = 2002, - WarnRpcRateLimit = 2003, - WarnRpcDeprecated = 2004 -}; - -/** - * @brief Holds information about a clio warning. + * @brief Holds info about a particular @ref rpc::EtlError. */ -struct WarningInfo { - constexpr WarningInfo() = default; - - /** - * @brief Construct a new Warning Info object - * - * @param code The warning code - * @param message The warning message - */ - constexpr WarningInfo(WarningCode code, char const* message) : code(code), message(message) - { - } - - WarningCode code = WarningCode::WarnUnknown; - std::string_view const message = "unknown warning"; +struct EtlErrorInfo { + EtlError const code; + std::string_view const error; + std::string_view const message; }; /** @@ -278,15 +91,6 @@ class AccountNotFoundError : public std::exception { */ static Status gOk; -/** - * @brief Get the warning info object from a warning code. - * - * @param code The warning code - * @return A reference to the static warning info - */ -WarningInfo const& -getWarningInfo(WarningCode code); - /** * @brief Get the error info object from an clio-specific error code. * @@ -297,13 +101,13 @@ ClioErrorInfo const& getErrorInfo(ClioError code); /** - * @brief Generate JSON from a @ref rpc::WarningCode. + * @brief Get the ETL error info object from an ETL error code. * - * @param code The warning code - * @return The JSON output + * @param code The error code + * @return A reference to the static error info */ -boost::json::object -makeWarning(WarningCode code); +EtlErrorInfo const& +getEtlErrorInfo(EtlError code); /** * @brief Generate JSON from a @ref rpc::Status. diff --git a/src/web/impl/ErrorHandling.hpp b/src/web/impl/ErrorHandling.hpp index 2f631b4485..fbc1b765c0 100644 --- a/src/web/impl/ErrorHandling.hpp +++ b/src/web/impl/ErrorHandling.hpp @@ -80,15 +80,13 @@ class ErrorHelper { case rpc::ClioError::RpcFieldNotFoundTransaction: case rpc::ClioError::RpcMalformedOracleDocumentId: case rpc::ClioError::RpcMalformedAuthorizedCredentials: - case rpc::ClioError::EtlConnectionError: - case rpc::ClioError::EtlRequestError: - case rpc::ClioError::EtlRequestTimeout: - case rpc::ClioError::EtlInvalidResponse: ASSERT( false, "Unknown rpc error code {}", static_cast(*clioCode) ); // this should never happen break; } + } else if (std::get_if(&err.code) != nullptr) { + ASSERT(false, "ETL error should not reach HTTP error handler"); // this should never happen } else { connection_->send( boost::json::serialize(composeError(err)), diff --git a/src/web/ng/impl/ErrorHandling.cpp b/src/web/ng/impl/ErrorHandling.cpp index c86ce259a1..d8a964811d 100644 --- a/src/web/ng/impl/ErrorHandling.cpp +++ b/src/web/ng/impl/ErrorHandling.cpp @@ -92,15 +92,13 @@ ErrorHelper::makeError(rpc::Status const& err) const case rpc::ClioError::RpcFieldNotFoundTransaction: case rpc::ClioError::RpcMalformedOracleDocumentId: case rpc::ClioError::RpcMalformedAuthorizedCredentials: - case rpc::ClioError::EtlConnectionError: - case rpc::ClioError::EtlRequestError: - case rpc::ClioError::EtlRequestTimeout: - case rpc::ClioError::EtlInvalidResponse: ASSERT( false, "Unknown rpc error code {}", static_cast(*clioCode) ); // this should never happen break; } + } else if (std::get_if(&err.code) != nullptr) { + ASSERT(false, "ETL error should not reach HTTP error handler"); // this should never happen } return Response{http::status::bad_request, composeError(err), rawRequest_}; diff --git a/tests/common/util/MockSource.hpp b/tests/common/util/MockSource.hpp index fbc4c4bbc1..ce15e75edb 100644 --- a/tests/common/util/MockSource.hpp +++ b/tests/common/util/MockSource.hpp @@ -5,7 +5,7 @@ #include "etl/NetworkValidatedLedgersInterface.hpp" #include "etl/Source.hpp" #include "feed/SubscriptionManagerInterface.hpp" -#include "rpc/Errors.hpp" +#include "etl/Errors.hpp" #include "util/config/ObjectView.hpp" #include @@ -50,7 +50,7 @@ struct MockSource : etl::SourceBase { (override) ); - using ForwardToRippledReturnType = std::expected; + using ForwardToRippledReturnType = std::expected; MOCK_METHOD( ForwardToRippledReturnType, forwardToRippled, @@ -132,7 +132,7 @@ class MockSourceWrapper : public etl::SourceBase { return mock_->loadInitialLedger(sequence, maxLedger, observer); } - [[nodiscard]] std::expected + [[nodiscard]] std::expected forwardToRippled( boost::json::object const& request, std::optional const& forwardToRippledClientIp, diff --git a/tests/unit/etl/ETLStateTests.cpp b/tests/unit/etl/ETLStateTests.cpp index 3db5702f63..e5641b17cb 100644 --- a/tests/unit/etl/ETLStateTests.cpp +++ b/tests/unit/etl/ETLStateTests.cpp @@ -1,5 +1,5 @@ #include "etl/ETLState.hpp" -#include "rpc/Errors.hpp" +#include "etl/Errors.hpp" #include "util/MockSource.hpp" #include @@ -18,7 +18,7 @@ struct ETLStateTest : public virtual ::testing::Test { TEST_F(ETLStateTest, Error) { EXPECT_CALL(source, forwardToRippled) - .WillOnce(Return(std::unexpected{rpc::ClioError::EtlInvalidResponse})); + .WillOnce(Return(std::unexpected{etl::EtlError::InvalidResponse})); auto const state = etl::ETLState::fetchETLStateFromSource(source); EXPECT_FALSE(state); } diff --git a/tests/unit/etl/ForwardingSourceTests.cpp b/tests/unit/etl/ForwardingSourceTests.cpp index f85d7ba930..40ba3efdee 100644 --- a/tests/unit/etl/ForwardingSourceTests.cpp +++ b/tests/unit/etl/ForwardingSourceTests.cpp @@ -1,5 +1,5 @@ #include "etl/impl/ForwardingSource.hpp" -#include "rpc/Errors.hpp" +#include "etl/Errors.hpp" #include "util/AsioContextTestFixture.hpp" #include "util/Spawn.hpp" #include "util/TestWsServer.hpp" @@ -35,7 +35,7 @@ TEST_F(ForwardingSourceTests, ConnectionFailed) runSpawn([&](boost::asio::yield_context yield) { auto result = forwardingSource_.forwardToRippled({}, {}, {}, yield); ASSERT_FALSE(result); - EXPECT_EQ(result.error(), rpc::ClioError::EtlConnectionError); + EXPECT_EQ(result.error(), etl::EtlError::ConnectionError); }); } @@ -79,7 +79,7 @@ TEST_F(ForwardingSourceOperationsTests, XUserHeader) boost::json::parse(message_).as_object(), {}, xUserValue, yield ); ASSERT_FALSE(result); - EXPECT_EQ(result.error(), rpc::ClioError::EtlRequestError); + EXPECT_EQ(result.error(), etl::EtlError::RequestError); }); } @@ -95,7 +95,7 @@ TEST_F(ForwardingSourceOperationsTests, ReadFailed) boost::json::parse(message_).as_object(), {}, {}, yield ); ASSERT_FALSE(result); - EXPECT_EQ(result.error(), rpc::ClioError::EtlRequestError); + EXPECT_EQ(result.error(), etl::EtlError::RequestError); }); } @@ -111,7 +111,7 @@ TEST_F(ForwardingSourceOperationsTests, ReadTimeout) boost::json::parse(message_).as_object(), {}, {}, yield ); ASSERT_FALSE(result); - EXPECT_EQ(result.error(), rpc::ClioError::EtlRequestTimeout); + EXPECT_EQ(result.error(), etl::EtlError::RequestTimeout); }); } @@ -136,7 +136,7 @@ TEST_F(ForwardingSourceOperationsTests, ParseFailed) boost::json::parse(message_).as_object(), {}, {}, yield ); ASSERT_FALSE(result); - EXPECT_EQ(result.error(), rpc::ClioError::EtlInvalidResponse); + EXPECT_EQ(result.error(), etl::EtlError::InvalidResponse); }); } @@ -162,7 +162,7 @@ TEST_F(ForwardingSourceOperationsTests, GotNotAnObject) boost::json::parse(message_).as_object(), {}, {}, yield ); ASSERT_FALSE(result); - EXPECT_EQ(result.error(), rpc::ClioError::EtlInvalidResponse); + EXPECT_EQ(result.error(), etl::EtlError::InvalidResponse); }); } diff --git a/tests/unit/etl/LoadBalancerTests.cpp b/tests/unit/etl/LoadBalancerTests.cpp index 243c276164..b383c4a719 100644 --- a/tests/unit/etl/LoadBalancerTests.cpp +++ b/tests/unit/etl/LoadBalancerTests.cpp @@ -3,6 +3,7 @@ #include "etl/LoadBalancerInterface.hpp" #include "etl/Models.hpp" #include "etl/Source.hpp" +#include "etl/Errors.hpp" #include "rpc/Errors.hpp" #include "util/AsioContextTestFixture.hpp" #include "util/MockBackendTestFixture.hpp" @@ -203,9 +204,9 @@ TEST_F(LoadBalancerConstructorTests, fetchETLState_AllSourcesFail) { EXPECT_CALL(sourceFactory_, makeSource).Times(2); EXPECT_CALL(sourceFactory_.sourceAt(0), forwardToRippled) - .WillOnce(Return(std::unexpected{rpc::ClioError::EtlConnectionError})); + .WillOnce(Return(std::unexpected{etl::EtlError::ConnectionError})); EXPECT_CALL(sourceFactory_.sourceAt(1), forwardToRippled) - .WillOnce(Return(std::unexpected{rpc::ClioError::EtlConnectionError})); + .WillOnce(Return(std::unexpected{etl::EtlError::ConnectionError})); EXPECT_THROW({ makeLoadBalancer(); }, std::logic_error); } @@ -225,7 +226,7 @@ TEST_F(LoadBalancerConstructorTests, fetchETLState_Source1Fails0OK) EXPECT_CALL(sourceFactory_.sourceAt(0), forwardToRippled) .WillOnce(Return(boost::json::object{})); EXPECT_CALL(sourceFactory_.sourceAt(1), forwardToRippled) - .WillOnce(Return(std::unexpected{rpc::ClioError::EtlConnectionError})); + .WillOnce(Return(std::unexpected{etl::EtlError::ConnectionError})); EXPECT_CALL(sourceFactory_.sourceAt(0), run); EXPECT_CALL(sourceFactory_.sourceAt(1), run); makeLoadBalancer(); @@ -235,7 +236,7 @@ TEST_F(LoadBalancerConstructorTests, fetchETLState_Source0Fails1OK) { EXPECT_CALL(sourceFactory_, makeSource).Times(2); EXPECT_CALL(sourceFactory_.sourceAt(0), forwardToRippled) - .WillOnce(Return(std::unexpected{rpc::ClioError::EtlConnectionError})); + .WillOnce(Return(std::unexpected{etl::EtlError::ConnectionError})); EXPECT_CALL(sourceFactory_.sourceAt(1), forwardToRippled) .WillOnce(Return(boost::json::object{})); EXPECT_CALL(sourceFactory_.sourceAt(0), run); @@ -265,7 +266,7 @@ TEST_F(LoadBalancerConstructorTests, fetchETLState_AllSourcesFailButAllowNoEtlIs .WillOnce(Return(boost::json::object{})); EXPECT_CALL(sourceFactory_.sourceAt(0), run); EXPECT_CALL(sourceFactory_.sourceAt(1), forwardToRippled) - .WillOnce(Return(std::unexpected{rpc::ClioError::EtlConnectionError})); + .WillOnce(Return(std::unexpected{etl::EtlError::ConnectionError})); EXPECT_CALL(sourceFactory_.sourceAt(1), run); configJson_.as_object()["allow_no_etl"] = true; @@ -716,7 +717,7 @@ TEST_F(LoadBalancerForwardToRippledTests, source0Fails) sourceFactory_.sourceAt(0), forwardToRippled(request_, clientIP_, LoadBalancer::kUserForwardingXUserValue, testing::_) ) - .WillOnce(Return(std::unexpected{rpc::ClioError::EtlConnectionError})); + .WillOnce(Return(std::unexpected{etl::EtlError::ConnectionError})); EXPECT_CALL( sourceFactory_.sourceAt(1), forwardToRippled(request_, clientIP_, LoadBalancer::kUserForwardingXUserValue, testing::_) @@ -819,7 +820,7 @@ TEST_F(LoadBalancerForwardToRippledPrometheusTests, source0Fails) sourceFactory_.sourceAt(0), forwardToRippled(request_, clientIP_, LoadBalancer::kUserForwardingXUserValue, testing::_) ) - .WillOnce(Return(std::unexpected{rpc::ClioError::EtlConnectionError})); + .WillOnce(Return(std::unexpected{etl::EtlError::ConnectionError})); EXPECT_CALL( sourceFactory_.sourceAt(1), forwardToRippled(request_, clientIP_, LoadBalancer::kUserForwardingXUserValue, testing::_) @@ -861,8 +862,8 @@ TEST_F(LoadBalancerForwardToRippledPrometheusTests, adminRequestAlwaysCacheMiss) struct LoadBalancerForwardToRippledErrorTestBundle { std::string testName; - rpc::ClioError firstSourceError; - rpc::ClioError secondSourceError; + etl::EtlError firstSourceError; + etl::EtlError secondSourceError; rpc::CombinedError responseExpectedError; }; @@ -876,33 +877,33 @@ INSTANTIATE_TEST_SUITE_P( testing::Values( LoadBalancerForwardToRippledErrorTestBundle{ "ConnectionError_RequestError", - rpc::ClioError::EtlConnectionError, - rpc::ClioError::EtlRequestError, - rpc::ClioError::EtlRequestError + etl::EtlError::ConnectionError, + etl::EtlError::RequestError, + etl::EtlError::RequestError }, LoadBalancerForwardToRippledErrorTestBundle{ "RequestError_RequestTimeout", - rpc::ClioError::EtlRequestError, - rpc::ClioError::EtlRequestTimeout, - rpc::ClioError::EtlRequestTimeout + etl::EtlError::RequestError, + etl::EtlError::RequestTimeout, + etl::EtlError::RequestTimeout }, LoadBalancerForwardToRippledErrorTestBundle{ "RequestTimeout_InvalidResponse", - rpc::ClioError::EtlRequestTimeout, - rpc::ClioError::EtlInvalidResponse, - rpc::ClioError::EtlInvalidResponse + etl::EtlError::RequestTimeout, + etl::EtlError::InvalidResponse, + etl::EtlError::InvalidResponse }, LoadBalancerForwardToRippledErrorTestBundle{ "BothRequestTimeout", - rpc::ClioError::EtlRequestTimeout, - rpc::ClioError::EtlRequestTimeout, - rpc::ClioError::EtlRequestTimeout + etl::EtlError::RequestTimeout, + etl::EtlError::RequestTimeout, + etl::EtlError::RequestTimeout }, LoadBalancerForwardToRippledErrorTestBundle{ "InvalidResponse_RequestError", - rpc::ClioError::EtlInvalidResponse, - rpc::ClioError::EtlRequestError, - rpc::ClioError::EtlInvalidResponse + etl::EtlError::InvalidResponse, + etl::EtlError::RequestError, + etl::EtlError::InvalidResponse } ), tests::util::kNameGenerator diff --git a/tests/unit/etl/SourceImplTests.cpp b/tests/unit/etl/SourceImplTests.cpp index f644b2badd..c250a92539 100644 --- a/tests/unit/etl/SourceImplTests.cpp +++ b/tests/unit/etl/SourceImplTests.cpp @@ -2,6 +2,7 @@ #include "etl/LoadBalancerInterface.hpp" #include "etl/Models.hpp" #include "etl/impl/SourceImpl.hpp" +#include "etl/Errors.hpp" #include "rpc/Errors.hpp" #include "util/Spawn.hpp" @@ -62,7 +63,7 @@ struct ForwardingSourceMock { (std::string const&, std::string const&, std::chrono::steady_clock::duration) ); - using ForwardToRippledReturnType = std::expected; + using ForwardToRippledReturnType = std::expected; using ClientIpOpt = std::optional; MOCK_METHOD( ForwardToRippledReturnType, diff --git a/tests/unit/rpc/ErrorTests.cpp b/tests/unit/rpc/ErrorTests.cpp index 2f78662545..207d177879 100644 --- a/tests/unit/rpc/ErrorTests.cpp +++ b/tests/unit/rpc/ErrorTests.cpp @@ -150,7 +150,7 @@ INSTANTIATE_TEST_SUITE_P( "Clio", WarningCode::WarnRpcClio, "This is a clio server. clio only serves validated data. If you want to talk to " - "rippled, include " + "xrpld, include " "'ledger_index':'current' in your request" }, WarningCodeTestBundle{ @@ -234,7 +234,7 @@ INSTANTIATE_TEST_SUITE_P( }, StatusStreamTestBundle{ .testName = "StatusWithCodeAndExtraInfo", - .status = Status{ClioError::EtlConnectionError, boost::json::object{}}, + .status = Status{EtlError::ConnectionError, boost::json::object{}}, .expectedOutput = "Code: 7000, Message: Couldn't connect to rippled., Extra Info: {}" }, StatusStreamTestBundle{ @@ -256,7 +256,7 @@ INSTANTIATE_TEST_SUITE_P( .testName = "StatusWithCodeErrorMessage", .status = Status{ - ClioError::EtlInvalidResponse, + EtlError::InvalidResponse, "invalidResponse", "Rippled returned an invalid response." }, diff --git a/tests/unit/rpc/ForwardingProxyTests.cpp b/tests/unit/rpc/ForwardingProxyTests.cpp index 396bfb5eb6..8f8e255acf 100644 --- a/tests/unit/rpc/ForwardingProxyTests.cpp +++ b/tests/unit/rpc/ForwardingProxyTests.cpp @@ -338,7 +338,7 @@ TEST_F(RPCForwardingProxyTest, ForwardingFailYieldsErrorStatus) *rawBalancerPtr, forwardToRippled(forwarded.as_object(), std::make_optional(kClientIp), true, _) ) - .WillOnce(Return(std::unexpected{rpc::ClioError::EtlInvalidResponse})); + .WillOnce(Return(std::unexpected{rpc::EtlError::InvalidResponse})); EXPECT_CALL(*rawHandlerProviderPtr, contains(method)).WillOnce(Return(true)); @@ -360,6 +360,6 @@ TEST_F(RPCForwardingProxyTest, ForwardingFailYieldsErrorStatus) auto const res = proxy_.forward(ctx); EXPECT_FALSE(res.response.has_value()); - EXPECT_EQ(res.response.error(), rpc::ClioError::EtlInvalidResponse); + EXPECT_EQ(res.response.error(), rpc::EtlError::InvalidResponse); }); } diff --git a/tests/unit/rpc/handlers/ServerInfoTests.cpp b/tests/unit/rpc/handlers/ServerInfoTests.cpp index c1e7cbdfcb..0a37ba8021 100644 --- a/tests/unit/rpc/handlers/ServerInfoTests.cpp +++ b/tests/unit/rpc/handlers/ServerInfoTests.cpp @@ -177,7 +177,7 @@ TEST_F(RPCServerInfoHandlerTest, DefaultOutputIsPresent) EXPECT_CALL( *rawBalancerPtr, forwardToRippled(testing::_, testing::Eq(kClientIp), false, testing::_) ) - .WillOnce(Return(std::unexpected{rpc::ClioError::EtlInvalidResponse})); + .WillOnce(Return(std::unexpected{rpc::EtlError::InvalidResponse})); EXPECT_CALL(*rawCountersPtr, uptime).WillOnce(Return(std::chrono::seconds{1234})); @@ -220,7 +220,7 @@ TEST_F(RPCServerInfoHandlerTest, AmendmentBlockedIsPresentIfSet) EXPECT_CALL( *rawBalancerPtr, forwardToRippled(testing::_, testing::Eq(kClientIp), false, testing::_) ) - .WillOnce(Return(std::unexpected{rpc::ClioError::EtlInvalidResponse})); + .WillOnce(Return(std::unexpected{rpc::EtlError::InvalidResponse})); EXPECT_CALL(*rawCountersPtr, uptime).WillOnce(Return(std::chrono::seconds{1234})); @@ -261,7 +261,7 @@ TEST_F(RPCServerInfoHandlerTest, CorruptionDetectedIsPresentIfSet) EXPECT_CALL( *rawBalancerPtr, forwardToRippled(testing::_, testing::Eq(kClientIp), false, testing::_) ) - .WillOnce(Return(std::unexpected{rpc::ClioError::EtlInvalidResponse})); + .WillOnce(Return(std::unexpected{rpc::EtlError::InvalidResponse})); EXPECT_CALL(*rawCountersPtr, uptime).WillOnce(Return(std::chrono::seconds{1234})); @@ -302,7 +302,7 @@ TEST_F(RPCServerInfoHandlerTest, CacheReportsEnabledFlagCorrectly) *rawBalancerPtr, forwardToRippled(testing::_, testing::Eq(kClientIp), false, testing::_) ) .Times(2) - .WillRepeatedly(Return(std::unexpected{rpc::ClioError::EtlInvalidResponse})); + .WillRepeatedly(Return(std::unexpected{rpc::EtlError::InvalidResponse})); EXPECT_CALL(*rawCountersPtr, uptime) .Times(2) diff --git a/tests/unit/web/RPCServerHandlerTests.cpp b/tests/unit/web/RPCServerHandlerTests.cpp index 4b5fb0fddc..13d12c4f89 100644 --- a/tests/unit/web/RPCServerHandlerTests.cpp +++ b/tests/unit/web/RPCServerHandlerTests.cpp @@ -122,7 +122,7 @@ TEST_F(WebRPCServerHandlerTest, HTTPDefaultPath) "warnings": [ { "id": 2001, - "message": "This is a clio server. clio only serves validated data. If you want to talk to rippled, include 'ledger_index':'current' in your request" + "message": "This is a clio server. clio only serves validated data. If you want to talk to xrpld, include 'ledger_index':'current' in your request" } ] })JSON"; @@ -190,7 +190,7 @@ TEST_F(WebRPCServerHandlerTest, WsNormalPath) "warnings": [ { "id": 2001, - "message": "This is a clio server. clio only serves validated data. If you want to talk to rippled, include 'ledger_index':'current' in your request" + "message": "This is a clio server. clio only serves validated data. If you want to talk to xrpld, include 'ledger_index':'current' in your request" } ] })JSON"; @@ -265,7 +265,7 @@ TEST_F(WebRPCServerHandlerTest, HTTPForwardedPath) "warnings": [ { "id": 2001, - "message": "This is a clio server. clio only serves validated data. If you want to talk to rippled, include 'ledger_index':'current' in your request" + "message": "This is a clio server. clio only serves validated data. If you want to talk to xrpld, include 'ledger_index':'current' in your request" } ] })JSON"; @@ -314,7 +314,7 @@ TEST_F(WebRPCServerHandlerTest, HTTPForwardedErrorPath) "warnings": [ { "id": 2001, - "message": "This is a clio server. clio only serves validated data. If you want to talk to rippled, include 'ledger_index':'current' in your request" + "message": "This is a clio server. clio only serves validated data. If you want to talk to xrpld, include 'ledger_index':'current' in your request" } ] })JSON"; @@ -361,7 +361,7 @@ TEST_F(WebRPCServerHandlerTest, WsForwardedPath) "warnings": [ { "id": 2001, - "message": "This is a clio server. clio only serves validated data. If you want to talk to rippled, include 'ledger_index':'current' in your request" + "message": "This is a clio server. clio only serves validated data. If you want to talk to xrpld, include 'ledger_index':'current' in your request" } ] })JSON"; @@ -412,7 +412,7 @@ TEST_F(WebRPCServerHandlerTest, WsForwardedErrorPath) "warnings": [ { "id": 2001, - "message": "This is a clio server. clio only serves validated data. If you want to talk to rippled, include 'ledger_index':'current' in your request" + "message": "This is a clio server. clio only serves validated data. If you want to talk to xrpld, include 'ledger_index':'current' in your request" } ] })JSON"; @@ -453,7 +453,7 @@ TEST_F(WebRPCServerHandlerTest, HTTPErrorPath) "warnings": [ { "id": 2001, - "message": "This is a clio server. clio only serves validated data. If you want to talk to rippled, include 'ledger_index':'current' in your request" + "message": "This is a clio server. clio only serves validated data. If you want to talk to xrpld, include 'ledger_index':'current' in your request" } ] })JSON"; @@ -510,7 +510,7 @@ TEST_F(WebRPCServerHandlerTest, WsErrorPath) "warnings": [ { "id": 2001, - "message": "This is a clio server. clio only serves validated data. If you want to talk to rippled, include 'ledger_index':'current' in your request" + "message": "This is a clio server. clio only serves validated data. If you want to talk to xrpld, include 'ledger_index':'current' in your request" } ] })JSON"; @@ -860,7 +860,7 @@ TEST_F(WebRPCServerHandlerTest, HTTPOutDated) "warnings": [ { "id": 2001, - "message": "This is a clio server. clio only serves validated data. If you want to talk to rippled, include 'ledger_index':'current' in your request" + "message": "This is a clio server. clio only serves validated data. If you want to talk to xrpld, include 'ledger_index':'current' in your request" }, { "id": 2002, @@ -903,7 +903,7 @@ TEST_F(WebRPCServerHandlerTest, WsOutdated) "warnings": [ { "id": 2001, - "message": "This is a clio server. clio only serves validated data. If you want to talk to rippled, include 'ledger_index':'current' in your request" + "message": "This is a clio server. clio only serves validated data. If you want to talk to xrpld, include 'ledger_index':'current' in your request" }, { "id": 2002, From 95b28f73afaf2ea74892de0e9512e3f6cc990994 Mon Sep 17 00:00:00 2001 From: Alex Kremer Date: Wed, 12 Aug 2026 13:59:11 +0100 Subject: [PATCH 05/24] Remove dead code --- src/rpc/Errors.hpp | 59 ---------------------------------------------- 1 file changed, 59 deletions(-) diff --git a/src/rpc/Errors.hpp b/src/rpc/Errors.hpp index d05e3f05c2..b79e2cdec4 100644 --- a/src/rpc/Errors.hpp +++ b/src/rpc/Errors.hpp @@ -4,11 +4,8 @@ #include #include -#include #include -#include #include -#include namespace rpc { @@ -30,62 +27,6 @@ struct EtlErrorInfo { std::string_view const message; }; -/** - * @brief Invalid parameters error. - */ -class InvalidParamsError : public std::exception { - std::string msg_; - -public: - /** - * @brief Construct a new Invalid Params Error object - * - * @param msg The error message - */ - explicit InvalidParamsError(std::string msg) : msg_(std::move(msg)) - { - } - - /** - * @brief Get the error message as a C string - * - * @return The error message - */ - [[nodiscard]] char const* - what() const noexcept override - { - return msg_.c_str(); - } -}; - -/** - * @brief Account not found error. - */ -class AccountNotFoundError : public std::exception { - std::string account_; - -public: - /** - * @brief Construct a new Account Not Found Error object - * - * @param acct The account - */ - explicit AccountNotFoundError(std::string acct) : account_(std::move(acct)) - { - } - - /** - * @brief Get the error message as a C string - * - * @return The error message - */ - [[nodiscard]] char const* - what() const noexcept override - { - return account_.c_str(); - } -}; - /** * @brief A globally available @ref rpc::Status that represents a successful state. */ From e31d7b530c23f0aff09442529ecdb51c2d4ef12c Mon Sep 17 00:00:00 2001 From: Alex Kremer Date: Wed, 12 Aug 2026 14:22:37 +0100 Subject: [PATCH 06/24] Fix clang-tidy --- src/app/WebHandlers.cpp | 1 + src/etl/LoadBalancer.cpp | 2 +- src/rpc/CredentialHelpers.cpp | 2 +- src/rpc/Factories.cpp | 2 +- src/rpc/RPCHelpers.cpp | 2 +- src/rpc/common/MetaProcessors.cpp | 2 +- src/rpc/common/Specs.cpp | 2 +- src/rpc/common/Validators.cpp | 2 +- src/rpc/handlers/AMMInfo.cpp | 2 +- src/rpc/handlers/AccountChannels.cpp | 2 +- src/rpc/handlers/AccountCurrencies.cpp | 2 +- src/rpc/handlers/AccountInfo.cpp | 2 +- src/rpc/handlers/AccountLines.cpp | 2 +- src/rpc/handlers/AccountMPTokenIssuances.cpp | 2 +- src/rpc/handlers/AccountMPTokens.cpp | 2 +- src/rpc/handlers/AccountNFTs.cpp | 2 +- src/rpc/handlers/AccountObjects.cpp | 2 +- src/rpc/handlers/AccountOffers.cpp | 2 +- src/rpc/handlers/AccountTx.cpp | 2 +- src/rpc/handlers/DepositAuthorized.cpp | 2 +- src/rpc/handlers/Feature.cpp | 2 +- src/rpc/handlers/MPTHolders.cpp | 2 +- src/web/ng/impl/ErrorHandling.cpp | 1 + tests/unit/app/WebHandlersTests.cpp | 2 +- tests/unit/rpc/RPCHelpersTests.cpp | 1 - tests/unit/rpc/common/CheckersTests.cpp | 2 +- tests/unit/rpc/handlers/AccountInfoTests.cpp | 1 + tests/unit/rpc/handlers/AccountLinesTests.cpp | 1 + tests/unit/rpc/handlers/LedgerDataTests.cpp | 1 + tests/unit/rpc/handlers/SubscribeTests.cpp | 1 + tests/unit/rpc/handlers/VaultInfoTests.cpp | 1 + tests/unit/web/impl/ErrorHandlingTests.cpp | 2 +- tests/unit/web/ng/RPCServerHandlerTests.cpp | 2 +- tests/unit/web/ng/impl/ErrorHandlingTests.cpp | 2 +- 34 files changed, 33 insertions(+), 27 deletions(-) diff --git a/src/app/WebHandlers.cpp b/src/app/WebHandlers.cpp index dce06c4120..a320927b5a 100644 --- a/src/app/WebHandlers.cpp +++ b/src/app/WebHandlers.cpp @@ -14,6 +14,7 @@ #include #include +#include #include #include diff --git a/src/etl/LoadBalancer.cpp b/src/etl/LoadBalancer.cpp index f19bc07883..03b4854f13 100644 --- a/src/etl/LoadBalancer.cpp +++ b/src/etl/LoadBalancer.cpp @@ -8,7 +8,6 @@ #include "etl/NetworkValidatedLedgersInterface.hpp" #include "etl/Source.hpp" #include "feed/SubscriptionManagerInterface.hpp" -#include "rpc/Errors.hpp" #include "util/Assert.hpp" #include "util/CoroutineGroup.hpp" #include "util/Profiler.hpp" @@ -28,6 +27,7 @@ #include #include #include +#include #include #include diff --git a/src/rpc/CredentialHelpers.cpp b/src/rpc/CredentialHelpers.cpp index 2601cb03e6..f89f904f14 100644 --- a/src/rpc/CredentialHelpers.cpp +++ b/src/rpc/CredentialHelpers.cpp @@ -1,5 +1,4 @@ #include "data/BackendInterface.hpp" -#include "rpc/Errors.hpp" #include "rpc/JS.hpp" #include "rpc/common/Types.hpp" #include "util/Assert.hpp" @@ -7,6 +6,7 @@ #include #include #include +#include #include #include #include diff --git a/src/rpc/Factories.cpp b/src/rpc/Factories.cpp index 6badd01479..22c4aa9964 100644 --- a/src/rpc/Factories.cpp +++ b/src/rpc/Factories.cpp @@ -1,7 +1,6 @@ #include "rpc/Factories.hpp" #include "data/Types.hpp" -#include "rpc/Errors.hpp" #include "rpc/common/APIVersion.hpp" #include "rpc/common/Types.hpp" #include "util/Taggable.hpp" @@ -13,6 +12,7 @@ #include #include #include +#include #include #include diff --git a/src/rpc/RPCHelpers.cpp b/src/rpc/RPCHelpers.cpp index 9b3600588a..4158b63582 100644 --- a/src/rpc/RPCHelpers.cpp +++ b/src/rpc/RPCHelpers.cpp @@ -4,7 +4,6 @@ #include "data/AmendmentCenterInterface.hpp" #include "data/BackendInterface.hpp" #include "data/Types.hpp" -#include "rpc/Errors.hpp" #include "rpc/JS.hpp" #include "rpc/common/Types.hpp" #include "util/AccountUtils.hpp" @@ -27,6 +26,7 @@ #include #include #include +#include #include #include #include diff --git a/src/rpc/common/MetaProcessors.cpp b/src/rpc/common/MetaProcessors.cpp index a5ab8cc7cf..6b6155d25c 100644 --- a/src/rpc/common/MetaProcessors.cpp +++ b/src/rpc/common/MetaProcessors.cpp @@ -1,9 +1,9 @@ #include "rpc/common/MetaProcessors.hpp" -#include "rpc/Errors.hpp" #include "rpc/common/Types.hpp" #include +#include #include diff --git a/src/rpc/common/Specs.cpp b/src/rpc/common/Specs.cpp index c837efeadf..ab0919265d 100644 --- a/src/rpc/common/Specs.cpp +++ b/src/rpc/common/Specs.cpp @@ -1,11 +1,11 @@ #include "rpc/common/Specs.hpp" -#include "rpc/Errors.hpp" #include "rpc/common/Checkers.hpp" #include "rpc/common/Types.hpp" #include #include +#include #include #include diff --git a/src/rpc/common/Validators.cpp b/src/rpc/common/Validators.cpp index 9be9275a3b..d2e1015fde 100644 --- a/src/rpc/common/Validators.cpp +++ b/src/rpc/common/Validators.cpp @@ -1,6 +1,5 @@ #include "rpc/common/Validators.hpp" -#include "rpc/Errors.hpp" #include "rpc/RPCHelpers.hpp" #include "rpc/common/Types.hpp" #include "util/AccountUtils.hpp" @@ -11,6 +10,7 @@ #include #include #include +#include #include #include #include diff --git a/src/rpc/handlers/AMMInfo.cpp b/src/rpc/handlers/AMMInfo.cpp index 3eb8f30020..685c253c30 100644 --- a/src/rpc/handlers/AMMInfo.cpp +++ b/src/rpc/handlers/AMMInfo.cpp @@ -2,7 +2,6 @@ #include "data/DBHelpers.hpp" #include "rpc/AMMHelpers.hpp" -#include "rpc/Errors.hpp" #include "rpc/JS.hpp" #include "rpc/RPCHelpers.hpp" #include "rpc/common/MetaProcessors.hpp" @@ -18,6 +17,7 @@ #include #include #include +#include #include #include #include diff --git a/src/rpc/handlers/AccountChannels.cpp b/src/rpc/handlers/AccountChannels.cpp index c977be2483..9a2ae4e092 100644 --- a/src/rpc/handlers/AccountChannels.cpp +++ b/src/rpc/handlers/AccountChannels.cpp @@ -1,6 +1,5 @@ #include "rpc/handlers/AccountChannels.hpp" -#include "rpc/Errors.hpp" #include "rpc/JS.hpp" #include "rpc/RPCHelpers.hpp" #include "rpc/common/Types.hpp" @@ -11,6 +10,7 @@ #include #include #include +#include #include #include #include diff --git a/src/rpc/handlers/AccountCurrencies.cpp b/src/rpc/handlers/AccountCurrencies.cpp index beceda7306..a72ebc3e91 100644 --- a/src/rpc/handlers/AccountCurrencies.cpp +++ b/src/rpc/handlers/AccountCurrencies.cpp @@ -1,6 +1,5 @@ #include "rpc/handlers/AccountCurrencies.hpp" -#include "rpc/Errors.hpp" #include "rpc/JS.hpp" #include "rpc/RPCHelpers.hpp" #include "rpc/common/Types.hpp" @@ -10,6 +9,7 @@ #include #include #include +#include #include #include #include diff --git a/src/rpc/handlers/AccountInfo.cpp b/src/rpc/handlers/AccountInfo.cpp index 16d02f4843..0112a64484 100644 --- a/src/rpc/handlers/AccountInfo.cpp +++ b/src/rpc/handlers/AccountInfo.cpp @@ -1,7 +1,6 @@ #include "rpc/handlers/AccountInfo.hpp" #include "data/AmendmentCenter.hpp" -#include "rpc/Errors.hpp" #include "rpc/JS.hpp" #include "rpc/RPCHelpers.hpp" #include "rpc/common/JsonBool.hpp" @@ -14,6 +13,7 @@ #include #include #include +#include #include #include #include diff --git a/src/rpc/handlers/AccountLines.cpp b/src/rpc/handlers/AccountLines.cpp index 8d0bacee66..982d2deefa 100644 --- a/src/rpc/handlers/AccountLines.cpp +++ b/src/rpc/handlers/AccountLines.cpp @@ -1,6 +1,5 @@ #include "rpc/handlers/AccountLines.hpp" -#include "rpc/Errors.hpp" #include "rpc/JS.hpp" #include "rpc/RPCHelpers.hpp" #include "rpc/common/Types.hpp" @@ -11,6 +10,7 @@ #include #include #include +#include #include #include #include diff --git a/src/rpc/handlers/AccountMPTokenIssuances.cpp b/src/rpc/handlers/AccountMPTokenIssuances.cpp index 5870adf67c..e3143a2542 100644 --- a/src/rpc/handlers/AccountMPTokenIssuances.cpp +++ b/src/rpc/handlers/AccountMPTokenIssuances.cpp @@ -1,6 +1,5 @@ #include "rpc/handlers/AccountMPTokenIssuances.hpp" -#include "rpc/Errors.hpp" #include "rpc/JS.hpp" #include "rpc/RPCHelpers.hpp" #include "rpc/common/Types.hpp" @@ -11,6 +10,7 @@ #include #include #include +#include #include #include #include diff --git a/src/rpc/handlers/AccountMPTokens.cpp b/src/rpc/handlers/AccountMPTokens.cpp index f5381cee2c..bede8e56c3 100644 --- a/src/rpc/handlers/AccountMPTokens.cpp +++ b/src/rpc/handlers/AccountMPTokens.cpp @@ -1,6 +1,5 @@ #include "rpc/handlers/AccountMPTokens.hpp" -#include "rpc/Errors.hpp" #include "rpc/JS.hpp" #include "rpc/RPCHelpers.hpp" #include "rpc/common/Types.hpp" @@ -12,6 +11,7 @@ #include #include #include +#include #include #include #include diff --git a/src/rpc/handlers/AccountNFTs.cpp b/src/rpc/handlers/AccountNFTs.cpp index 96b7f95829..2e65fc6cb9 100644 --- a/src/rpc/handlers/AccountNFTs.cpp +++ b/src/rpc/handlers/AccountNFTs.cpp @@ -1,6 +1,5 @@ #include "rpc/handlers/AccountNFTs.hpp" -#include "rpc/Errors.hpp" #include "rpc/JS.hpp" #include "rpc/RPCHelpers.hpp" #include "rpc/common/Types.hpp" @@ -10,6 +9,7 @@ #include #include #include +#include #include #include #include diff --git a/src/rpc/handlers/AccountObjects.cpp b/src/rpc/handlers/AccountObjects.cpp index 183ebd4e12..cdde3372b4 100644 --- a/src/rpc/handlers/AccountObjects.cpp +++ b/src/rpc/handlers/AccountObjects.cpp @@ -1,6 +1,5 @@ #include "rpc/handlers/AccountObjects.hpp" -#include "rpc/Errors.hpp" #include "rpc/JS.hpp" #include "rpc/RPCHelpers.hpp" #include "rpc/common/Types.hpp" @@ -12,6 +11,7 @@ #include #include #include +#include #include #include #include diff --git a/src/rpc/handlers/AccountOffers.cpp b/src/rpc/handlers/AccountOffers.cpp index 09abb815d1..c733ead9f8 100644 --- a/src/rpc/handlers/AccountOffers.cpp +++ b/src/rpc/handlers/AccountOffers.cpp @@ -1,6 +1,5 @@ #include "rpc/handlers/AccountOffers.hpp" -#include "rpc/Errors.hpp" #include "rpc/JS.hpp" #include "rpc/RPCHelpers.hpp" #include "rpc/common/Types.hpp" @@ -11,6 +10,7 @@ #include #include #include +#include #include #include #include diff --git a/src/rpc/handlers/AccountTx.cpp b/src/rpc/handlers/AccountTx.cpp index f15bfdb607..ec3c71950e 100644 --- a/src/rpc/handlers/AccountTx.cpp +++ b/src/rpc/handlers/AccountTx.cpp @@ -1,7 +1,6 @@ #include "rpc/handlers/AccountTx.hpp" #include "data/Types.hpp" -#include "rpc/Errors.hpp" #include "rpc/JS.hpp" #include "rpc/RPCHelpers.hpp" #include "rpc/common/JsonBool.hpp" @@ -17,6 +16,7 @@ #include #include #include +#include #include #include #include diff --git a/src/rpc/handlers/DepositAuthorized.cpp b/src/rpc/handlers/DepositAuthorized.cpp index e07fb503da..5ba93019d2 100644 --- a/src/rpc/handlers/DepositAuthorized.cpp +++ b/src/rpc/handlers/DepositAuthorized.cpp @@ -1,7 +1,6 @@ #include "rpc/handlers/DepositAuthorized.hpp" #include "rpc/CredentialHelpers.hpp" -#include "rpc/Errors.hpp" #include "rpc/JS.hpp" #include "rpc/RPCHelpers.hpp" #include "rpc/common/Types.hpp" @@ -13,6 +12,7 @@ #include #include #include +#include #include #include #include diff --git a/src/rpc/handlers/Feature.cpp b/src/rpc/handlers/Feature.cpp index f9821e5e92..a0b69c490f 100644 --- a/src/rpc/handlers/Feature.cpp +++ b/src/rpc/handlers/Feature.cpp @@ -1,7 +1,6 @@ #include "rpc/handlers/Feature.hpp" #include "data/Types.hpp" -#include "rpc/Errors.hpp" #include "rpc/JS.hpp" #include "rpc/RPCHelpers.hpp" #include "rpc/common/MetaProcessors.hpp" @@ -14,6 +13,7 @@ #include #include #include +#include #include #include #include diff --git a/src/rpc/handlers/MPTHolders.cpp b/src/rpc/handlers/MPTHolders.cpp index 247f7a8154..e84181ccd7 100644 --- a/src/rpc/handlers/MPTHolders.cpp +++ b/src/rpc/handlers/MPTHolders.cpp @@ -1,6 +1,5 @@ #include "rpc/handlers/MPTHolders.hpp" -#include "rpc/Errors.hpp" #include "rpc/JS.hpp" #include "rpc/RPCHelpers.hpp" #include "rpc/common/Types.hpp" @@ -11,6 +10,7 @@ #include #include #include +#include #include #include #include diff --git a/src/web/ng/impl/ErrorHandling.cpp b/src/web/ng/impl/ErrorHandling.cpp index d8a964811d..55a87a450c 100644 --- a/src/web/ng/impl/ErrorHandling.cpp +++ b/src/web/ng/impl/ErrorHandling.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include diff --git a/tests/unit/app/WebHandlersTests.cpp b/tests/unit/app/WebHandlersTests.cpp index cf86f38f42..5377ef4795 100644 --- a/tests/unit/app/WebHandlersTests.cpp +++ b/tests/unit/app/WebHandlersTests.cpp @@ -1,5 +1,4 @@ #include "app/WebHandlers.hpp" -#include "rpc/Errors.hpp" #include "rpc/WorkQueue.hpp" #include "util/AsioContextTestFixture.hpp" #include "util/MockLedgerCache.hpp" @@ -24,6 +23,7 @@ #include #include #include +#include #include #include diff --git a/tests/unit/rpc/RPCHelpersTests.cpp b/tests/unit/rpc/RPCHelpersTests.cpp index 779eb32191..91ac7409a5 100644 --- a/tests/unit/rpc/RPCHelpersTests.cpp +++ b/tests/unit/rpc/RPCHelpersTests.cpp @@ -1,6 +1,5 @@ #include "data/AmendmentCenter.hpp" #include "data/Types.hpp" -#include "rpc/Errors.hpp" #include "rpc/JS.hpp" #include "rpc/RPCHelpers.hpp" #include "rpc/common/Types.hpp" diff --git a/tests/unit/rpc/common/CheckersTests.cpp b/tests/unit/rpc/common/CheckersTests.cpp index 2c34322cba..a9c99d8999 100644 --- a/tests/unit/rpc/common/CheckersTests.cpp +++ b/tests/unit/rpc/common/CheckersTests.cpp @@ -1,8 +1,8 @@ -#include "rpc/Errors.hpp" #include "rpc/common/Checkers.hpp" #include #include +#include #include diff --git a/tests/unit/rpc/handlers/AccountInfoTests.cpp b/tests/unit/rpc/handlers/AccountInfoTests.cpp index 481e2c705f..5f54977cd7 100644 --- a/tests/unit/rpc/handlers/AccountInfoTests.cpp +++ b/tests/unit/rpc/handlers/AccountInfoTests.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include diff --git a/tests/unit/rpc/handlers/AccountLinesTests.cpp b/tests/unit/rpc/handlers/AccountLinesTests.cpp index d473349cfa..e9f42ba931 100644 --- a/tests/unit/rpc/handlers/AccountLinesTests.cpp +++ b/tests/unit/rpc/handlers/AccountLinesTests.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include diff --git a/tests/unit/rpc/handlers/LedgerDataTests.cpp b/tests/unit/rpc/handlers/LedgerDataTests.cpp index 5e3255ee7d..3bf7ed676c 100644 --- a/tests/unit/rpc/handlers/LedgerDataTests.cpp +++ b/tests/unit/rpc/handlers/LedgerDataTests.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include diff --git a/tests/unit/rpc/handlers/SubscribeTests.cpp b/tests/unit/rpc/handlers/SubscribeTests.cpp index 28e77e7290..83d8af7063 100644 --- a/tests/unit/rpc/handlers/SubscribeTests.cpp +++ b/tests/unit/rpc/handlers/SubscribeTests.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include diff --git a/tests/unit/rpc/handlers/VaultInfoTests.cpp b/tests/unit/rpc/handlers/VaultInfoTests.cpp index f1fc0afb01..73e9dc5804 100644 --- a/tests/unit/rpc/handlers/VaultInfoTests.cpp +++ b/tests/unit/rpc/handlers/VaultInfoTests.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include diff --git a/tests/unit/web/impl/ErrorHandlingTests.cpp b/tests/unit/web/impl/ErrorHandlingTests.cpp index 6b36c4ceef..a89d6f12db 100644 --- a/tests/unit/web/impl/ErrorHandlingTests.cpp +++ b/tests/unit/web/impl/ErrorHandlingTests.cpp @@ -1,4 +1,3 @@ -#include "rpc/Errors.hpp" #include "util/NameGenerator.hpp" #include "util/Taggable.hpp" #include "util/config/ConfigDefinition.hpp" @@ -12,6 +11,7 @@ #include #include #include +#include #include #include diff --git a/tests/unit/web/ng/RPCServerHandlerTests.cpp b/tests/unit/web/ng/RPCServerHandlerTests.cpp index 3a0f9c79a2..31486c4b09 100644 --- a/tests/unit/web/ng/RPCServerHandlerTests.cpp +++ b/tests/unit/web/ng/RPCServerHandlerTests.cpp @@ -1,4 +1,3 @@ -#include "rpc/Errors.hpp" #include "rpc/common/Types.hpp" #include "util/AsioContextTestFixture.hpp" #include "util/MockBackendTestFixture.hpp" @@ -25,6 +24,7 @@ #include #include #include +#include #include #include diff --git a/tests/unit/web/ng/impl/ErrorHandlingTests.cpp b/tests/unit/web/ng/impl/ErrorHandlingTests.cpp index f40721c359..2e16147e1d 100644 --- a/tests/unit/web/ng/impl/ErrorHandlingTests.cpp +++ b/tests/unit/web/ng/impl/ErrorHandlingTests.cpp @@ -1,4 +1,3 @@ -#include "rpc/Errors.hpp" #include "util/NameGenerator.hpp" #include "web/ng/Request.hpp" #include "web/ng/impl/ErrorHandling.hpp" @@ -11,6 +10,7 @@ #include #include #include +#include #include #include From aef5b353a2d5284365e501be2a4a0c4edda24177 Mon Sep 17 00:00:00 2001 From: Alex Kremer Date: Wed, 12 Aug 2026 14:26:01 +0100 Subject: [PATCH 07/24] Fix precommit --- src/etl/LoadBalancer.cpp | 2 +- src/etl/Source.hpp | 2 +- src/etl/impl/SourceImpl.hpp | 2 +- src/web/impl/ErrorHandling.hpp | 4 +++- tests/common/util/MockSource.hpp | 2 +- tests/unit/etl/ForwardingSourceTests.cpp | 2 +- tests/unit/etl/LoadBalancerTests.cpp | 2 +- tests/unit/etl/SourceImplTests.cpp | 2 +- 8 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/etl/LoadBalancer.cpp b/src/etl/LoadBalancer.cpp index 03b4854f13..3a743f992d 100644 --- a/src/etl/LoadBalancer.cpp +++ b/src/etl/LoadBalancer.cpp @@ -1,8 +1,8 @@ #include "etl/LoadBalancer.hpp" #include "data/BackendInterface.hpp" -#include "etl/Errors.hpp" #include "etl/ETLState.hpp" +#include "etl/Errors.hpp" #include "etl/InitialLoadObserverInterface.hpp" #include "etl/LoadBalancerInterface.hpp" #include "etl/NetworkValidatedLedgersInterface.hpp" diff --git a/src/etl/Source.hpp b/src/etl/Source.hpp index 58659c0f46..4bb606c3b3 100644 --- a/src/etl/Source.hpp +++ b/src/etl/Source.hpp @@ -1,10 +1,10 @@ #pragma once +#include "etl/Errors.hpp" #include "etl/InitialLoadObserverInterface.hpp" #include "etl/LoadBalancerInterface.hpp" #include "etl/NetworkValidatedLedgersInterface.hpp" #include "feed/SubscriptionManagerInterface.hpp" -#include "etl/Errors.hpp" #include "util/config/ObjectView.hpp" #include diff --git a/src/etl/impl/SourceImpl.hpp b/src/etl/impl/SourceImpl.hpp index 34d3cc8e69..ccd6f54445 100644 --- a/src/etl/impl/SourceImpl.hpp +++ b/src/etl/impl/SourceImpl.hpp @@ -1,12 +1,12 @@ #pragma once +#include "etl/Errors.hpp" #include "etl/InitialLoadObserverInterface.hpp" #include "etl/LoadBalancerInterface.hpp" #include "etl/Source.hpp" #include "etl/impl/ForwardingSource.hpp" #include "etl/impl/GrpcSource.hpp" #include "etl/impl/SubscriptionSource.hpp" -#include "etl/Errors.hpp" #include #include diff --git a/src/web/impl/ErrorHandling.hpp b/src/web/impl/ErrorHandling.hpp index fbc1b765c0..dee79d6db0 100644 --- a/src/web/impl/ErrorHandling.hpp +++ b/src/web/impl/ErrorHandling.hpp @@ -86,7 +86,9 @@ class ErrorHelper { break; } } else if (std::get_if(&err.code) != nullptr) { - ASSERT(false, "ETL error should not reach HTTP error handler"); // this should never happen + ASSERT( + false, "ETL error should not reach HTTP error handler" + ); // this should never happen } else { connection_->send( boost::json::serialize(composeError(err)), diff --git a/tests/common/util/MockSource.hpp b/tests/common/util/MockSource.hpp index ce15e75edb..c54897fd73 100644 --- a/tests/common/util/MockSource.hpp +++ b/tests/common/util/MockSource.hpp @@ -1,11 +1,11 @@ #pragma once +#include "etl/Errors.hpp" #include "etl/InitialLoadObserverInterface.hpp" #include "etl/LoadBalancerInterface.hpp" #include "etl/NetworkValidatedLedgersInterface.hpp" #include "etl/Source.hpp" #include "feed/SubscriptionManagerInterface.hpp" -#include "etl/Errors.hpp" #include "util/config/ObjectView.hpp" #include diff --git a/tests/unit/etl/ForwardingSourceTests.cpp b/tests/unit/etl/ForwardingSourceTests.cpp index 40ba3efdee..4971b13c27 100644 --- a/tests/unit/etl/ForwardingSourceTests.cpp +++ b/tests/unit/etl/ForwardingSourceTests.cpp @@ -1,5 +1,5 @@ -#include "etl/impl/ForwardingSource.hpp" #include "etl/Errors.hpp" +#include "etl/impl/ForwardingSource.hpp" #include "util/AsioContextTestFixture.hpp" #include "util/Spawn.hpp" #include "util/TestWsServer.hpp" diff --git a/tests/unit/etl/LoadBalancerTests.cpp b/tests/unit/etl/LoadBalancerTests.cpp index b383c4a719..2ab70e19cb 100644 --- a/tests/unit/etl/LoadBalancerTests.cpp +++ b/tests/unit/etl/LoadBalancerTests.cpp @@ -1,9 +1,9 @@ +#include "etl/Errors.hpp" #include "etl/InitialLoadObserverInterface.hpp" #include "etl/LoadBalancer.hpp" #include "etl/LoadBalancerInterface.hpp" #include "etl/Models.hpp" #include "etl/Source.hpp" -#include "etl/Errors.hpp" #include "rpc/Errors.hpp" #include "util/AsioContextTestFixture.hpp" #include "util/MockBackendTestFixture.hpp" diff --git a/tests/unit/etl/SourceImplTests.cpp b/tests/unit/etl/SourceImplTests.cpp index c250a92539..4d9b08df68 100644 --- a/tests/unit/etl/SourceImplTests.cpp +++ b/tests/unit/etl/SourceImplTests.cpp @@ -1,8 +1,8 @@ +#include "etl/Errors.hpp" #include "etl/InitialLoadObserverInterface.hpp" #include "etl/LoadBalancerInterface.hpp" #include "etl/Models.hpp" #include "etl/impl/SourceImpl.hpp" -#include "etl/Errors.hpp" #include "rpc/Errors.hpp" #include "util/Spawn.hpp" From 9d4ee75bd063fe886d96f28c36bc0c80b558edb1 Mon Sep 17 00:00:00 2001 From: Alex Kremer Date: Wed, 12 Aug 2026 14:34:10 +0100 Subject: [PATCH 08/24] Fix doxy --- src/rpc/Errors.cpp | 10 ++++++++++ src/rpc/Errors.hpp | 12 ++++++------ src/rpc/common/AnyHandler.hpp | 2 +- src/rpc/common/Specs.hpp | 4 ++-- 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/rpc/Errors.cpp b/src/rpc/Errors.cpp index 807eb842a3..ba1c12ccd1 100644 --- a/src/rpc/Errors.cpp +++ b/src/rpc/Errors.cpp @@ -24,6 +24,16 @@ using namespace std; namespace rpc { +/** + * @brief Stream a Status in human readable form. + * + * Declared in rpcspec but implemented here: rendering a code needs Clio's + * getErrorInfo/getEtlErrorInfo tables and xrpl::RPC::getErrorInfo. + * + * @param stream The stream to write to + * @param status The status to write + * @return The same stream + */ std::ostream& operator<<(std::ostream& stream, Status const& status) { diff --git a/src/rpc/Errors.hpp b/src/rpc/Errors.hpp index b79e2cdec4..8790a3cc7b 100644 --- a/src/rpc/Errors.hpp +++ b/src/rpc/Errors.hpp @@ -10,7 +10,7 @@ namespace rpc { /** - * @brief Holds info about a particular @ref ClioError. + * @brief Holds info about a particular ClioError. */ struct ClioErrorInfo { ClioError const code; @@ -19,7 +19,7 @@ struct ClioErrorInfo { }; /** - * @brief Holds info about a particular @ref rpc::EtlError. + * @brief Holds info about a particular rpc::EtlError. */ struct EtlErrorInfo { EtlError const code; @@ -28,7 +28,7 @@ struct EtlErrorInfo { }; /** - * @brief A globally available @ref rpc::Status that represents a successful state. + * @brief A globally available rpc::Status that represents a successful state. */ static Status gOk; @@ -51,7 +51,7 @@ EtlErrorInfo const& getEtlErrorInfo(EtlError code); /** - * @brief Generate JSON from a @ref rpc::Status. + * @brief Generate JSON from a rpc::Status. * * @param status The status object * @return The JSON output @@ -60,7 +60,7 @@ boost::json::object makeError(Status const& status); /** - * @brief Generate JSON from a @ref rpc::RippledError. + * @brief Generate JSON from a rpc::RippledError. * * @param err The rippled error * @param customError A custom error @@ -75,7 +75,7 @@ makeError( ); /** - * @brief Generate JSON from a @ref rpc::ClioError. + * @brief Generate JSON from a rpc::ClioError. * * @param err The clio's custom error * @param customError A custom error diff --git a/src/rpc/common/AnyHandler.hpp b/src/rpc/common/AnyHandler.hpp index 3c305d0806..c818a2e2c4 100644 --- a/src/rpc/common/AnyHandler.hpp +++ b/src/rpc/common/AnyHandler.hpp @@ -60,7 +60,7 @@ class AnyHandler final { * * @param value The JSON to process * @param ctx Request context - * @return JSON result or @ref Status on error + * @return JSON result or Status on error */ [[nodiscard]] ReturnType process(boost::json::value const& value, Context const& ctx) const diff --git a/src/rpc/common/Specs.hpp b/src/rpc/common/Specs.hpp index f3996da37d..5a5e6eaa7a 100644 --- a/src/rpc/common/Specs.hpp +++ b/src/rpc/common/Specs.hpp @@ -54,7 +54,7 @@ struct FieldSpec final { * @brief Processes the passed JSON value using the stored processors. * * @param value The JSON value to validate and/or modify - * @return Nothing on success; @ref Status on error + * @return Nothing on success; Status on error */ [[nodiscard]] MaybeError process(boost::json::value& value) const; @@ -106,7 +106,7 @@ struct RpcSpec final { * @brief Processes the passed JSON value using the stored field specs. * * @param value The JSON value to validate and/or modify - * @return Nothing on success; @ref Status on error + * @return Nothing on success; Status on error */ [[nodiscard]] MaybeError process(boost::json::value& value) const; From 48faa0cf27acd76276bae5bd99102ac23227b9ef Mon Sep 17 00:00:00 2001 From: Alex Kremer Date: Wed, 12 Aug 2026 15:04:38 +0100 Subject: [PATCH 09/24] Fix more clang-tidy --- src/rpc/handlers/GatewayBalances.cpp | 2 +- src/rpc/handlers/GetAggregatePrice.cpp | 1 - src/rpc/handlers/LedgerData.cpp | 2 +- src/rpc/handlers/LedgerEntry.cpp | 2 +- src/rpc/handlers/LedgerIndex.cpp | 2 +- src/rpc/handlers/NFTHistory.cpp | 2 +- src/rpc/handlers/NFTInfo.cpp | 2 +- src/rpc/handlers/NFTOffersCommon.cpp | 2 +- src/rpc/handlers/NFTsByIssuer.cpp | 2 +- src/rpc/handlers/NoRippleCheck.cpp | 2 +- src/rpc/handlers/Subscribe.cpp | 2 +- src/rpc/handlers/TransactionEntry.cpp | 2 +- src/rpc/handlers/Unsubscribe.cpp | 2 +- src/rpc/handlers/VaultInfo.cpp | 2 +- tests/unit/etl/LoadBalancerTests.cpp | 2 +- tests/unit/etl/SourceImplTests.cpp | 1 - tests/unit/rpc/BaseTests.cpp | 1 - tests/unit/rpc/ErrorTests.cpp | 1 + tests/unit/rpc/ForwardingProxyTests.cpp | 2 +- tests/unit/rpc/RPCEngineTests.cpp | 2 +- tests/unit/rpc/common/SpecsTests.cpp | 2 +- tests/unit/rpc/common/TypesTests.cpp | 1 - tests/unit/rpc/handlers/AccountCurrenciesTests.cpp | 1 + tests/unit/rpc/handlers/AccountOffersTests.cpp | 1 + tests/unit/rpc/handlers/CredentialHelpersTests.cpp | 2 +- tests/unit/rpc/handlers/LedgerEntryTests.cpp | 1 + tests/unit/rpc/handlers/LedgerTests.cpp | 1 + tests/unit/rpc/handlers/ServerInfoTests.cpp | 1 + tests/unit/rpc/handlers/TestHandlerTests.cpp | 1 + tests/unit/rpc/handlers/TxTests.cpp | 1 + tests/unit/rpc/handlers/UnsubscribeTests.cpp | 1 + tests/unit/web/RPCServerHandlerTests.cpp | 2 +- 32 files changed, 28 insertions(+), 23 deletions(-) diff --git a/src/rpc/handlers/GatewayBalances.cpp b/src/rpc/handlers/GatewayBalances.cpp index e9585d2bea..294bdd74c9 100644 --- a/src/rpc/handlers/GatewayBalances.cpp +++ b/src/rpc/handlers/GatewayBalances.cpp @@ -1,6 +1,5 @@ #include "rpc/handlers/GatewayBalances.hpp" -#include "rpc/Errors.hpp" #include "rpc/JS.hpp" #include "rpc/RPCHelpers.hpp" #include "rpc/common/Types.hpp" @@ -12,6 +11,7 @@ #include #include #include +#include #include #include #include diff --git a/src/rpc/handlers/GetAggregatePrice.cpp b/src/rpc/handlers/GetAggregatePrice.cpp index 0b8cc519ae..4d464a1f82 100644 --- a/src/rpc/handlers/GetAggregatePrice.cpp +++ b/src/rpc/handlers/GetAggregatePrice.cpp @@ -1,6 +1,5 @@ #include "rpc/handlers/GetAggregatePrice.hpp" -#include "rpc/Errors.hpp" #include "rpc/JS.hpp" #include "rpc/RPCHelpers.hpp" #include "rpc/common/Types.hpp" diff --git a/src/rpc/handlers/LedgerData.cpp b/src/rpc/handlers/LedgerData.cpp index 9a05caa40e..74724980c2 100644 --- a/src/rpc/handlers/LedgerData.cpp +++ b/src/rpc/handlers/LedgerData.cpp @@ -1,7 +1,6 @@ #include "rpc/handlers/LedgerData.hpp" #include "data/Types.hpp" -#include "rpc/Errors.hpp" #include "rpc/JS.hpp" #include "rpc/RPCHelpers.hpp" #include "rpc/common/Types.hpp" @@ -14,6 +13,7 @@ #include #include #include +#include #include #include #include diff --git a/src/rpc/handlers/LedgerEntry.cpp b/src/rpc/handlers/LedgerEntry.cpp index 20982b6280..c57928e9a4 100644 --- a/src/rpc/handlers/LedgerEntry.cpp +++ b/src/rpc/handlers/LedgerEntry.cpp @@ -1,7 +1,6 @@ #include "rpc/handlers/LedgerEntry.hpp" #include "rpc/CredentialHelpers.hpp" -#include "rpc/Errors.hpp" #include "rpc/JS.hpp" #include "rpc/RPCHelpers.hpp" #include "rpc/common/Types.hpp" @@ -13,6 +12,7 @@ #include #include #include +#include #include #include #include diff --git a/src/rpc/handlers/LedgerIndex.cpp b/src/rpc/handlers/LedgerIndex.cpp index 0315b242b7..350f96b095 100644 --- a/src/rpc/handlers/LedgerIndex.cpp +++ b/src/rpc/handlers/LedgerIndex.cpp @@ -1,6 +1,5 @@ #include "rpc/handlers/LedgerIndex.hpp" -#include "rpc/Errors.hpp" #include "rpc/JS.hpp" #include "rpc/common/Types.hpp" #include "util/Assert.hpp" @@ -9,6 +8,7 @@ #include #include #include +#include #include #include #include diff --git a/src/rpc/handlers/NFTHistory.cpp b/src/rpc/handlers/NFTHistory.cpp index 51a1f17914..f2229f7210 100644 --- a/src/rpc/handlers/NFTHistory.cpp +++ b/src/rpc/handlers/NFTHistory.cpp @@ -1,7 +1,6 @@ #include "rpc/handlers/NFTHistory.hpp" #include "data/Types.hpp" -#include "rpc/Errors.hpp" #include "rpc/JS.hpp" #include "rpc/RPCHelpers.hpp" #include "rpc/common/Types.hpp" @@ -15,6 +14,7 @@ #include #include #include +#include #include #include #include diff --git a/src/rpc/handlers/NFTInfo.cpp b/src/rpc/handlers/NFTInfo.cpp index acaad62b94..6506d78ce8 100644 --- a/src/rpc/handlers/NFTInfo.cpp +++ b/src/rpc/handlers/NFTInfo.cpp @@ -1,6 +1,5 @@ #include "rpc/handlers/NFTInfo.hpp" -#include "rpc/Errors.hpp" #include "rpc/JS.hpp" #include "rpc/RPCHelpers.hpp" #include "rpc/common/Types.hpp" @@ -11,6 +10,7 @@ #include #include #include +#include #include #include #include diff --git a/src/rpc/handlers/NFTOffersCommon.cpp b/src/rpc/handlers/NFTOffersCommon.cpp index ce4efe203e..b3797b14e0 100644 --- a/src/rpc/handlers/NFTOffersCommon.cpp +++ b/src/rpc/handlers/NFTOffersCommon.cpp @@ -1,6 +1,5 @@ #include "rpc/handlers/NFTOffersCommon.hpp" -#include "rpc/Errors.hpp" #include "rpc/JS.hpp" #include "rpc/RPCHelpers.hpp" #include "rpc/common/Types.hpp" @@ -12,6 +11,7 @@ #include #include #include +#include #include #include #include diff --git a/src/rpc/handlers/NFTsByIssuer.cpp b/src/rpc/handlers/NFTsByIssuer.cpp index 471b50d9a4..ca5a2eed8d 100644 --- a/src/rpc/handlers/NFTsByIssuer.cpp +++ b/src/rpc/handlers/NFTsByIssuer.cpp @@ -1,6 +1,5 @@ #include "rpc/handlers/NFTsByIssuer.hpp" -#include "rpc/Errors.hpp" #include "rpc/JS.hpp" #include "rpc/RPCHelpers.hpp" #include "rpc/common/Types.hpp" @@ -11,6 +10,7 @@ #include #include #include +#include #include #include #include diff --git a/src/rpc/handlers/NoRippleCheck.cpp b/src/rpc/handlers/NoRippleCheck.cpp index 10d3dd091b..2a5ff16018 100644 --- a/src/rpc/handlers/NoRippleCheck.cpp +++ b/src/rpc/handlers/NoRippleCheck.cpp @@ -1,6 +1,5 @@ #include "rpc/handlers/NoRippleCheck.hpp" -#include "rpc/Errors.hpp" #include "rpc/JS.hpp" #include "rpc/RPCHelpers.hpp" #include "rpc/common/JsonBool.hpp" @@ -14,6 +13,7 @@ #include #include #include +#include #include #include #include diff --git a/src/rpc/handlers/Subscribe.cpp b/src/rpc/handlers/Subscribe.cpp index 0782154571..d0f2a40740 100644 --- a/src/rpc/handlers/Subscribe.cpp +++ b/src/rpc/handlers/Subscribe.cpp @@ -5,7 +5,6 @@ #include "data/Types.hpp" #include "feed/SubscriptionManagerInterface.hpp" #include "feed/Types.hpp" -#include "rpc/Errors.hpp" #include "rpc/JS.hpp" #include "rpc/RPCHelpers.hpp" #include "rpc/common/Checkers.hpp" @@ -21,6 +20,7 @@ #include #include #include +#include #include #include #include diff --git a/src/rpc/handlers/TransactionEntry.cpp b/src/rpc/handlers/TransactionEntry.cpp index 9d21467568..083023bfb6 100644 --- a/src/rpc/handlers/TransactionEntry.cpp +++ b/src/rpc/handlers/TransactionEntry.cpp @@ -1,6 +1,5 @@ #include "rpc/handlers/TransactionEntry.hpp" -#include "rpc/Errors.hpp" #include "rpc/JS.hpp" #include "rpc/RPCHelpers.hpp" #include "rpc/common/Types.hpp" @@ -10,6 +9,7 @@ #include #include #include +#include #include #include #include diff --git a/src/rpc/handlers/Unsubscribe.cpp b/src/rpc/handlers/Unsubscribe.cpp index d0923cd4ee..f7c521adf3 100644 --- a/src/rpc/handlers/Unsubscribe.cpp +++ b/src/rpc/handlers/Unsubscribe.cpp @@ -2,7 +2,6 @@ #include "feed/SubscriptionManagerInterface.hpp" #include "feed/Types.hpp" -#include "rpc/Errors.hpp" #include "rpc/JS.hpp" #include "rpc/RPCHelpers.hpp" #include "rpc/common/Checkers.hpp" @@ -14,6 +13,7 @@ #include #include #include +#include #include #include diff --git a/src/rpc/handlers/VaultInfo.cpp b/src/rpc/handlers/VaultInfo.cpp index 967f19a038..7fee9f2ae4 100644 --- a/src/rpc/handlers/VaultInfo.cpp +++ b/src/rpc/handlers/VaultInfo.cpp @@ -1,7 +1,6 @@ #include "rpc/handlers/VaultInfo.hpp" #include "data/BackendInterface.hpp" -#include "rpc/Errors.hpp" #include "rpc/JS.hpp" #include "rpc/RPCHelpers.hpp" #include "rpc/common/Types.hpp" @@ -11,6 +10,7 @@ #include #include #include +#include #include #include #include diff --git a/tests/unit/etl/LoadBalancerTests.cpp b/tests/unit/etl/LoadBalancerTests.cpp index 2ab70e19cb..6e3fab0a9d 100644 --- a/tests/unit/etl/LoadBalancerTests.cpp +++ b/tests/unit/etl/LoadBalancerTests.cpp @@ -4,7 +4,6 @@ #include "etl/LoadBalancerInterface.hpp" #include "etl/Models.hpp" #include "etl/Source.hpp" -#include "rpc/Errors.hpp" #include "util/AsioContextTestFixture.hpp" #include "util/MockBackendTestFixture.hpp" #include "util/MockNetworkValidatedLedgers.hpp" @@ -31,6 +30,7 @@ #include #include #include +#include #include #include diff --git a/tests/unit/etl/SourceImplTests.cpp b/tests/unit/etl/SourceImplTests.cpp index 4d9b08df68..cd97fd7f9b 100644 --- a/tests/unit/etl/SourceImplTests.cpp +++ b/tests/unit/etl/SourceImplTests.cpp @@ -3,7 +3,6 @@ #include "etl/LoadBalancerInterface.hpp" #include "etl/Models.hpp" #include "etl/impl/SourceImpl.hpp" -#include "rpc/Errors.hpp" #include "util/Spawn.hpp" #include diff --git a/tests/unit/rpc/BaseTests.cpp b/tests/unit/rpc/BaseTests.cpp index 0c09ea4aa8..af27bce4a2 100644 --- a/tests/unit/rpc/BaseTests.cpp +++ b/tests/unit/rpc/BaseTests.cpp @@ -1,4 +1,3 @@ -#include "rpc/Errors.hpp" #include "rpc/common/MetaProcessors.hpp" #include "rpc/common/Modifiers.hpp" #include "rpc/common/Specs.hpp" diff --git a/tests/unit/rpc/ErrorTests.cpp b/tests/unit/rpc/ErrorTests.cpp index 207d177879..31a4a2fc3f 100644 --- a/tests/unit/rpc/ErrorTests.cpp +++ b/tests/unit/rpc/ErrorTests.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include diff --git a/tests/unit/rpc/ForwardingProxyTests.cpp b/tests/unit/rpc/ForwardingProxyTests.cpp index 8f8e255acf..4a7eaff63f 100644 --- a/tests/unit/rpc/ForwardingProxyTests.cpp +++ b/tests/unit/rpc/ForwardingProxyTests.cpp @@ -1,5 +1,4 @@ #include "data/Types.hpp" -#include "rpc/Errors.hpp" #include "rpc/common/impl/ForwardingProxy.hpp" #include "util/HandlerBaseTestFixture.hpp" #include "util/MockCounters.hpp" @@ -16,6 +15,7 @@ #include #include #include +#include #include #include diff --git a/tests/unit/rpc/RPCEngineTests.cpp b/tests/unit/rpc/RPCEngineTests.cpp index 48820e48b6..840e05c5cb 100644 --- a/tests/unit/rpc/RPCEngineTests.cpp +++ b/tests/unit/rpc/RPCEngineTests.cpp @@ -1,6 +1,5 @@ #include "data/BackendInterface.hpp" #include "data/Types.hpp" -#include "rpc/Errors.hpp" #include "rpc/FakesAndMocks.hpp" #include "rpc/RPCEngine.hpp" #include "rpc/WorkQueue.hpp" @@ -29,6 +28,7 @@ #include #include #include +#include #include #include diff --git a/tests/unit/rpc/common/SpecsTests.cpp b/tests/unit/rpc/common/SpecsTests.cpp index 8e48db857c..620a22b7c1 100644 --- a/tests/unit/rpc/common/SpecsTests.cpp +++ b/tests/unit/rpc/common/SpecsTests.cpp @@ -1,4 +1,3 @@ -#include "rpc/Errors.hpp" #include "rpc/common/Checkers.hpp" #include "rpc/common/Specs.hpp" #include "rpc/common/Types.hpp" @@ -7,6 +6,7 @@ #include #include #include +#include #include #include diff --git a/tests/unit/rpc/common/TypesTests.cpp b/tests/unit/rpc/common/TypesTests.cpp index 3ee5eb037a..eec172d5a2 100644 --- a/tests/unit/rpc/common/TypesTests.cpp +++ b/tests/unit/rpc/common/TypesTests.cpp @@ -1,4 +1,3 @@ -#include "rpc/Errors.hpp" #include "rpc/common/Types.hpp" #include diff --git a/tests/unit/rpc/handlers/AccountCurrenciesTests.cpp b/tests/unit/rpc/handlers/AccountCurrenciesTests.cpp index 8220331d50..909a752794 100644 --- a/tests/unit/rpc/handlers/AccountCurrenciesTests.cpp +++ b/tests/unit/rpc/handlers/AccountCurrenciesTests.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include diff --git a/tests/unit/rpc/handlers/AccountOffersTests.cpp b/tests/unit/rpc/handlers/AccountOffersTests.cpp index 447968bd8a..e9d70b1113 100644 --- a/tests/unit/rpc/handlers/AccountOffersTests.cpp +++ b/tests/unit/rpc/handlers/AccountOffersTests.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include diff --git a/tests/unit/rpc/handlers/CredentialHelpersTests.cpp b/tests/unit/rpc/handlers/CredentialHelpersTests.cpp index 07cf381b08..b11935bb57 100644 --- a/tests/unit/rpc/handlers/CredentialHelpersTests.cpp +++ b/tests/unit/rpc/handlers/CredentialHelpersTests.cpp @@ -1,5 +1,4 @@ #include "rpc/CredentialHelpers.hpp" -#include "rpc/Errors.hpp" #include "rpc/JS.hpp" #include "util/AsioContextTestFixture.hpp" #include "util/MockBackendTestFixture.hpp" @@ -12,6 +11,7 @@ #include #include #include +#include #include #include #include diff --git a/tests/unit/rpc/handlers/LedgerEntryTests.cpp b/tests/unit/rpc/handlers/LedgerEntryTests.cpp index 5a7e66c4f6..75032aef9e 100644 --- a/tests/unit/rpc/handlers/LedgerEntryTests.cpp +++ b/tests/unit/rpc/handlers/LedgerEntryTests.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include diff --git a/tests/unit/rpc/handlers/LedgerTests.cpp b/tests/unit/rpc/handlers/LedgerTests.cpp index 1b04f60afb..6ce57bb94a 100644 --- a/tests/unit/rpc/handlers/LedgerTests.cpp +++ b/tests/unit/rpc/handlers/LedgerTests.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include diff --git a/tests/unit/rpc/handlers/ServerInfoTests.cpp b/tests/unit/rpc/handlers/ServerInfoTests.cpp index 0a37ba8021..a1a2ddd288 100644 --- a/tests/unit/rpc/handlers/ServerInfoTests.cpp +++ b/tests/unit/rpc/handlers/ServerInfoTests.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include diff --git a/tests/unit/rpc/handlers/TestHandlerTests.cpp b/tests/unit/rpc/handlers/TestHandlerTests.cpp index c30c39f15b..84a07769fe 100644 --- a/tests/unit/rpc/handlers/TestHandlerTests.cpp +++ b/tests/unit/rpc/handlers/TestHandlerTests.cpp @@ -6,6 +6,7 @@ #include #include +#include using namespace std; using namespace rpc; diff --git a/tests/unit/rpc/handlers/TxTests.cpp b/tests/unit/rpc/handlers/TxTests.cpp index b8acacc69a..e7e4f63fcd 100644 --- a/tests/unit/rpc/handlers/TxTests.cpp +++ b/tests/unit/rpc/handlers/TxTests.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include diff --git a/tests/unit/rpc/handlers/UnsubscribeTests.cpp b/tests/unit/rpc/handlers/UnsubscribeTests.cpp index c515bf7753..04c23300e6 100644 --- a/tests/unit/rpc/handlers/UnsubscribeTests.cpp +++ b/tests/unit/rpc/handlers/UnsubscribeTests.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include diff --git a/tests/unit/web/RPCServerHandlerTests.cpp b/tests/unit/web/RPCServerHandlerTests.cpp index 13d12c4f89..fb98796547 100644 --- a/tests/unit/web/RPCServerHandlerTests.cpp +++ b/tests/unit/web/RPCServerHandlerTests.cpp @@ -1,4 +1,3 @@ -#include "rpc/Errors.hpp" #include "rpc/common/APIVersion.hpp" #include "rpc/common/Types.hpp" #include "util/AsioContextTestFixture.hpp" @@ -21,6 +20,7 @@ #include #include #include +#include #include #include From 577bc40827119744819b5df2b78f4a5760a8aedd Mon Sep 17 00:00:00 2001 From: Alex Kremer Date: Wed, 26 Aug 2026 18:02:45 +0100 Subject: [PATCH 10/24] Fix clang-tidy --- src/rpc/handlers/MPTokenIssuanceHistory.cpp | 2 +- tests/unit/rpc/BaseTests.cpp | 1 + tests/unit/rpc/RPCHelpersTests.cpp | 2 ++ tests/unit/web/LoadWarningTests.cpp | 2 +- 4 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/rpc/handlers/MPTokenIssuanceHistory.cpp b/src/rpc/handlers/MPTokenIssuanceHistory.cpp index 813528ed3f..b03d6b78cf 100644 --- a/src/rpc/handlers/MPTokenIssuanceHistory.cpp +++ b/src/rpc/handlers/MPTokenIssuanceHistory.cpp @@ -1,7 +1,6 @@ #include "rpc/handlers/MPTokenIssuanceHistory.hpp" #include "data/Types.hpp" -#include "rpc/Errors.hpp" #include "rpc/JS.hpp" #include "rpc/RPCHelpers.hpp" #include "rpc/common/Types.hpp" @@ -15,6 +14,7 @@ #include #include #include +#include #include #include #include diff --git a/tests/unit/rpc/BaseTests.cpp b/tests/unit/rpc/BaseTests.cpp index c780e9c666..70f49b0113 100644 --- a/tests/unit/rpc/BaseTests.cpp +++ b/tests/unit/rpc/BaseTests.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include diff --git a/tests/unit/rpc/RPCHelpersTests.cpp b/tests/unit/rpc/RPCHelpersTests.cpp index 921e395adf..55fce5e97f 100644 --- a/tests/unit/rpc/RPCHelpersTests.cpp +++ b/tests/unit/rpc/RPCHelpersTests.cpp @@ -1,5 +1,6 @@ #include "data/AmendmentCenter.hpp" #include "data/Types.hpp" +#include "rpc/Errors.hpp" #include "rpc/JS.hpp" #include "rpc/RPCHelpers.hpp" #include "rpc/common/Types.hpp" @@ -24,6 +25,7 @@ #include #include #include +#include #include #include #include diff --git a/tests/unit/web/LoadWarningTests.cpp b/tests/unit/web/LoadWarningTests.cpp index c4adaf34af..d174981bb1 100644 --- a/tests/unit/web/LoadWarningTests.cpp +++ b/tests/unit/web/LoadWarningTests.cpp @@ -1,7 +1,7 @@ -#include "rpc/Errors.hpp" #include "web/LoadWarning.hpp" #include +#include #include #include From 30621e6467ddd4fab2f0af11a00f9b8dc3b1e4b9 Mon Sep 17 00:00:00 2001 From: Alex Kremer Date: Thu, 27 Aug 2026 13:12:53 +0100 Subject: [PATCH 11/24] Rename to RpcForwarding errors and move back to rpc-spec --- conan.lock | 2 +- conanfile.py | 2 +- src/etl/Errors.hpp | 11 --- src/etl/LoadBalancer.cpp | 3 +- src/etl/Source.hpp | 4 +- src/etl/impl/ForwardingSource.cpp | 14 ++-- src/etl/impl/ForwardingSource.hpp | 4 +- src/etl/impl/SourceImpl.hpp | 6 +- src/rpc/Errors.cpp | 75 ++++++--------------- src/rpc/Errors.hpp | 18 ----- src/web/impl/ErrorHandling.hpp | 8 +-- src/web/ng/impl/ErrorHandling.cpp | 6 +- tests/common/util/MockSource.hpp | 6 +- tests/unit/etl/ETLStateTests.cpp | 4 +- tests/unit/etl/ForwardingSourceTests.cpp | 14 ++-- tests/unit/etl/LoadBalancerTests.cpp | 49 +++++++------- tests/unit/etl/SourceImplTests.cpp | 4 +- tests/unit/rpc/ErrorTests.cpp | 4 +- tests/unit/rpc/ForwardingProxyTests.cpp | 4 +- tests/unit/rpc/handlers/ServerInfoTests.cpp | 8 +-- 20 files changed, 93 insertions(+), 153 deletions(-) delete mode 100644 src/etl/Errors.hpp diff --git a/conan.lock b/conan.lock index c0a1fcd70c..a599de0815 100644 --- a/conan.lock +++ b/conan.lock @@ -3,7 +3,7 @@ "requires": [ "zlib/1.3.2#1cb806da49011867778ffb6ac7190fcb%1782392402.122708", "xxhash/0.8.3#681d36a0a6111fc56e5e45ea182c19cc%1782392402.420688", - "xrpl-rpc-spec/0.1.6#0147dce06088874791dca62dc8503b6c%1787751048.16847", + "xrpl-rpc-spec/0.1.7#774d2f93c4b48a1523d8d5a94a2b082a%1787768955.574105", "xrpl/3.3.0#5e356a24ae1f0d6da6bd617b926f92e6%1786467262.262007", "sqlite3/3.53.0#324ada52333108388a9a6108bfa96734%1782392403.185447", "spdlog/1.17.0#bcbaaf7147bda6ad24ffbd1ac3d7142c%1782736610.443882", diff --git a/conanfile.py b/conanfile.py index 3ef0797cf7..056f092b27 100644 --- a/conanfile.py +++ b/conanfile.py @@ -17,7 +17,7 @@ class ClioConan(ConanFile): "fmt/12.1.0", "libbacktrace/cci.20210118", "spdlog/1.17.0", - "xrpl-rpc-spec/0.1.6", + "xrpl-rpc-spec/0.1.7", "xrpl/3.3.0", ] diff --git a/src/etl/Errors.hpp b/src/etl/Errors.hpp deleted file mode 100644 index 55a360e2d1..0000000000 --- a/src/etl/Errors.hpp +++ /dev/null @@ -1,11 +0,0 @@ -/** @file */ -#pragma once - -#include - -namespace etl { - -/** @brief Error codes produced by ETL source operations. Aliased from rpc::EtlError. */ -using EtlError = rpc::EtlError; - -} // namespace etl diff --git a/src/etl/LoadBalancer.cpp b/src/etl/LoadBalancer.cpp index 3a743f992d..e2c42cf8f8 100644 --- a/src/etl/LoadBalancer.cpp +++ b/src/etl/LoadBalancer.cpp @@ -2,7 +2,6 @@ #include "data/BackendInterface.hpp" #include "etl/ETLState.hpp" -#include "etl/Errors.hpp" #include "etl/InitialLoadObserverInterface.hpp" #include "etl/LoadBalancerInterface.hpp" #include "etl/NetworkValidatedLedgersInterface.hpp" @@ -293,7 +292,7 @@ LoadBalancer::forwardToRippled( auto xUserValue = isAdmin ? kAdminForwardingXUserValue : kUserForwardingXUserValue; std::optional response; - etl::EtlError error = etl::EtlError::ConnectionError; + rpc::ClioError error = rpc::ClioError::RpcForwardingConnectionError; while (numAttempts < sources_.size()) { auto [res, duration] = util::timed([&]() { return sources_[sourceIdx]->forwardToRippled(request, clientIp, xUserValue, yield); diff --git a/src/etl/Source.hpp b/src/etl/Source.hpp index 4bb606c3b3..7118e31fc0 100644 --- a/src/etl/Source.hpp +++ b/src/etl/Source.hpp @@ -1,6 +1,5 @@ #pragma once -#include "etl/Errors.hpp" #include "etl/InitialLoadObserverInterface.hpp" #include "etl/LoadBalancerInterface.hpp" #include "etl/NetworkValidatedLedgersInterface.hpp" @@ -13,6 +12,7 @@ #include #include #include +#include #include #include @@ -131,7 +131,7 @@ class SourceBase { * @param yield The coroutine context * @return Response on success or error on failure */ - [[nodiscard]] virtual std::expected + [[nodiscard]] virtual std::expected forwardToRippled( boost::json::object const& request, std::optional const& forwardToRippledClientIp, diff --git a/src/etl/impl/ForwardingSource.cpp b/src/etl/impl/ForwardingSource.cpp index df997507e5..2b41154713 100644 --- a/src/etl/impl/ForwardingSource.cpp +++ b/src/etl/impl/ForwardingSource.cpp @@ -1,6 +1,5 @@ #include "etl/impl/ForwardingSource.hpp" -#include "etl/Errors.hpp" #include "util/log/Logger.hpp" #include @@ -10,6 +9,7 @@ #include #include #include +#include #include #include @@ -38,7 +38,7 @@ ForwardingSource::ForwardingSource( ); } -std::expected +std::expected ForwardingSource::forwardToRippled( boost::json::object const& request, std::optional const& forwardToRippledClientIp, @@ -58,14 +58,14 @@ ForwardingSource::forwardToRippled( auto expectedConnection = connectionBuilder.connect(yield); if (not expectedConnection) { LOG(log_.debug()) << "Couldn't connect to rippled to forward request."; - return std::unexpected{EtlError::ConnectionError}; + return std::unexpected{rpc::ClioError::RpcForwardingConnectionError}; } auto& connection = expectedConnection.value(); auto writeError = connection->write(boost::json::serialize(request), yield, forwardingTimeout_); if (writeError) { LOG(log_.debug()) << "Error sending request to rippled to forward request."; - return std::unexpected{EtlError::RequestError}; + return std::unexpected{rpc::ClioError::RpcForwardingRequestError}; } auto response = connection->read(yield, forwardingTimeout_); @@ -73,10 +73,10 @@ ForwardingSource::forwardToRippled( if (auto errorCode = response.error().errorCode(); errorCode.has_value() and errorCode->value() == boost::system::errc::timed_out) { LOG(log_.debug()) << "Request to rippled timed out"; - return std::unexpected{EtlError::RequestTimeout}; + return std::unexpected{rpc::ClioError::RpcForwardingTimeout}; } LOG(log_.debug()) << "Error sending request to rippled to forward request."; - return std::unexpected{EtlError::RequestError}; + return std::unexpected{rpc::ClioError::RpcForwardingRequestError}; } boost::json::value parsedResponse; @@ -87,7 +87,7 @@ ForwardingSource::forwardToRippled( } catch (std::exception const& e) { LOG(log_.debug()) << "Error parsing response from rippled: " << e.what() << ". Response: " << *response; - return std::unexpected{EtlError::InvalidResponse}; + return std::unexpected{rpc::ClioError::RpcForwardingInvalidResponse}; } auto responseObject = parsedResponse.as_object(); diff --git a/src/etl/impl/ForwardingSource.hpp b/src/etl/impl/ForwardingSource.hpp index 0f7114e686..d86e3a3127 100644 --- a/src/etl/impl/ForwardingSource.hpp +++ b/src/etl/impl/ForwardingSource.hpp @@ -1,11 +1,11 @@ #pragma once -#include "etl/Errors.hpp" #include "util/log/Logger.hpp" #include "util/requests/WsConnection.hpp" #include #include +#include #include #include @@ -39,7 +39,7 @@ class ForwardingSource { * @param yield The coroutine context * @return Response on success or error on failure */ - [[nodiscard]] std::expected + [[nodiscard]] std::expected forwardToRippled( boost::json::object const& request, std::optional const& forwardToRippledClientIp, diff --git a/src/etl/impl/SourceImpl.hpp b/src/etl/impl/SourceImpl.hpp index ccd6f54445..790c2fba63 100644 --- a/src/etl/impl/SourceImpl.hpp +++ b/src/etl/impl/SourceImpl.hpp @@ -1,6 +1,5 @@ #pragma once -#include "etl/Errors.hpp" #include "etl/InitialLoadObserverInterface.hpp" #include "etl/LoadBalancerInterface.hpp" #include "etl/Source.hpp" @@ -12,6 +11,7 @@ #include #include #include +#include #include #include @@ -208,9 +208,9 @@ class SourceImpl : public SourceBase { * @param forwardToRippledClientIp IP of the client forwarding this request if known * @param xUserValue Optional value of the X-User header * @param yield The coroutine context - * @return Response or EtlError + * @return Response or ClioError */ - [[nodiscard]] std::expected + [[nodiscard]] std::expected forwardToRippled( boost::json::object const& request, std::optional const& forwardToRippledClientIp, diff --git a/src/rpc/Errors.cpp b/src/rpc/Errors.cpp index ba1c12ccd1..c0c3b0ea51 100644 --- a/src/rpc/Errors.cpp +++ b/src/rpc/Errors.cpp @@ -28,7 +28,7 @@ namespace rpc { * @brief Stream a Status in human readable form. * * Declared in rpcspec but implemented here: rendering a code needs Clio's - * getErrorInfo/getEtlErrorInfo tables and xrpl::RPC::getErrorInfo. + * getErrorInfo table and xrpl::RPC::getErrorInfo. * * @param stream The stream to write to * @param status The status to write @@ -58,16 +58,6 @@ operator<<(std::ostream& stream, Status const& status) } else { stream << ", Message: " << getErrorInfo(err).message; } - }, - [&stream, &status](EtlError err) { - stream << "Code: " << static_cast>(err); - if (!status.error.empty()) - stream << ", Error: " << status.error; - if (!status.message.empty()) { - stream << ", Message: " << status.message; - } else { - stream << ", Message: " << getEtlErrorInfo(err).message; - } } }, status.code @@ -79,38 +69,6 @@ operator<<(std::ostream& stream, Status const& status) return stream; } -EtlErrorInfo const& -getEtlErrorInfo(EtlError code) -{ - static constexpr auto kInfos = std::to_array({ - { - .code = EtlError::ConnectionError, - .error = "connectionError", - .message = "Couldn't connect to rippled.", - }, - { - .code = EtlError::RequestError, - .error = "requestError", - .message = "Error sending request to rippled.", - }, - { - .code = EtlError::RequestTimeout, - .error = "timeout", - .message = "Request to rippled timed out.", - }, - { - .code = EtlError::InvalidResponse, - .error = "invalidResponse", - .message = "Rippled returned an invalid response.", - }, - }); - - if (auto it = ranges::find(kInfos, code, &EtlErrorInfo::code); it != end(kInfos)) - return *it; - - throw(out_of_range("Invalid EtlError code")); -} - ClioErrorInfo const& getErrorInfo(ClioError code) { @@ -181,6 +139,27 @@ getErrorInfo(ClioError code) .error = "paramsUnparsable", .message = "Params must be an array holding exactly one object.", }, + // errors from forwarding to an upstream rippled source + { + .code = ClioError::RpcForwardingConnectionError, + .error = "connectionError", + .message = "Couldn't connect to rippled.", + }, + { + .code = ClioError::RpcForwardingRequestError, + .error = "requestError", + .message = "Error sending request to rippled.", + }, + { + .code = ClioError::RpcForwardingTimeout, + .error = "timeout", + .message = "Request to rippled timed out.", + }, + { + .code = ClioError::RpcForwardingInvalidResponse, + .error = "invalidResponse", + .message = "Rippled returned an invalid response.", + }, }); if (auto it = ranges::find(kInfos, code, &ClioErrorInfo::code); it != end(kInfos)) @@ -248,16 +227,6 @@ makeError(Status const& status) [&status, &wrapOptional](ClioError err) { return makeError(err, wrapOptional(status.error), wrapOptional(status.message)); }, - [](EtlError err) { - auto const& info = getEtlErrorInfo(err); - return boost::json::object{ - {"error", info.error}, - {"error_code", static_cast(err)}, - {"error_message", info.message}, - {"status", "error"}, - {"type", "response"} - }; - }, }, status.code ); diff --git a/src/rpc/Errors.hpp b/src/rpc/Errors.hpp index 8790a3cc7b..c5bb2e3f99 100644 --- a/src/rpc/Errors.hpp +++ b/src/rpc/Errors.hpp @@ -18,15 +18,6 @@ struct ClioErrorInfo { std::string_view const message; }; -/** - * @brief Holds info about a particular rpc::EtlError. - */ -struct EtlErrorInfo { - EtlError const code; - std::string_view const error; - std::string_view const message; -}; - /** * @brief A globally available rpc::Status that represents a successful state. */ @@ -41,15 +32,6 @@ static Status gOk; ClioErrorInfo const& getErrorInfo(ClioError code); -/** - * @brief Get the ETL error info object from an ETL error code. - * - * @param code The error code - * @return A reference to the static error info - */ -EtlErrorInfo const& -getEtlErrorInfo(EtlError code); - /** * @brief Generate JSON from a rpc::Status. * diff --git a/src/web/impl/ErrorHandling.hpp b/src/web/impl/ErrorHandling.hpp index dee79d6db0..f14e6131c2 100644 --- a/src/web/impl/ErrorHandling.hpp +++ b/src/web/impl/ErrorHandling.hpp @@ -80,15 +80,15 @@ class ErrorHelper { case rpc::ClioError::RpcFieldNotFoundTransaction: case rpc::ClioError::RpcMalformedOracleDocumentId: case rpc::ClioError::RpcMalformedAuthorizedCredentials: + case rpc::ClioError::RpcForwardingConnectionError: + case rpc::ClioError::RpcForwardingRequestError: + case rpc::ClioError::RpcForwardingTimeout: + case rpc::ClioError::RpcForwardingInvalidResponse: ASSERT( false, "Unknown rpc error code {}", static_cast(*clioCode) ); // this should never happen break; } - } else if (std::get_if(&err.code) != nullptr) { - ASSERT( - false, "ETL error should not reach HTTP error handler" - ); // this should never happen } else { connection_->send( boost::json::serialize(composeError(err)), diff --git a/src/web/ng/impl/ErrorHandling.cpp b/src/web/ng/impl/ErrorHandling.cpp index 55a87a450c..a1b5bebe36 100644 --- a/src/web/ng/impl/ErrorHandling.cpp +++ b/src/web/ng/impl/ErrorHandling.cpp @@ -93,13 +93,15 @@ ErrorHelper::makeError(rpc::Status const& err) const case rpc::ClioError::RpcFieldNotFoundTransaction: case rpc::ClioError::RpcMalformedOracleDocumentId: case rpc::ClioError::RpcMalformedAuthorizedCredentials: + case rpc::ClioError::RpcForwardingConnectionError: + case rpc::ClioError::RpcForwardingRequestError: + case rpc::ClioError::RpcForwardingTimeout: + case rpc::ClioError::RpcForwardingInvalidResponse: ASSERT( false, "Unknown rpc error code {}", static_cast(*clioCode) ); // this should never happen break; } - } else if (std::get_if(&err.code) != nullptr) { - ASSERT(false, "ETL error should not reach HTTP error handler"); // this should never happen } return Response{http::status::bad_request, composeError(err), rawRequest_}; diff --git a/tests/common/util/MockSource.hpp b/tests/common/util/MockSource.hpp index c54897fd73..31db732dad 100644 --- a/tests/common/util/MockSource.hpp +++ b/tests/common/util/MockSource.hpp @@ -1,6 +1,5 @@ #pragma once -#include "etl/Errors.hpp" #include "etl/InitialLoadObserverInterface.hpp" #include "etl/LoadBalancerInterface.hpp" #include "etl/NetworkValidatedLedgersInterface.hpp" @@ -16,6 +15,7 @@ #include #include #include +#include #include #include @@ -50,7 +50,7 @@ struct MockSource : etl::SourceBase { (override) ); - using ForwardToRippledReturnType = std::expected; + using ForwardToRippledReturnType = std::expected; MOCK_METHOD( ForwardToRippledReturnType, forwardToRippled, @@ -132,7 +132,7 @@ class MockSourceWrapper : public etl::SourceBase { return mock_->loadInitialLedger(sequence, maxLedger, observer); } - [[nodiscard]] std::expected + [[nodiscard]] std::expected forwardToRippled( boost::json::object const& request, std::optional const& forwardToRippledClientIp, diff --git a/tests/unit/etl/ETLStateTests.cpp b/tests/unit/etl/ETLStateTests.cpp index e5641b17cb..09dbf259f9 100644 --- a/tests/unit/etl/ETLStateTests.cpp +++ b/tests/unit/etl/ETLStateTests.cpp @@ -1,10 +1,10 @@ #include "etl/ETLState.hpp" -#include "etl/Errors.hpp" #include "util/MockSource.hpp" #include #include #include +#include #include @@ -18,7 +18,7 @@ struct ETLStateTest : public virtual ::testing::Test { TEST_F(ETLStateTest, Error) { EXPECT_CALL(source, forwardToRippled) - .WillOnce(Return(std::unexpected{etl::EtlError::InvalidResponse})); + .WillOnce(Return(std::unexpected{rpc::ClioError::RpcForwardingInvalidResponse})); auto const state = etl::ETLState::fetchETLStateFromSource(source); EXPECT_FALSE(state); } diff --git a/tests/unit/etl/ForwardingSourceTests.cpp b/tests/unit/etl/ForwardingSourceTests.cpp index 4971b13c27..e7f9e08226 100644 --- a/tests/unit/etl/ForwardingSourceTests.cpp +++ b/tests/unit/etl/ForwardingSourceTests.cpp @@ -1,4 +1,3 @@ -#include "etl/Errors.hpp" #include "etl/impl/ForwardingSource.hpp" #include "util/AsioContextTestFixture.hpp" #include "util/Spawn.hpp" @@ -9,6 +8,7 @@ #include #include #include +#include #include #include @@ -35,7 +35,7 @@ TEST_F(ForwardingSourceTests, ConnectionFailed) runSpawn([&](boost::asio::yield_context yield) { auto result = forwardingSource_.forwardToRippled({}, {}, {}, yield); ASSERT_FALSE(result); - EXPECT_EQ(result.error(), etl::EtlError::ConnectionError); + EXPECT_EQ(result.error(), rpc::ClioError::RpcForwardingConnectionError); }); } @@ -79,7 +79,7 @@ TEST_F(ForwardingSourceOperationsTests, XUserHeader) boost::json::parse(message_).as_object(), {}, xUserValue, yield ); ASSERT_FALSE(result); - EXPECT_EQ(result.error(), etl::EtlError::RequestError); + EXPECT_EQ(result.error(), rpc::ClioError::RpcForwardingRequestError); }); } @@ -95,7 +95,7 @@ TEST_F(ForwardingSourceOperationsTests, ReadFailed) boost::json::parse(message_).as_object(), {}, {}, yield ); ASSERT_FALSE(result); - EXPECT_EQ(result.error(), etl::EtlError::RequestError); + EXPECT_EQ(result.error(), rpc::ClioError::RpcForwardingRequestError); }); } @@ -111,7 +111,7 @@ TEST_F(ForwardingSourceOperationsTests, ReadTimeout) boost::json::parse(message_).as_object(), {}, {}, yield ); ASSERT_FALSE(result); - EXPECT_EQ(result.error(), etl::EtlError::RequestTimeout); + EXPECT_EQ(result.error(), rpc::ClioError::RpcForwardingTimeout); }); } @@ -136,7 +136,7 @@ TEST_F(ForwardingSourceOperationsTests, ParseFailed) boost::json::parse(message_).as_object(), {}, {}, yield ); ASSERT_FALSE(result); - EXPECT_EQ(result.error(), etl::EtlError::InvalidResponse); + EXPECT_EQ(result.error(), rpc::ClioError::RpcForwardingInvalidResponse); }); } @@ -162,7 +162,7 @@ TEST_F(ForwardingSourceOperationsTests, GotNotAnObject) boost::json::parse(message_).as_object(), {}, {}, yield ); ASSERT_FALSE(result); - EXPECT_EQ(result.error(), etl::EtlError::InvalidResponse); + EXPECT_EQ(result.error(), rpc::ClioError::RpcForwardingInvalidResponse); }); } diff --git a/tests/unit/etl/LoadBalancerTests.cpp b/tests/unit/etl/LoadBalancerTests.cpp index 6e3fab0a9d..35da1f7131 100644 --- a/tests/unit/etl/LoadBalancerTests.cpp +++ b/tests/unit/etl/LoadBalancerTests.cpp @@ -1,4 +1,3 @@ -#include "etl/Errors.hpp" #include "etl/InitialLoadObserverInterface.hpp" #include "etl/LoadBalancer.hpp" #include "etl/LoadBalancerInterface.hpp" @@ -204,9 +203,9 @@ TEST_F(LoadBalancerConstructorTests, fetchETLState_AllSourcesFail) { EXPECT_CALL(sourceFactory_, makeSource).Times(2); EXPECT_CALL(sourceFactory_.sourceAt(0), forwardToRippled) - .WillOnce(Return(std::unexpected{etl::EtlError::ConnectionError})); + .WillOnce(Return(std::unexpected{rpc::ClioError::RpcForwardingConnectionError})); EXPECT_CALL(sourceFactory_.sourceAt(1), forwardToRippled) - .WillOnce(Return(std::unexpected{etl::EtlError::ConnectionError})); + .WillOnce(Return(std::unexpected{rpc::ClioError::RpcForwardingConnectionError})); EXPECT_THROW({ makeLoadBalancer(); }, std::logic_error); } @@ -226,7 +225,7 @@ TEST_F(LoadBalancerConstructorTests, fetchETLState_Source1Fails0OK) EXPECT_CALL(sourceFactory_.sourceAt(0), forwardToRippled) .WillOnce(Return(boost::json::object{})); EXPECT_CALL(sourceFactory_.sourceAt(1), forwardToRippled) - .WillOnce(Return(std::unexpected{etl::EtlError::ConnectionError})); + .WillOnce(Return(std::unexpected{rpc::ClioError::RpcForwardingConnectionError})); EXPECT_CALL(sourceFactory_.sourceAt(0), run); EXPECT_CALL(sourceFactory_.sourceAt(1), run); makeLoadBalancer(); @@ -236,7 +235,7 @@ TEST_F(LoadBalancerConstructorTests, fetchETLState_Source0Fails1OK) { EXPECT_CALL(sourceFactory_, makeSource).Times(2); EXPECT_CALL(sourceFactory_.sourceAt(0), forwardToRippled) - .WillOnce(Return(std::unexpected{etl::EtlError::ConnectionError})); + .WillOnce(Return(std::unexpected{rpc::ClioError::RpcForwardingConnectionError})); EXPECT_CALL(sourceFactory_.sourceAt(1), forwardToRippled) .WillOnce(Return(boost::json::object{})); EXPECT_CALL(sourceFactory_.sourceAt(0), run); @@ -266,7 +265,7 @@ TEST_F(LoadBalancerConstructorTests, fetchETLState_AllSourcesFailButAllowNoEtlIs .WillOnce(Return(boost::json::object{})); EXPECT_CALL(sourceFactory_.sourceAt(0), run); EXPECT_CALL(sourceFactory_.sourceAt(1), forwardToRippled) - .WillOnce(Return(std::unexpected{etl::EtlError::ConnectionError})); + .WillOnce(Return(std::unexpected{rpc::ClioError::RpcForwardingConnectionError})); EXPECT_CALL(sourceFactory_.sourceAt(1), run); configJson_.as_object()["allow_no_etl"] = true; @@ -717,7 +716,7 @@ TEST_F(LoadBalancerForwardToRippledTests, source0Fails) sourceFactory_.sourceAt(0), forwardToRippled(request_, clientIP_, LoadBalancer::kUserForwardingXUserValue, testing::_) ) - .WillOnce(Return(std::unexpected{etl::EtlError::ConnectionError})); + .WillOnce(Return(std::unexpected{rpc::ClioError::RpcForwardingConnectionError})); EXPECT_CALL( sourceFactory_.sourceAt(1), forwardToRippled(request_, clientIP_, LoadBalancer::kUserForwardingXUserValue, testing::_) @@ -820,7 +819,7 @@ TEST_F(LoadBalancerForwardToRippledPrometheusTests, source0Fails) sourceFactory_.sourceAt(0), forwardToRippled(request_, clientIP_, LoadBalancer::kUserForwardingXUserValue, testing::_) ) - .WillOnce(Return(std::unexpected{etl::EtlError::ConnectionError})); + .WillOnce(Return(std::unexpected{rpc::ClioError::RpcForwardingConnectionError})); EXPECT_CALL( sourceFactory_.sourceAt(1), forwardToRippled(request_, clientIP_, LoadBalancer::kUserForwardingXUserValue, testing::_) @@ -862,8 +861,8 @@ TEST_F(LoadBalancerForwardToRippledPrometheusTests, adminRequestAlwaysCacheMiss) struct LoadBalancerForwardToRippledErrorTestBundle { std::string testName; - etl::EtlError firstSourceError; - etl::EtlError secondSourceError; + rpc::ClioError firstSourceError; + rpc::ClioError secondSourceError; rpc::CombinedError responseExpectedError; }; @@ -877,33 +876,33 @@ INSTANTIATE_TEST_SUITE_P( testing::Values( LoadBalancerForwardToRippledErrorTestBundle{ "ConnectionError_RequestError", - etl::EtlError::ConnectionError, - etl::EtlError::RequestError, - etl::EtlError::RequestError + rpc::ClioError::RpcForwardingConnectionError, + rpc::ClioError::RpcForwardingRequestError, + rpc::ClioError::RpcForwardingRequestError }, LoadBalancerForwardToRippledErrorTestBundle{ "RequestError_RequestTimeout", - etl::EtlError::RequestError, - etl::EtlError::RequestTimeout, - etl::EtlError::RequestTimeout + rpc::ClioError::RpcForwardingRequestError, + rpc::ClioError::RpcForwardingTimeout, + rpc::ClioError::RpcForwardingTimeout }, LoadBalancerForwardToRippledErrorTestBundle{ "RequestTimeout_InvalidResponse", - etl::EtlError::RequestTimeout, - etl::EtlError::InvalidResponse, - etl::EtlError::InvalidResponse + rpc::ClioError::RpcForwardingTimeout, + rpc::ClioError::RpcForwardingInvalidResponse, + rpc::ClioError::RpcForwardingInvalidResponse }, LoadBalancerForwardToRippledErrorTestBundle{ "BothRequestTimeout", - etl::EtlError::RequestTimeout, - etl::EtlError::RequestTimeout, - etl::EtlError::RequestTimeout + rpc::ClioError::RpcForwardingTimeout, + rpc::ClioError::RpcForwardingTimeout, + rpc::ClioError::RpcForwardingTimeout }, LoadBalancerForwardToRippledErrorTestBundle{ "InvalidResponse_RequestError", - etl::EtlError::InvalidResponse, - etl::EtlError::RequestError, - etl::EtlError::InvalidResponse + rpc::ClioError::RpcForwardingInvalidResponse, + rpc::ClioError::RpcForwardingRequestError, + rpc::ClioError::RpcForwardingInvalidResponse } ), tests::util::kNameGenerator diff --git a/tests/unit/etl/SourceImplTests.cpp b/tests/unit/etl/SourceImplTests.cpp index cd97fd7f9b..758f941d90 100644 --- a/tests/unit/etl/SourceImplTests.cpp +++ b/tests/unit/etl/SourceImplTests.cpp @@ -1,4 +1,3 @@ -#include "etl/Errors.hpp" #include "etl/InitialLoadObserverInterface.hpp" #include "etl/LoadBalancerInterface.hpp" #include "etl/Models.hpp" @@ -13,6 +12,7 @@ #include #include #include +#include #include #include @@ -62,7 +62,7 @@ struct ForwardingSourceMock { (std::string const&, std::string const&, std::chrono::steady_clock::duration) ); - using ForwardToRippledReturnType = std::expected; + using ForwardToRippledReturnType = std::expected; using ClientIpOpt = std::optional; MOCK_METHOD( ForwardToRippledReturnType, diff --git a/tests/unit/rpc/ErrorTests.cpp b/tests/unit/rpc/ErrorTests.cpp index 31a4a2fc3f..5952069aa9 100644 --- a/tests/unit/rpc/ErrorTests.cpp +++ b/tests/unit/rpc/ErrorTests.cpp @@ -235,7 +235,7 @@ INSTANTIATE_TEST_SUITE_P( }, StatusStreamTestBundle{ .testName = "StatusWithCodeAndExtraInfo", - .status = Status{EtlError::ConnectionError, boost::json::object{}}, + .status = Status{ClioError::RpcForwardingConnectionError, boost::json::object{}}, .expectedOutput = "Code: 7000, Message: Couldn't connect to rippled., Extra Info: {}" }, StatusStreamTestBundle{ @@ -257,7 +257,7 @@ INSTANTIATE_TEST_SUITE_P( .testName = "StatusWithCodeErrorMessage", .status = Status{ - EtlError::InvalidResponse, + ClioError::RpcForwardingInvalidResponse, "invalidResponse", "Rippled returned an invalid response." }, diff --git a/tests/unit/rpc/ForwardingProxyTests.cpp b/tests/unit/rpc/ForwardingProxyTests.cpp index 4a7eaff63f..a8cac87ea9 100644 --- a/tests/unit/rpc/ForwardingProxyTests.cpp +++ b/tests/unit/rpc/ForwardingProxyTests.cpp @@ -338,7 +338,7 @@ TEST_F(RPCForwardingProxyTest, ForwardingFailYieldsErrorStatus) *rawBalancerPtr, forwardToRippled(forwarded.as_object(), std::make_optional(kClientIp), true, _) ) - .WillOnce(Return(std::unexpected{rpc::EtlError::InvalidResponse})); + .WillOnce(Return(std::unexpected{rpc::ClioError::RpcForwardingInvalidResponse})); EXPECT_CALL(*rawHandlerProviderPtr, contains(method)).WillOnce(Return(true)); @@ -360,6 +360,6 @@ TEST_F(RPCForwardingProxyTest, ForwardingFailYieldsErrorStatus) auto const res = proxy_.forward(ctx); EXPECT_FALSE(res.response.has_value()); - EXPECT_EQ(res.response.error(), rpc::EtlError::InvalidResponse); + EXPECT_EQ(res.response.error(), rpc::ClioError::RpcForwardingInvalidResponse); }); } diff --git a/tests/unit/rpc/handlers/ServerInfoTests.cpp b/tests/unit/rpc/handlers/ServerInfoTests.cpp index a1a2ddd288..84b7427e89 100644 --- a/tests/unit/rpc/handlers/ServerInfoTests.cpp +++ b/tests/unit/rpc/handlers/ServerInfoTests.cpp @@ -178,7 +178,7 @@ TEST_F(RPCServerInfoHandlerTest, DefaultOutputIsPresent) EXPECT_CALL( *rawBalancerPtr, forwardToRippled(testing::_, testing::Eq(kClientIp), false, testing::_) ) - .WillOnce(Return(std::unexpected{rpc::EtlError::InvalidResponse})); + .WillOnce(Return(std::unexpected{rpc::ClioError::RpcForwardingInvalidResponse})); EXPECT_CALL(*rawCountersPtr, uptime).WillOnce(Return(std::chrono::seconds{1234})); @@ -221,7 +221,7 @@ TEST_F(RPCServerInfoHandlerTest, AmendmentBlockedIsPresentIfSet) EXPECT_CALL( *rawBalancerPtr, forwardToRippled(testing::_, testing::Eq(kClientIp), false, testing::_) ) - .WillOnce(Return(std::unexpected{rpc::EtlError::InvalidResponse})); + .WillOnce(Return(std::unexpected{rpc::ClioError::RpcForwardingInvalidResponse})); EXPECT_CALL(*rawCountersPtr, uptime).WillOnce(Return(std::chrono::seconds{1234})); @@ -262,7 +262,7 @@ TEST_F(RPCServerInfoHandlerTest, CorruptionDetectedIsPresentIfSet) EXPECT_CALL( *rawBalancerPtr, forwardToRippled(testing::_, testing::Eq(kClientIp), false, testing::_) ) - .WillOnce(Return(std::unexpected{rpc::EtlError::InvalidResponse})); + .WillOnce(Return(std::unexpected{rpc::ClioError::RpcForwardingInvalidResponse})); EXPECT_CALL(*rawCountersPtr, uptime).WillOnce(Return(std::chrono::seconds{1234})); @@ -303,7 +303,7 @@ TEST_F(RPCServerInfoHandlerTest, CacheReportsEnabledFlagCorrectly) *rawBalancerPtr, forwardToRippled(testing::_, testing::Eq(kClientIp), false, testing::_) ) .Times(2) - .WillRepeatedly(Return(std::unexpected{rpc::EtlError::InvalidResponse})); + .WillRepeatedly(Return(std::unexpected{rpc::ClioError::RpcForwardingInvalidResponse})); EXPECT_CALL(*rawCountersPtr, uptime) .Times(2) From 7f4652751e5ae4eff215d1187b7f0a4c19ab877b Mon Sep 17 00:00:00 2001 From: Alex Kremer Date: Tue, 1 Sep 2026 14:43:30 +0100 Subject: [PATCH 12/24] Accept spec-library handlers alongside legacy ones --- src/rpc/RPCHelpers.cpp | 37 ++++++ src/rpc/RPCHelpers.hpp | 29 +++++ src/rpc/common/Concepts.hpp | 33 ++++- src/rpc/common/impl/Processors.hpp | 28 ++++- tests/common/rpc/FakesAndMocks.hpp | 48 ++++++++ tests/unit/rpc/RPCHelpersTests.cpp | 114 ++++++++++++++++++ .../rpc/handlers/DefaultProcessorTests.cpp | 70 +++++++++++ 7 files changed, 357 insertions(+), 2 deletions(-) diff --git a/src/rpc/RPCHelpers.cpp b/src/rpc/RPCHelpers.cpp index 2c95a64e11..e3ccc95280 100644 --- a/src/rpc/RPCHelpers.cpp +++ b/src/rpc/RPCHelpers.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -82,6 +83,7 @@ #include #include #include +#include #include namespace rpc { @@ -547,6 +549,41 @@ getLedgerHeaderFromHashOrSeq( return *lgrInfo; } +std::expected +getLedgerHeaderFromLedgerSpecifier( + BackendInterface const& backend, + boost::asio::yield_context yield, + spec::LedgerSpecifier const& ledger, + uint32_t maxSeq +) +{ + auto const err = std::unexpected{Status{RippledError::RpcLgrNotFound, "ledgerNotFound"}}; + auto const resolved = ledger.resolved(); + + if (resolved.isHash()) { + auto const lgrInfo = + backend.fetchLedgerByHash(std::get(resolved.value), yield); + if (!lgrInfo || lgrInfo->seq > maxSeq) + return err; + + return *lgrInfo; + } + + // A shortcut means the latest validated ledger; see the declaration for why that holds + // for all three of them. + auto const ledgerSequence = resolved.isSequence() ? std::get(resolved.value) : maxSeq; + + // return without hitting the db + if (ledgerSequence > maxSeq) + return err; + + auto const lgrInfo = backend.fetchLedgerBySequence(ledgerSequence, yield); + if (!lgrInfo) + return err; + + return *lgrInfo; +} + std::vector ledgerHeaderToBlob(xrpl::LedgerHeader const& info, bool includeHash) { diff --git a/src/rpc/RPCHelpers.hpp b/src/rpc/RPCHelpers.hpp index e649809b92..a9a77fa8fc 100644 --- a/src/rpc/RPCHelpers.hpp +++ b/src/rpc/RPCHelpers.hpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -299,6 +300,34 @@ getLedgerHeaderFromHashOrSeq( uint32_t maxSeq ); +/** + * @brief Get ledger header from a spec-library ledger specifier. + * + * The strong-typed counterpart of @ref getLedgerHeaderFromHashOrSeq, for handlers whose + * spec produces a @c LedgerSpecifier instead of a ledger_hash / ledger_index pair. + * Behaviour matches that overload: a hash or sequence beyond @p maxSeq, or one absent from + * the backend, yields @c ledgerNotFound. + * + * All three shortcuts resolve to @p maxSeq. Clio only serves validated data, so + * @c validated is the latest validated sequence by definition, and @c current / @c closed + * never arrive here — @ref specifiesCurrentOrClosedLedger forwards those upstream before + * dispatch. An unspecified ledger resolves via @c LedgerSpecifier::resolved(), which the + * spec library fixes to @c validated for Clio. + * + * @param backend The backend to use + * @param yield The coroutine context + * @param ledger The ledger the request selected + * @param maxSeq The maximum sequence to search + * @return The ledger header or an error status + */ +std::expected +getLedgerHeaderFromLedgerSpecifier( + BackendInterface const& backend, + boost::asio::yield_context yield, + spec::LedgerSpecifier const& ledger, + uint32_t maxSeq +); + /** * @brief Traverse nodes owned by an account * diff --git a/src/rpc/common/Concepts.hpp b/src/rpc/common/Concepts.hpp index c455b2ded6..330b905cea 100644 --- a/src/rpc/common/Concepts.hpp +++ b/src/rpc/common/Concepts.hpp @@ -7,8 +7,10 @@ #include #include #include +#include #include +#include #include #include @@ -71,17 +73,46 @@ concept SomeHandlerWithInput = requires(T a, uint32_t version) { { a.spec(version) } -> std::same_as; } and SomeContextProcessWithInput and boost::json::has_value_to::value; +/** + * @brief Specifies what a Handler validated by the shared consteval spec must provide. + * + * Such a handler inherits @c rpc::spec::HandlerFor from the spec library, which + * supplies a static @c parseInput (validate and deserialise in one pass) and a static + * @c spec returning a type-erased @ref rpc::spec::RpcSpecView. Presence of @c parseInput + * is what selects this path over @ref SomeHandlerWithInput. + * + * The two input paths are mutually exclusive by construction: a legacy handler returns + * @c RpcSpec @c const& from a non-static @c spec and needs a @c value_to for its Input, + * neither of which holds here. @ref kIsSingleInputPath asserts that below. + */ +template +concept SomeHandlerWithTypedInput = requires(uint32_t version, boost::json::value jv) { + typename T::Input; + { T::parseInput(jv, version) } -> std::same_as>; + { T::spec(version) } -> std::same_as; +} and SomeContextProcessWithInput; + /** * @brief Specifies what a Handler without Input must provide. */ template concept SomeHandlerWithoutInput = SomeContextProcessWithoutInput; +/** + * @brief True when @p T does not straddle the legacy and typed input paths. + * + * Guards the @c if @c constexpr chain in @ref rpc::impl::DefaultProcessor: were a handler + * to satisfy both, the dispatch order alone would silently decide which spec ran. + */ +template +constexpr bool kIsSingleInputPath = not(SomeHandlerWithInput and SomeHandlerWithTypedInput); + /** * @brief Specifies what a Handler type must provide. */ template -concept SomeHandler = (SomeHandlerWithInput or SomeHandlerWithoutInput) and +concept SomeHandler = + (SomeHandlerWithInput or SomeHandlerWithTypedInput or SomeHandlerWithoutInput) and boost::json::has_value_from::value; } // namespace rpc diff --git a/src/rpc/common/impl/Processors.hpp b/src/rpc/common/impl/Processors.hpp index 1242ded035..dc56157ec9 100644 --- a/src/rpc/common/impl/Processors.hpp +++ b/src/rpc/common/impl/Processors.hpp @@ -5,6 +5,9 @@ #include "util/UnsupportedType.hpp" #include +#include + +#include namespace rpc::impl { @@ -19,7 +22,30 @@ struct DefaultProcessor final { { using boost::json::value_from; using boost::json::value_to; - if constexpr (SomeHandlerWithInput) { + + static_assert( + kIsSingleInputPath, + "handler satisfies both the legacy and the typed input path; dispatch would be " + "decided by the order of the branches below rather than by the handler" + ); + + if constexpr (SomeHandlerWithTypedInput) { + // The shared consteval spec validates and deserializes in a single pass, so there + // is no separate process() step here: RpcSpecView::process() is a no-op for a + // TypedSpec. check() still runs separately because warnings are collected against + // the request as sent, and must be forwarded even when parsing then fails. + auto warnings = spec::toJsonArray(HandlerType::spec(ctx.apiVersion).check(value)); + + auto input = HandlerType::parseInput(value, ctx.apiVersion); + if (not input) + return ReturnType{Error{std::move(input).error()}, std::move(warnings)}; + + auto ret = handler.process(*input, ctx); + if (not ret) + return ReturnType{Error{std::move(ret).error()}, std::move(warnings)}; + + return ReturnType{value_from(std::move(ret).value()), std::move(warnings)}; + } else if constexpr (SomeHandlerWithInput) { // first we run validation against specified API version auto const spec = handler.spec(ctx.apiVersion); diff --git a/tests/common/rpc/FakesAndMocks.hpp b/tests/common/rpc/FakesAndMocks.hpp index e277b68d60..f44781feb2 100644 --- a/tests/common/rpc/FakesAndMocks.hpp +++ b/tests/common/rpc/FakesAndMocks.hpp @@ -10,6 +10,12 @@ #include #include #include +#include +#include +#include +#include +#include +#include #include #include @@ -153,4 +159,46 @@ struct HandlerWithoutInputMock { MOCK_METHOD(Result, process, (rpc::Context const&), (const)); }; +// The shared consteval spec resolves a handler's spec from its Input type via an ADL +// `specFor` hook, so the fake Input below needs its own namespace to host that hook. +namespace typed_fake { + +// input data for TypedHandlerFake; mirrors TestInput so the two paths stay comparable +struct TypedInput { + std::string hello; + std::optional limit; +}; + +inline constexpr auto kInputSpec = rpc::spec::spec( + rpc::spec::field("hello", &TypedInput::hello, rpc::spec::required, rpc::spec::asString), + rpc::spec::field("limit", &TypedInput::limit, rpc::spec::asUint32), + rpc::spec::field("old_field", rpc::spec::deprecated) +); + +inline constexpr auto kSpec = rpc::spec::versioned(kInputSpec); + +/** @brief ADL hook: resolve the versioned spec from the Input type. */ +[[nodiscard]] constexpr auto const& +specFor(TypedInput const*) noexcept +{ + return kSpec; +} + +} // namespace typed_fake + +// example handler validated by the shared consteval spec rather than by rpc::RpcSpec. +// Note it declares no spec() and no Input of its own: both come from HandlerFor, and there +// is no tag_invoke for TypedInput, which is what keeps it off the legacy path. +class TypedHandlerFake : public rpc::spec::HandlerFor { +public: + using Output = TestOutput; + using Result = rpc::HandlerReturnType; + + static Result + process(Input const& input, [[maybe_unused]] rpc::Context const& ctx) + { + return Output{input.hello + '_' + std::to_string(input.limit.value_or(0))}; + } +}; + } // namespace tests::common diff --git a/tests/unit/rpc/RPCHelpersTests.cpp b/tests/unit/rpc/RPCHelpersTests.cpp index 55fce5e97f..48ca1b7948 100644 --- a/tests/unit/rpc/RPCHelpersTests.cpp +++ b/tests/unit/rpc/RPCHelpersTests.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -2058,3 +2059,116 @@ INSTANTIATE_TEST_SUITE_P( ), tests::util::kNameGenerator ); + +// getLedgerHeaderFromLedgerSpecifier — the strong-typed counterpart of +// getLedgerHeaderFromHashOrSeq. The fixture's range is [10, 300], so kRangeMax below is 300. + +namespace { +constexpr auto kSpecifierRangeMax = 300u; +} // namespace + +TEST_F(RPCHelpersTest, LedgerHeaderFromSpecifierByHash) +{ + auto const expected = createLedgerHeader(kIndex1, 30); + EXPECT_CALL(*backend_, fetchLedgerByHash(xrpl::uint256{kIndex1}, _)).WillOnce(Return(expected)); + + runSpawn([&, this](auto yield) { + auto const res = getLedgerHeaderFromLedgerSpecifier( + *backend_, yield, spec::LedgerSpecifier{xrpl::uint256{kIndex1}}, kSpecifierRangeMax + ); + ASSERT_TRUE(res.has_value()); + EXPECT_EQ(res->seq, 30); + }); +} + +TEST_F(RPCHelpersTest, LedgerHeaderFromSpecifierByHashNotFound) +{ + EXPECT_CALL(*backend_, fetchLedgerByHash(xrpl::uint256{kIndex1}, _)) + .WillOnce(Return(std::nullopt)); + + runSpawn([&, this](auto yield) { + auto const res = getLedgerHeaderFromLedgerSpecifier( + *backend_, yield, spec::LedgerSpecifier{xrpl::uint256{kIndex1}}, kSpecifierRangeMax + ); + ASSERT_FALSE(res.has_value()); + EXPECT_EQ(res.error().message, "ledgerNotFound"); + }); +} + +TEST_F(RPCHelpersTest, LedgerHeaderFromSpecifierByHashBeyondMaxSeq) +{ + // present in the backend, but newer than the range the caller may serve + EXPECT_CALL(*backend_, fetchLedgerByHash(xrpl::uint256{kIndex1}, _)) + .WillOnce(Return(createLedgerHeader(kIndex1, kSpecifierRangeMax + 1))); + + runSpawn([&, this](auto yield) { + auto const res = getLedgerHeaderFromLedgerSpecifier( + *backend_, yield, spec::LedgerSpecifier{xrpl::uint256{kIndex1}}, kSpecifierRangeMax + ); + ASSERT_FALSE(res.has_value()); + EXPECT_EQ(res.error().message, "ledgerNotFound"); + }); +} + +TEST_F(RPCHelpersTest, LedgerHeaderFromSpecifierBySequence) +{ + EXPECT_CALL(*backend_, fetchLedgerBySequence(30, _)) + .WillOnce(Return(createLedgerHeader(kIndex1, 30))); + + runSpawn([&, this](auto yield) { + auto const res = getLedgerHeaderFromLedgerSpecifier( + *backend_, yield, spec::LedgerSpecifier{uint32_t{30}}, kSpecifierRangeMax + ); + ASSERT_TRUE(res.has_value()); + EXPECT_EQ(res->seq, 30); + }); +} + +TEST_F(RPCHelpersTest, LedgerHeaderFromSpecifierBySequenceBeyondMaxSeqSkipsBackend) +{ + EXPECT_CALL(*backend_, fetchLedgerBySequence).Times(0); + + runSpawn([&, this](auto yield) { + auto const res = getLedgerHeaderFromLedgerSpecifier( + *backend_, + yield, + spec::LedgerSpecifier{uint32_t{kSpecifierRangeMax + 1}}, + kSpecifierRangeMax + ); + ASSERT_FALSE(res.has_value()); + EXPECT_EQ(res.error().message, "ledgerNotFound"); + }); +} + +TEST_F(RPCHelpersTest, LedgerHeaderFromSpecifierShortcutUsesMaxSeq) +{ + EXPECT_CALL(*backend_, fetchLedgerBySequence(kSpecifierRangeMax, _)) + .WillOnce(Return(createLedgerHeader(kIndex1, kSpecifierRangeMax))); + + runSpawn([&, this](auto yield) { + auto const res = getLedgerHeaderFromLedgerSpecifier( + *backend_, + yield, + spec::LedgerSpecifier{spec::LedgerShortcut::Validated}, + kSpecifierRangeMax + ); + ASSERT_TRUE(res.has_value()); + EXPECT_EQ(res->seq, kSpecifierRangeMax); + }); +} + +TEST_F(RPCHelpersTest, LedgerHeaderFromSpecifierUnspecifiedResolvesToMaxSeq) +{ + // an unspecified ledger resolves via LedgerSpecifier::resolved(), which the spec library + // fixes to `validated` under RPCSPEC_IS_CLIO + EXPECT_CALL(*backend_, fetchLedgerBySequence(kSpecifierRangeMax, _)) + .WillOnce(Return(createLedgerHeader(kIndex1, kSpecifierRangeMax))); + + runSpawn([&, this](auto yield) { + auto const res = getLedgerHeaderFromLedgerSpecifier( + *backend_, yield, spec::LedgerSpecifier{}, kSpecifierRangeMax + ); + ASSERT_TRUE(res.has_value()); + EXPECT_EQ(res->seq, kSpecifierRangeMax); + }); +} diff --git a/tests/unit/rpc/handlers/DefaultProcessorTests.cpp b/tests/unit/rpc/handlers/DefaultProcessorTests.cpp index cb4066ec07..9b900c6d5e 100644 --- a/tests/unit/rpc/handlers/DefaultProcessorTests.cpp +++ b/tests/unit/rpc/handlers/DefaultProcessorTests.cpp @@ -67,3 +67,73 @@ TEST_F(RPCDefaultProcessorTest, InvalidInput) EXPECT_TRUE(ret.warnings.empty()); }); } + +// Pin which path each fake takes. Without this, a change that made a typed handler also +// satisfy SomeHandlerWithInput would silently reroute it through the legacy validators and +// every test below would still pass. +static_assert(SomeHandlerWithTypedInput); +static_assert(not SomeHandlerWithInput); +static_assert(SomeHandlerWithInput); +static_assert(not SomeHandlerWithTypedInput); + +// The four tests below exercise the typed path — a handler whose spec, validation and +// deserialization all come from the shared consteval spec via HandlerFor. They run +// against the same DefaultProcessor as the legacy tests above, which is the point: the +// dual path is a dispatch detail, not a second processor. + +TEST_F(RPCDefaultProcessorTest, NewSpecHandler_HappyPath) +{ + runSpawn([](auto yield) { + TypedHandlerFake const handler; + rpc::impl::DefaultProcessor const processor; + + auto const input = boost::json::parse(R"JSON({ "hello": "world", "limit": 42 })JSON"); + + auto const ret = processor(handler, input, Context{yield}); + ASSERT_TRUE(ret); + EXPECT_TRUE(ret.warnings.empty()); + EXPECT_EQ(ret.result.value().at("computed").as_string(), "world_42"); + }); +} + +TEST_F(RPCDefaultProcessorTest, NewSpecHandler_MissingRequiredField_ReturnsError) +{ + runSpawn([](auto yield) { + TypedHandlerFake const handler; + rpc::impl::DefaultProcessor const processor; + + auto const input = boost::json::parse(R"JSON({ "limit": 42 })JSON"); + + auto const ret = processor(handler, input, Context{yield}); + ASSERT_FALSE(ret); + EXPECT_TRUE(ret.warnings.empty()); + }); +} + +TEST_F(RPCDefaultProcessorTest, NewSpecHandler_DeprecatedField_WarningsForwarded) +{ + runSpawn([](auto yield) { + TypedHandlerFake const handler; + rpc::impl::DefaultProcessor const processor; + + auto const input = boost::json::parse(R"JSON({ "hello": "world", "old_field": true })JSON"); + + auto const ret = processor(handler, input, Context{yield}); + ASSERT_TRUE(ret); + EXPECT_EQ(ret.warnings.size(), 1); + }); +} + +TEST_F(RPCDefaultProcessorTest, NewSpecHandler_DeprecatedFieldAbsent_NoWarnings) +{ + runSpawn([](auto yield) { + TypedHandlerFake const handler; + rpc::impl::DefaultProcessor const processor; + + auto const input = boost::json::parse(R"JSON({ "hello": "world" })JSON"); + + auto const ret = processor(handler, input, Context{yield}); + ASSERT_TRUE(ret); + EXPECT_TRUE(ret.warnings.empty()); + }); +} From a2488820d5f82f699090487f4cfdb3108bd93d3e Mon Sep 17 00:00:00 2001 From: Alex Kremer Date: Tue, 1 Sep 2026 17:43:35 +0100 Subject: [PATCH 13/24] Assert on incorrect shortcut --- src/rpc/RPCHelpers.cpp | 10 +++++-- src/rpc/RPCHelpers.hpp | 12 +++++---- tests/unit/rpc/RPCHelpersTests.cpp | 43 +++++++++++++++++++++++++++++- 3 files changed, 57 insertions(+), 8 deletions(-) diff --git a/src/rpc/RPCHelpers.cpp b/src/rpc/RPCHelpers.cpp index e3ccc95280..80227143d9 100644 --- a/src/rpc/RPCHelpers.cpp +++ b/src/rpc/RPCHelpers.cpp @@ -569,8 +569,14 @@ getLedgerHeaderFromLedgerSpecifier( return *lgrInfo; } - // A shortcut means the latest validated ledger; see the declaration for why that holds - // for all three of them. + if (resolved.isShortcut()) { + auto const shortcut = std::get(resolved.value); + ASSERT( + shortcut == spec::LedgerShortcut::Validated, + "current/closed ledgers must be forwarded before dispatch" + ); + } + auto const ledgerSequence = resolved.isSequence() ? std::get(resolved.value) : maxSeq; // return without hitting the db diff --git a/src/rpc/RPCHelpers.hpp b/src/rpc/RPCHelpers.hpp index a9a77fa8fc..823e45b1f6 100644 --- a/src/rpc/RPCHelpers.hpp +++ b/src/rpc/RPCHelpers.hpp @@ -59,6 +59,7 @@ #include #include #include +#include #include #include #include @@ -308,11 +309,12 @@ getLedgerHeaderFromHashOrSeq( * Behaviour matches that overload: a hash or sequence beyond @p maxSeq, or one absent from * the backend, yields @c ledgerNotFound. * - * All three shortcuts resolve to @p maxSeq. Clio only serves validated data, so - * @c validated is the latest validated sequence by definition, and @c current / @c closed - * never arrive here — @ref specifiesCurrentOrClosedLedger forwards those upstream before - * dispatch. An unspecified ledger resolves via @c LedgerSpecifier::resolved(), which the - * spec library fixes to @c validated for Clio. + * A @c validated shortcut resolves to @p maxSeq, which is what it means for a server that + * only serves validated data. An unspecified ledger resolves via + * @c LedgerSpecifier::resolved(), which the spec library fixes to @c validated for Clio. + * + * @c current and @c closed cannot reach here: @ref specifiesCurrentOrClosedLedger forwards + * those upstream before dispatch. * * @param backend The backend to use * @param yield The coroutine context diff --git a/tests/unit/rpc/RPCHelpersTests.cpp b/tests/unit/rpc/RPCHelpersTests.cpp index 48ca1b7948..eb73aed083 100644 --- a/tests/unit/rpc/RPCHelpersTests.cpp +++ b/tests/unit/rpc/RPCHelpersTests.cpp @@ -7,6 +7,7 @@ #include "util/AsioContextTestFixture.hpp" #include "util/LoggerFixtures.hpp" #include "util/MockAmendmentCenter.hpp" +#include "util/MockAssert.hpp" #include "util/MockBackendTestFixture.hpp" #include "util/MockPrometheus.hpp" #include "util/NameGenerator.hpp" @@ -2140,7 +2141,7 @@ TEST_F(RPCHelpersTest, LedgerHeaderFromSpecifierBySequenceBeyondMaxSeqSkipsBacke }); } -TEST_F(RPCHelpersTest, LedgerHeaderFromSpecifierShortcutUsesMaxSeq) +TEST_F(RPCHelpersTest, LedgerHeaderFromSpecifierValidatedUsesMaxSeq) { EXPECT_CALL(*backend_, fetchLedgerBySequence(kSpecifierRangeMax, _)) .WillOnce(Return(createLedgerHeader(kIndex1, kSpecifierRangeMax))); @@ -2157,6 +2158,46 @@ TEST_F(RPCHelpersTest, LedgerHeaderFromSpecifierShortcutUsesMaxSeq) }); } +struct RPCHelpersAssertTest : RPCHelpersTest, common::util::WithMockAssert {}; + +TEST_F(RPCHelpersAssertTest, LedgerHeaderFromSpecifierCurrentAsserts) +{ + EXPECT_CALL(*backend_, fetchLedgerBySequence).Times(0); + + runSpawn([&, this](auto yield) { + EXPECT_CLIO_ASSERT_FAIL_WITH_MESSAGE( + { + [[maybe_unused]] auto const res = getLedgerHeaderFromLedgerSpecifier( + *backend_, + yield, + spec::LedgerSpecifier{spec::LedgerShortcut::Current}, + kSpecifierRangeMax + ); + }, + "must be forwarded before dispatch" + ); + }); +} + +TEST_F(RPCHelpersAssertTest, LedgerHeaderFromSpecifierClosedAsserts) +{ + EXPECT_CALL(*backend_, fetchLedgerBySequence).Times(0); + + runSpawn([&, this](auto yield) { + EXPECT_CLIO_ASSERT_FAIL_WITH_MESSAGE( + { + [[maybe_unused]] auto const res = getLedgerHeaderFromLedgerSpecifier( + *backend_, + yield, + spec::LedgerSpecifier{spec::LedgerShortcut::Closed}, + kSpecifierRangeMax + ); + }, + "must be forwarded before dispatch" + ); + }); +} + TEST_F(RPCHelpersTest, LedgerHeaderFromSpecifierUnspecifiedResolvesToMaxSeq) { // an unspecified ledger resolves via LedgerSpecifier::resolved(), which the spec library From a1e9e4943a436edbbe15ad9792a85db9b1abba89 Mon Sep 17 00:00:00 2001 From: Alex Kremer Date: Wed, 2 Sep 2026 01:18:43 +0100 Subject: [PATCH 14/24] Fix clang-tidy and doxy --- src/rpc/RPCHelpers.cpp | 1 - src/rpc/common/Concepts.hpp | 12 +++++++----- tests/common/CMakeLists.txt | 1 + tests/common/rpc/FakesAndMocks.cpp | 6 ++++++ tests/common/rpc/FakesAndMocks.hpp | 2 +- tests/unit/rpc/handlers/DefaultProcessorTests.cpp | 1 + 6 files changed, 16 insertions(+), 7 deletions(-) create mode 100644 tests/common/rpc/FakesAndMocks.cpp diff --git a/src/rpc/RPCHelpers.cpp b/src/rpc/RPCHelpers.cpp index 80227143d9..ab20a22ed6 100644 --- a/src/rpc/RPCHelpers.cpp +++ b/src/rpc/RPCHelpers.cpp @@ -83,7 +83,6 @@ #include #include #include -#include #include namespace rpc { diff --git a/src/rpc/common/Concepts.hpp b/src/rpc/common/Concepts.hpp index 330b905cea..bf289335c2 100644 --- a/src/rpc/common/Concepts.hpp +++ b/src/rpc/common/Concepts.hpp @@ -7,8 +7,10 @@ #include #include #include +#include #include +#include #include #include #include @@ -78,12 +80,12 @@ concept SomeHandlerWithInput = requires(T a, uint32_t version) { * * Such a handler inherits @c rpc::spec::HandlerFor from the spec library, which * supplies a static @c parseInput (validate and deserialise in one pass) and a static - * @c spec returning a type-erased @ref rpc::spec::RpcSpecView. Presence of @c parseInput - * is what selects this path over @ref SomeHandlerWithInput. + * @c spec returning a type-erased @c RpcSpecView. Presence of @c parseInput is what + * selects this path over @c SomeHandlerWithInput. * * The two input paths are mutually exclusive by construction: a legacy handler returns * @c RpcSpec @c const& from a non-static @c spec and needs a @c value_to for its Input, - * neither of which holds here. @ref kIsSingleInputPath asserts that below. + * neither of which holds here. @c kIsSingleInputPath asserts that below. */ template concept SomeHandlerWithTypedInput = requires(uint32_t version, boost::json::value jv) { @@ -101,8 +103,8 @@ concept SomeHandlerWithoutInput = SomeContextProcessWithoutInput; /** * @brief True when @p T does not straddle the legacy and typed input paths. * - * Guards the @c if @c constexpr chain in @ref rpc::impl::DefaultProcessor: were a handler - * to satisfy both, the dispatch order alone would silently decide which spec ran. + * Guards the @c if @c constexpr chain in @c DefaultProcessor - were a handler to satisfy + * both, the dispatch order alone would silently decide which spec ran. */ template constexpr bool kIsSingleInputPath = not(SomeHandlerWithInput and SomeHandlerWithTypedInput); diff --git a/tests/common/CMakeLists.txt b/tests/common/CMakeLists.txt index 51b41e6a6b..6c093112a8 100644 --- a/tests/common/CMakeLists.txt +++ b/tests/common/CMakeLists.txt @@ -3,6 +3,7 @@ add_library(clio_testing_common) target_sources( clio_testing_common PRIVATE + rpc/FakesAndMocks.cpp util/AssignRandomPort.cpp util/BinaryTestObject.cpp util/CallWithTimeout.cpp diff --git a/tests/common/rpc/FakesAndMocks.cpp b/tests/common/rpc/FakesAndMocks.cpp new file mode 100644 index 0000000000..62a8245274 --- /dev/null +++ b/tests/common/rpc/FakesAndMocks.cpp @@ -0,0 +1,6 @@ +#include "rpc/FakesAndMocks.hpp" + +#include +#include // IWYU pragma: keep + +template struct rpc::spec::HandlerFor; diff --git a/tests/common/rpc/FakesAndMocks.hpp b/tests/common/rpc/FakesAndMocks.hpp index f44781feb2..0c58fbeebf 100644 --- a/tests/common/rpc/FakesAndMocks.hpp +++ b/tests/common/rpc/FakesAndMocks.hpp @@ -12,8 +12,8 @@ #include #include #include +#include #include -#include #include #include diff --git a/tests/unit/rpc/handlers/DefaultProcessorTests.cpp b/tests/unit/rpc/handlers/DefaultProcessorTests.cpp index 9b900c6d5e..42d7138599 100644 --- a/tests/unit/rpc/handlers/DefaultProcessorTests.cpp +++ b/tests/unit/rpc/handlers/DefaultProcessorTests.cpp @@ -1,4 +1,5 @@ #include "rpc/FakesAndMocks.hpp" +#include "rpc/common/Concepts.hpp" #include "rpc/common/Specs.hpp" #include "rpc/common/Types.hpp" #include "rpc/common/Validators.hpp" From cad37682eb7f8de52116fb44d86f647c7150e530 Mon Sep 17 00:00:00 2001 From: Alex Kremer Date: Wed, 2 Sep 2026 12:08:55 +0100 Subject: [PATCH 15/24] Add coverage for fail path --- tests/common/rpc/FakesAndMocks.hpp | 13 ++++++++ .../rpc/handlers/DefaultProcessorTests.cpp | 32 +++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/tests/common/rpc/FakesAndMocks.hpp b/tests/common/rpc/FakesAndMocks.hpp index 0c58fbeebf..56d66fa025 100644 --- a/tests/common/rpc/FakesAndMocks.hpp +++ b/tests/common/rpc/FakesAndMocks.hpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -201,4 +202,16 @@ class TypedHandlerFake : public rpc::spec::HandlerFor { } }; +class FailingTypedHandlerFake : public rpc::spec::HandlerFor { +public: + using Output = TestOutput; + using Result = rpc::HandlerReturnType; + + static Result + process([[maybe_unused]] Input const& input, [[maybe_unused]] rpc::Context const& ctx) + { + return rpc::Error{rpc::Status{"Very custom error"}}; + } +}; + } // namespace tests::common diff --git a/tests/unit/rpc/handlers/DefaultProcessorTests.cpp b/tests/unit/rpc/handlers/DefaultProcessorTests.cpp index 42d7138599..22afa8cb6f 100644 --- a/tests/unit/rpc/handlers/DefaultProcessorTests.cpp +++ b/tests/unit/rpc/handlers/DefaultProcessorTests.cpp @@ -1,3 +1,4 @@ +#include "rpc/Errors.hpp" #include "rpc/FakesAndMocks.hpp" #include "rpc/common/Concepts.hpp" #include "rpc/common/Specs.hpp" @@ -74,6 +75,7 @@ TEST_F(RPCDefaultProcessorTest, InvalidInput) // every test below would still pass. static_assert(SomeHandlerWithTypedInput); static_assert(not SomeHandlerWithInput); +static_assert(SomeHandlerWithTypedInput); static_assert(SomeHandlerWithInput); static_assert(not SomeHandlerWithTypedInput); @@ -125,6 +127,36 @@ TEST_F(RPCDefaultProcessorTest, NewSpecHandler_DeprecatedField_WarningsForwarded }); } +TEST_F(RPCDefaultProcessorTest, NewSpecHandler_HandlerReturnsError_ForwardsError) +{ + runSpawn([](auto yield) { + FailingTypedHandlerFake const handler; + rpc::impl::DefaultProcessor const processor; + + auto const input = boost::json::parse(R"JSON({ "hello": "world", "limit": 42 })JSON"); + auto const ret = processor(handler, input, Context{yield}); + + ASSERT_FALSE(ret); + EXPECT_EQ(rpc::makeError(ret.result.error()).at("error").as_string(), "Very custom error"); + EXPECT_TRUE(ret.warnings.empty()); + }); +} + +TEST_F(RPCDefaultProcessorTest, NewSpecHandler_HandlerReturnsError_StillForwardsWarnings) +{ + runSpawn([](auto yield) { + FailingTypedHandlerFake const handler; + rpc::impl::DefaultProcessor const processor; + + auto const input = boost::json::parse(R"JSON({ "hello": "world", "old_field": true })JSON"); + auto const ret = processor(handler, input, Context{yield}); + + ASSERT_FALSE(ret); + EXPECT_EQ(rpc::makeError(ret.result.error()).at("error").as_string(), "Very custom error"); + EXPECT_EQ(ret.warnings.size(), 1); + }); +} + TEST_F(RPCDefaultProcessorTest, NewSpecHandler_DeprecatedFieldAbsent_NoWarnings) { runSpawn([](auto yield) { From 6c2ba1395f5c5315a4675189aaf881d8bfa71a35 Mon Sep 17 00:00:00 2001 From: Alex Kremer Date: Wed, 2 Sep 2026 12:16:36 +0100 Subject: [PATCH 16/24] Add coverage for write fail in forwarding --- tests/unit/etl/ForwardingSourceTests.cpp | 33 ++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tests/unit/etl/ForwardingSourceTests.cpp b/tests/unit/etl/ForwardingSourceTests.cpp index e7f9e08226..4cce0c14cd 100644 --- a/tests/unit/etl/ForwardingSourceTests.cpp +++ b/tests/unit/etl/ForwardingSourceTests.cpp @@ -12,6 +12,7 @@ #include #include +#include #include #include #include @@ -83,6 +84,38 @@ TEST_F(ForwardingSourceOperationsTests, XUserHeader) }); } +struct ForwardingSourceWriteFailedTests : SyncAsioContextTest { +protected: + static constexpr std::size_t kPayloadSize = 8 * 1024 * 1024; + + TestWsServer server_{ctx_, "0.0.0.0"}; + ForwardingSource forwardingSource_{ + "127.0.0.1", + server_.port(), + std::chrono::milliseconds{10}, + std::chrono::milliseconds{20} + }; +}; + +TEST_F(ForwardingSourceWriteFailedTests, WriteFailed) +{ + TestWsConnectionPtr connection; + util::spawn(ctx_, [&](boost::asio::yield_context yield) { + server_.acceptConnection(yield); + auto accepted = server_.acceptConnection(yield); + [&]() { ASSERT_TRUE(accepted) << accepted.error().message(); }(); + connection = std::make_unique(std::move(accepted).value()); + }); + + runSpawn([&](boost::asio::yield_context yield) { + boost::json::object const request{{"data", std::string(kPayloadSize, 'x')}}; + auto result = forwardingSource_.forwardToRippled(request, {}, {}, yield); + + ASSERT_FALSE(result); + EXPECT_EQ(result.error(), rpc::ClioError::RpcForwardingRequestError); + }); +} + TEST_F(ForwardingSourceOperationsTests, ReadFailed) { util::spawn(ctx_, [&](boost::asio::yield_context yield) { From f5fecc0e82b09e5904c5a34529148e1cf64a8d73 Mon Sep 17 00:00:00 2001 From: Alex Kremer Date: Wed, 2 Sep 2026 12:36:01 +0100 Subject: [PATCH 17/24] Remove test --- tests/unit/etl/ForwardingSourceTests.cpp | 33 ------------------------ 1 file changed, 33 deletions(-) diff --git a/tests/unit/etl/ForwardingSourceTests.cpp b/tests/unit/etl/ForwardingSourceTests.cpp index 4cce0c14cd..e7f9e08226 100644 --- a/tests/unit/etl/ForwardingSourceTests.cpp +++ b/tests/unit/etl/ForwardingSourceTests.cpp @@ -12,7 +12,6 @@ #include #include -#include #include #include #include @@ -84,38 +83,6 @@ TEST_F(ForwardingSourceOperationsTests, XUserHeader) }); } -struct ForwardingSourceWriteFailedTests : SyncAsioContextTest { -protected: - static constexpr std::size_t kPayloadSize = 8 * 1024 * 1024; - - TestWsServer server_{ctx_, "0.0.0.0"}; - ForwardingSource forwardingSource_{ - "127.0.0.1", - server_.port(), - std::chrono::milliseconds{10}, - std::chrono::milliseconds{20} - }; -}; - -TEST_F(ForwardingSourceWriteFailedTests, WriteFailed) -{ - TestWsConnectionPtr connection; - util::spawn(ctx_, [&](boost::asio::yield_context yield) { - server_.acceptConnection(yield); - auto accepted = server_.acceptConnection(yield); - [&]() { ASSERT_TRUE(accepted) << accepted.error().message(); }(); - connection = std::make_unique(std::move(accepted).value()); - }); - - runSpawn([&](boost::asio::yield_context yield) { - boost::json::object const request{{"data", std::string(kPayloadSize, 'x')}}; - auto result = forwardingSource_.forwardToRippled(request, {}, {}, yield); - - ASSERT_FALSE(result); - EXPECT_EQ(result.error(), rpc::ClioError::RpcForwardingRequestError); - }); -} - TEST_F(ForwardingSourceOperationsTests, ReadFailed) { util::spawn(ctx_, [&](boost::asio::yield_context yield) { From fbdfc345a51636769eb8016599a0bc5f9b48ab8c Mon Sep 17 00:00:00 2001 From: Alex Kremer Date: Thu, 3 Sep 2026 14:08:30 +0100 Subject: [PATCH 18/24] Migrate first two handlers to spec system --- conan.lock | 2 +- conanfile.py | 2 +- src/rpc/handlers/AccountCurrencies.cpp | 42 +++------- src/rpc/handlers/AccountCurrencies.hpp | 49 +---------- src/rpc/handlers/AccountInfo.cpp | 50 +++-------- src/rpc/handlers/AccountInfo.hpp | 58 +------------ .../rpc/handlers/AccountCurrenciesTests.cpp | 84 ++++++++++++++++++- tests/unit/rpc/handlers/AccountInfoTests.cpp | 9 +- tests/unit/rpc/handlers/AllHandlerTests.cpp | 3 +- 9 files changed, 120 insertions(+), 179 deletions(-) diff --git a/conan.lock b/conan.lock index a599de0815..4ffedb65d0 100644 --- a/conan.lock +++ b/conan.lock @@ -3,7 +3,7 @@ "requires": [ "zlib/1.3.2#1cb806da49011867778ffb6ac7190fcb%1782392402.122708", "xxhash/0.8.3#681d36a0a6111fc56e5e45ea182c19cc%1782392402.420688", - "xrpl-rpc-spec/0.1.7#774d2f93c4b48a1523d8d5a94a2b082a%1787768955.574105", + "xrpl-rpc-spec/0.1.8#173f7685d2e09121cbadabf507c0991a%1788439265.07151", "xrpl/3.3.0#5e356a24ae1f0d6da6bd617b926f92e6%1786467262.262007", "sqlite3/3.53.0#324ada52333108388a9a6108bfa96734%1782392403.185447", "spdlog/1.17.0#bcbaaf7147bda6ad24ffbd1ac3d7142c%1782736610.443882", diff --git a/conanfile.py b/conanfile.py index 056f092b27..45f0b502cb 100644 --- a/conanfile.py +++ b/conanfile.py @@ -17,7 +17,7 @@ class ClioConan(ConanFile): "fmt/12.1.0", "libbacktrace/cci.20210118", "spdlog/1.17.0", - "xrpl-rpc-spec/0.1.7", + "xrpl-rpc-spec/0.1.8", "xrpl/3.3.0", ] diff --git a/src/rpc/handlers/AccountCurrencies.cpp b/src/rpc/handlers/AccountCurrencies.cpp index a72ebc3e91..18023b1584 100644 --- a/src/rpc/handlers/AccountCurrencies.cpp +++ b/src/rpc/handlers/AccountCurrencies.cpp @@ -4,12 +4,14 @@ #include "rpc/RPCHelpers.hpp" #include "rpc/common/Types.hpp" #include "util/Assert.hpp" -#include "util/JsonUtils.hpp" #include #include -#include #include +#include +#include // IWYU pragma: keep +#include // IWYU pragma: keep +#include #include #include #include @@ -24,6 +26,8 @@ #include #include +template struct rpc::spec::HandlerFor; + namespace rpc { AccountCurrenciesHandler::Result AccountCurrenciesHandler::process( @@ -33,11 +37,10 @@ AccountCurrenciesHandler::process( { auto const range = sharedPtrBackend_->fetchLedgerRange(); ASSERT(range.has_value(), "AccountCurrencies' ledger range must be available"); - auto const expectedLgrInfo = getLedgerHeaderFromHashOrSeq( + auto const expectedLgrInfo = getLedgerHeaderFromLedgerSpecifier( *sharedPtrBackend_, ctx.yield, - input.ledgerHash, - input.ledgerIndex, + input.ledger, range->maxSequence // NOLINT(bugprone-unchecked-optional-access) ); @@ -45,13 +48,10 @@ AccountCurrenciesHandler::process( return Error{expectedLgrInfo.error()}; auto const& lgrInfo = *expectedLgrInfo; - auto const accountID = accountFromStringStrict(input.account); + auto const& accountID = input.account; auto const accountLedgerObject = sharedPtrBackend_->fetchLedgerObject( - // NOLINTNEXTLINE(bugprone-unchecked-optional-access) - xrpl::keylet::account(*accountID).key, - lgrInfo.seq, - ctx.yield + xrpl::keylet::account(accountID).key, lgrInfo.seq, ctx.yield ); if (!accountLedgerObject) return Error{Status{RippledError::RpcActNotFound}}; @@ -88,7 +88,7 @@ AccountCurrenciesHandler::process( // traverse all owned nodes, limit->max, marker->empty traverseOwnedNodes( *sharedPtrBackend_, - *accountID, // NOLINT(bugprone-unchecked-optional-access) + accountID, lgrInfo.seq, std::numeric_limits::max(), {}, @@ -120,24 +120,4 @@ tag_invoke( }; } -AccountCurrenciesHandler::Input -tag_invoke(boost::json::value_to_tag, boost::json::value const& jv) -{ - auto input = AccountCurrenciesHandler::Input{}; - auto const& jsonObject = jv.as_object(); - - input.account = boost::json::value_to(jv.at(JS(account))); - - if (jsonObject.contains(JS(ledger_hash))) - input.ledgerHash = boost::json::value_to(jv.at(JS(ledger_hash))); - - if (jsonObject.contains(JS(ledger_index))) { - auto const expectedLedgerIndex = util::getLedgerIndex(jv.at(JS(ledger_index))); - if (expectedLedgerIndex.has_value()) - input.ledgerIndex = *expectedLedgerIndex; - } - - return input; -} - } // namespace rpc diff --git a/src/rpc/handlers/AccountCurrencies.hpp b/src/rpc/handlers/AccountCurrencies.hpp index f8e4441f20..3f262cd769 100644 --- a/src/rpc/handlers/AccountCurrencies.hpp +++ b/src/rpc/handlers/AccountCurrencies.hpp @@ -1,19 +1,15 @@ #pragma once #include "data/BackendInterface.hpp" -#include "rpc/JS.hpp" -#include "rpc/common/Checkers.hpp" -#include "rpc/common/Specs.hpp" #include "rpc/common/Types.hpp" -#include "rpc/common/Validators.hpp" #include #include -#include +#include +#include #include #include -#include #include #include @@ -25,7 +21,8 @@ namespace rpc { * * For more details see: https://xrpl.org/account_currencies.html */ -class AccountCurrenciesHandler { +class AccountCurrenciesHandler + : public spec::HandlerFor { // dependencies std::shared_ptr sharedPtrBackend_; @@ -42,15 +39,6 @@ class AccountCurrenciesHandler { bool validated = true; }; - /** - * @brief A struct to hold the input data for the command - */ - struct Input { - std::string account; - std::optional ledgerHash; - std::optional ledgerIndex; - }; - using Result = HandlerReturnType; /** @@ -63,26 +51,6 @@ class AccountCurrenciesHandler { { } - /** - * @brief Returns the API specification for the command - * - * @param apiVersion The api version to return the spec for - * @return The spec for the given apiVersion - */ - static RpcSpecConstRef - spec([[maybe_unused]] uint32_t apiVersion) - { - static auto const kRpcSpec = RpcSpec{ - {JS(account), validation::Required{}, validation::CustomValidators::accountValidator}, - {JS(ledger_hash), validation::CustomValidators::uint256HexStringValidator}, - {JS(ledger_index), validation::CustomValidators::ledgerIndexValidator}, - {"account_index", check::Deprecated{}}, - {JS(strict), check::Deprecated{}} - }; - - return kRpcSpec; - } - /** * @brief Process the AccountCurrencies command * @@ -102,15 +70,6 @@ class AccountCurrenciesHandler { */ friend void tag_invoke(boost::json::value_from_tag, boost::json::value& jv, Output const& output); - - /** - * @brief Convert a JSON object to Input type - * - * @param jv The JSON object to convert - * @return Input parsed from the JSON object - */ - friend Input - tag_invoke(boost::json::value_to_tag, boost::json::value const& jv); }; } // namespace rpc diff --git a/src/rpc/handlers/AccountInfo.cpp b/src/rpc/handlers/AccountInfo.cpp index 870e223937..a04984eafa 100644 --- a/src/rpc/handlers/AccountInfo.cpp +++ b/src/rpc/handlers/AccountInfo.cpp @@ -3,17 +3,18 @@ #include "data/AmendmentCenter.hpp" #include "rpc/JS.hpp" #include "rpc/RPCHelpers.hpp" -#include "rpc/common/JsonBool.hpp" #include "rpc/common/Types.hpp" #include "util/Assert.hpp" -#include "util/JsonUtils.hpp" #include #include #include #include -#include #include +#include +#include // IWYU pragma: keep +#include // IWYU pragma: keep +#include #include #include #include @@ -32,6 +33,8 @@ #include #include +template struct rpc::spec::HandlerFor; + namespace rpc { AccountInfoHandler::Result AccountInfoHandler::process(AccountInfoHandler::Input const& input, Context const& ctx) const @@ -46,11 +49,10 @@ AccountInfoHandler::process(AccountInfoHandler::Input const& input, Context cons auto const range = sharedPtrBackend_->fetchLedgerRange(); ASSERT(range.has_value(), "AccountInfo's ledger range must be available"); - auto const expectedLgrInfo = getLedgerHeaderFromHashOrSeq( + auto const expectedLgrInfo = getLedgerHeaderFromLedgerSpecifier( *sharedPtrBackend_, ctx.yield, - input.ledgerHash, - input.ledgerIndex, + input.ledger, range->maxSequence // NOLINT(bugprone-unchecked-optional-access) ); @@ -58,10 +60,8 @@ AccountInfoHandler::process(AccountInfoHandler::Input const& input, Context cons return Error{expectedLgrInfo.error()}; auto const& lgrInfo = *expectedLgrInfo; - auto const accountStr = input.account.value_or(input.ident.value_or("")); - auto const accountID = accountFromStringStrict(accountStr); - auto const accountKeylet = - xrpl::keylet::account(*accountID); // NOLINT(bugprone-unchecked-optional-access) + auto const accountID = input.account ? *input.account : *input.ident; + auto const accountKeylet = xrpl::keylet::account(accountID); auto const accountLedgerObject = sharedPtrBackend_->fetchLedgerObject(accountKeylet.key, lgrInfo.seq, ctx.yield); @@ -99,8 +99,7 @@ AccountInfoHandler::process(AccountInfoHandler::Input const& input, Context cons if (input.signerLists) { // We put the SignerList in an array because of an anticipated // future when we support multiple signer lists on one account. - auto const signersKey = - xrpl::keylet::signerList(*accountID); // NOLINT(bugprone-unchecked-optional-access) + auto const signersKey = xrpl::keylet::signerList(accountID); // This code will need to be revisited if in the future we // support multiple SignerLists on one account. @@ -203,31 +202,4 @@ tag_invoke( } } -AccountInfoHandler::Input -tag_invoke(boost::json::value_to_tag, boost::json::value const& jv) -{ - auto input = AccountInfoHandler::Input{}; - auto const& jsonObject = jv.as_object(); - - if (jsonObject.contains(JS(ident))) - input.ident = boost::json::value_to(jsonObject.at(JS(ident))); - - if (jsonObject.contains(JS(account))) - input.account = boost::json::value_to(jsonObject.at(JS(account))); - - if (jsonObject.contains(JS(ledger_hash))) - input.ledgerHash = boost::json::value_to(jsonObject.at(JS(ledger_hash))); - - if (jsonObject.contains(JS(ledger_index))) { - auto const expectedLedgerIndex = util::getLedgerIndex(jsonObject.at(JS(ledger_index))); - if (expectedLedgerIndex.has_value()) - input.ledgerIndex = *expectedLedgerIndex; - } - - if (jsonObject.contains(JS(signer_lists))) - input.signerLists = boost::json::value_to(jsonObject.at(JS(signer_lists))); - - return input; -} - } // namespace rpc diff --git a/src/rpc/handlers/AccountInfo.hpp b/src/rpc/handlers/AccountInfo.hpp index 091859b0ea..3077070ecd 100644 --- a/src/rpc/handlers/AccountInfo.hpp +++ b/src/rpc/handlers/AccountInfo.hpp @@ -2,17 +2,13 @@ #include "data/AmendmentCenterInterface.hpp" #include "data/BackendInterface.hpp" -#include "rpc/JS.hpp" -#include "rpc/common/Checkers.hpp" -#include "rpc/common/JsonBool.hpp" -#include "rpc/common/Specs.hpp" #include "rpc/common/Types.hpp" -#include "rpc/common/Validators.hpp" #include #include +#include +#include #include -#include #include #include @@ -28,7 +24,7 @@ namespace rpc { * * For more details see: https://xrpl.org/account_info.html */ -class AccountInfoHandler { +class AccountInfoHandler : public spec::HandlerFor { std::shared_ptr sharedPtrBackend_; std::shared_ptr amendmentCenter_; @@ -49,20 +45,6 @@ class AccountInfoHandler { bool validated = true; }; - /** - * @brief A struct to hold the input data for the command - * - * `queue` is not available in Reporting mode - * `ident` is deprecated, keep it for now, in line with rippled - */ - struct Input { - std::optional account; - std::optional ident; - std::optional ledgerHash; - std::optional ledgerIndex; - JsonBool signerLists{false}; - }; - using Result = HandlerReturnType; /** @@ -79,31 +61,6 @@ class AccountInfoHandler { { } - /** - * @brief Returns the API specification for the command - * - * @param apiVersion The api version to return the spec for - * @return The spec for the given apiVersion - */ - static RpcSpecConstRef - spec([[maybe_unused]] uint32_t apiVersion) - { - static auto const kRpcSpecV1 = RpcSpec{ - {JS(account), validation::CustomValidators::accountValidator}, - {JS(ident), validation::CustomValidators::accountValidator}, - {JS(ident), check::Deprecated{}}, - {JS(ledger_hash), validation::CustomValidators::uint256HexStringValidator}, - {JS(ledger_index), validation::CustomValidators::ledgerIndexValidator}, - {JS(ledger), check::Deprecated{}}, - {JS(strict), check::Deprecated{}} - }; - - static auto const kRpcSpec = - RpcSpec{kRpcSpecV1, {{JS(signer_lists), validation::Type{}}}}; - - return apiVersion == 1 ? kRpcSpecV1 : kRpcSpec; - } - /** * @brief Process the AccountInfo command * @@ -123,15 +80,6 @@ class AccountInfoHandler { */ friend void tag_invoke(boost::json::value_from_tag, boost::json::value& jv, Output const& output); - - /** - * @brief Convert a JSON object to Input type - * - * @param jv The JSON object to convert - * @return Input parsed from the JSON object - */ - friend Input - tag_invoke(boost::json::value_to_tag, boost::json::value const& jv); }; } // namespace rpc diff --git a/tests/unit/rpc/handlers/AccountCurrenciesTests.cpp b/tests/unit/rpc/handlers/AccountCurrenciesTests.cpp index 909a752794..a7d7f9317d 100644 --- a/tests/unit/rpc/handlers/AccountCurrenciesTests.cpp +++ b/tests/unit/rpc/handlers/AccountCurrenciesTests.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -152,6 +153,87 @@ TEST_F(RPCAccountCurrenciesHandlerTest, LedgerNonExistViaHash) }); } +TEST_F(RPCAccountCurrenciesHandlerTest, LedgerHashMalformed) +{ + static auto const kInput = boost::json::parse( + fmt::format(R"JSON({{ "account": "{}", "ledger_hash": "1" }})JSON", kAccount) + ); + auto const handler = AnyHandler{AccountCurrenciesHandler{backend_}}; + runSpawn([&](auto yield) { + auto const output = handler.process(kInput, Context{yield}); + ASSERT_FALSE(output); + auto const err = rpc::makeError(output.result.error()); + EXPECT_EQ(err.at("error").as_string(), "invalidParams"); + EXPECT_EQ( + err.at("error_message").as_string(), "Invalid field 'ledger_hash', not hex string." + ); + }); +} + +TEST_F(RPCAccountCurrenciesHandlerTest, LedgerIndexMalformed) +{ + static auto const kInput = boost::json::parse( + fmt::format(R"JSON({{ "account": "{}", "ledger_index": "a" }})JSON", kAccount) + ); + auto const handler = AnyHandler{AccountCurrenciesHandler{backend_}}; + runSpawn([&](auto yield) { + auto const output = handler.process(kInput, Context{yield}); + ASSERT_FALSE(output); + auto const err = rpc::makeError(output.result.error()); + EXPECT_EQ(err.at("error").as_string(), "invalidParams"); + EXPECT_EQ( + err.at("error_message").as_string(), + "Invalid field 'ledger_index', not string or number." + ); + }); +} + +TEST_F(RPCAccountCurrenciesHandlerTest, LedgerIndexEmptyStringMalformed) +{ + static auto const kInput = boost::json::parse( + fmt::format(R"JSON({{ "account": "{}", "ledger_index": "" }})JSON", kAccount) + ); + auto const handler = AnyHandler{AccountCurrenciesHandler{backend_}}; + runSpawn([&](auto yield) { + auto const output = handler.process(kInput, Context{yield}); + ASSERT_FALSE(output); + EXPECT_EQ(rpc::makeError(output.result.error()).at("error").as_string(), "invalidParams"); + }); +} + +TEST_F(RPCAccountCurrenciesHandlerTest, LedgerIndexOutOfRangeMalformed) +{ + static auto const kInput = boost::json::parse( + fmt::format(R"JSON({{ "account": "{}", "ledger_index": 4294967296 }})JSON", kAccount) + ); + auto const handler = AnyHandler{AccountCurrenciesHandler{backend_}}; + runSpawn([&](auto yield) { + auto const output = handler.process(kInput, Context{yield}); + ASSERT_FALSE(output); + EXPECT_EQ(rpc::makeError(output.result.error()).at("error").as_string(), "invalidParams"); + }); +} + +TEST_F(RPCAccountCurrenciesHandlerTest, ValidLedgerHashWithMalformedLedgerIndex) +{ + static auto const kInput = boost::json::parse( + fmt::format( + R"JSON({{ "account": "{}", "ledger_hash": "{}", "ledger_index": "a" }})JSON", + kAccount, + kLedgerHash + ) + ); + auto const handler = AnyHandler{AccountCurrenciesHandler{backend_}}; + runSpawn([&](auto yield) { + auto const output = handler.process(kInput, Context{yield}); + ASSERT_FALSE(output); + EXPECT_EQ( + rpc::makeError(output.result.error()).at("error_message").as_string(), + "Invalid field 'ledger_index', not string or number." + ); + }); +} + TEST_F(RPCAccountCurrenciesHandlerTest, DefaultParameter) { static constexpr auto kOutput = R"JSON({ @@ -318,7 +400,7 @@ TEST(RPCAccountCurrenciesHandlerSpecTest, DeprecatedFields) {"strict", true} }; auto const spec = AccountCurrenciesHandler::spec(2); - auto const warnings = spec.check(json); + auto const warnings = rpc::spec::toJsonArray(spec.check(json)); ASSERT_EQ(warnings.size(), 1); ASSERT_TRUE(warnings[0].is_object()); auto const& warning = warnings[0].as_object(); diff --git a/tests/unit/rpc/handlers/AccountInfoTests.cpp b/tests/unit/rpc/handlers/AccountInfoTests.cpp index e28bceb968..46a237b2e5 100644 --- a/tests/unit/rpc/handlers/AccountInfoTests.cpp +++ b/tests/unit/rpc/handlers/AccountInfoTests.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -106,21 +107,21 @@ generateTestValuesForParametersTest() .testJson = R"JSON({"ident": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", "ledger_hash": "1"})JSON", .expectedError = "invalidParams", - .expectedErrorMessage = "ledger_hashMalformed" + .expectedErrorMessage = "Invalid field 'ledger_hash', not hex string." }, AccountInfoParamTestCaseBundle{ .testName = "LedgerHashNotString", .testJson = R"JSON({"ident": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", "ledger_hash": 1})JSON", .expectedError = "invalidParams", - .expectedErrorMessage = "ledger_hashNotString" + .expectedErrorMessage = "Invalid field 'ledger_hash', not hex string." }, AccountInfoParamTestCaseBundle{ .testName = "LedgerIndexInvalid", .testJson = R"JSON({"ident": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", "ledger_index": "a"})JSON", .expectedError = "invalidParams", - .expectedErrorMessage = "ledgerIndexMalformed" + .expectedErrorMessage = "Invalid field 'ledger_index', not string or number." }, }; } @@ -952,7 +953,7 @@ TEST(RPCAccountInfoHandlerSpecTest, DeprecatedFields) {"strict", true} }; auto const spec = AccountInfoHandler::spec(2); - auto const warnings = spec.check(json); + auto const warnings = rpc::spec::toJsonArray(spec.check(json)); ASSERT_EQ(warnings.size(), 1); auto const& warning = warnings[0]; ASSERT_TRUE(warning.is_object()); diff --git a/tests/unit/rpc/handlers/AllHandlerTests.cpp b/tests/unit/rpc/handlers/AllHandlerTests.cpp index 8f739c5961..7816bde59e 100644 --- a/tests/unit/rpc/handlers/AllHandlerTests.cpp +++ b/tests/unit/rpc/handlers/AllHandlerTests.cpp @@ -168,8 +168,7 @@ AccountInfoHandler::Input createInput() { AccountInfoHandler::Input input{}; - input.account = kAccount; - input.ident = "asdf"; + input.account = getAccountIdWithString(kAccount); return input; } From 55464cbf85f22d8f38b9d79d743cb9243bccc39d Mon Sep 17 00:00:00 2001 From: Alex Kremer Date: Tue, 8 Sep 2026 17:35:31 +0100 Subject: [PATCH 19/24] Use explicit rpc::spec --- src/rpc/RPCHelpers.cpp | 4 ++-- src/rpc/RPCHelpers.hpp | 2 +- src/rpc/common/Concepts.hpp | 2 +- src/rpc/common/impl/Processors.hpp | 2 +- tests/unit/rpc/RPCHelpersTests.cpp | 18 +++++++++--------- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/rpc/RPCHelpers.cpp b/src/rpc/RPCHelpers.cpp index ab20a22ed6..cd122a7de6 100644 --- a/src/rpc/RPCHelpers.cpp +++ b/src/rpc/RPCHelpers.cpp @@ -569,9 +569,9 @@ getLedgerHeaderFromLedgerSpecifier( } if (resolved.isShortcut()) { - auto const shortcut = std::get(resolved.value); + auto const shortcut = std::get(resolved.value); ASSERT( - shortcut == spec::LedgerShortcut::Validated, + shortcut == rpc::spec::LedgerShortcut::Validated, "current/closed ledgers must be forwarded before dispatch" ); } diff --git a/src/rpc/RPCHelpers.hpp b/src/rpc/RPCHelpers.hpp index 823e45b1f6..0e10dfbf0a 100644 --- a/src/rpc/RPCHelpers.hpp +++ b/src/rpc/RPCHelpers.hpp @@ -326,7 +326,7 @@ std::expected getLedgerHeaderFromLedgerSpecifier( BackendInterface const& backend, boost::asio::yield_context yield, - spec::LedgerSpecifier const& ledger, + rpc::spec::LedgerSpecifier const& ledger, uint32_t maxSeq ); diff --git a/src/rpc/common/Concepts.hpp b/src/rpc/common/Concepts.hpp index bf289335c2..df7be4d528 100644 --- a/src/rpc/common/Concepts.hpp +++ b/src/rpc/common/Concepts.hpp @@ -91,7 +91,7 @@ template concept SomeHandlerWithTypedInput = requires(uint32_t version, boost::json::value jv) { typename T::Input; { T::parseInput(jv, version) } -> std::same_as>; - { T::spec(version) } -> std::same_as; + { T::spec(version) } -> std::same_as; } and SomeContextProcessWithInput; /** diff --git a/src/rpc/common/impl/Processors.hpp b/src/rpc/common/impl/Processors.hpp index dc56157ec9..21cff12083 100644 --- a/src/rpc/common/impl/Processors.hpp +++ b/src/rpc/common/impl/Processors.hpp @@ -34,7 +34,7 @@ struct DefaultProcessor final { // is no separate process() step here: RpcSpecView::process() is a no-op for a // TypedSpec. check() still runs separately because warnings are collected against // the request as sent, and must be forwarded even when parsing then fails. - auto warnings = spec::toJsonArray(HandlerType::spec(ctx.apiVersion).check(value)); + auto warnings = rpc::spec::toJsonArray(HandlerType::spec(ctx.apiVersion).check(value)); auto input = HandlerType::parseInput(value, ctx.apiVersion); if (not input) diff --git a/tests/unit/rpc/RPCHelpersTests.cpp b/tests/unit/rpc/RPCHelpersTests.cpp index eb73aed083..b7c6055985 100644 --- a/tests/unit/rpc/RPCHelpersTests.cpp +++ b/tests/unit/rpc/RPCHelpersTests.cpp @@ -2075,7 +2075,7 @@ TEST_F(RPCHelpersTest, LedgerHeaderFromSpecifierByHash) runSpawn([&, this](auto yield) { auto const res = getLedgerHeaderFromLedgerSpecifier( - *backend_, yield, spec::LedgerSpecifier{xrpl::uint256{kIndex1}}, kSpecifierRangeMax + *backend_, yield, rpc::spec::LedgerSpecifier{xrpl::uint256{kIndex1}}, kSpecifierRangeMax ); ASSERT_TRUE(res.has_value()); EXPECT_EQ(res->seq, 30); @@ -2089,7 +2089,7 @@ TEST_F(RPCHelpersTest, LedgerHeaderFromSpecifierByHashNotFound) runSpawn([&, this](auto yield) { auto const res = getLedgerHeaderFromLedgerSpecifier( - *backend_, yield, spec::LedgerSpecifier{xrpl::uint256{kIndex1}}, kSpecifierRangeMax + *backend_, yield, rpc::spec::LedgerSpecifier{xrpl::uint256{kIndex1}}, kSpecifierRangeMax ); ASSERT_FALSE(res.has_value()); EXPECT_EQ(res.error().message, "ledgerNotFound"); @@ -2104,7 +2104,7 @@ TEST_F(RPCHelpersTest, LedgerHeaderFromSpecifierByHashBeyondMaxSeq) runSpawn([&, this](auto yield) { auto const res = getLedgerHeaderFromLedgerSpecifier( - *backend_, yield, spec::LedgerSpecifier{xrpl::uint256{kIndex1}}, kSpecifierRangeMax + *backend_, yield, rpc::spec::LedgerSpecifier{xrpl::uint256{kIndex1}}, kSpecifierRangeMax ); ASSERT_FALSE(res.has_value()); EXPECT_EQ(res.error().message, "ledgerNotFound"); @@ -2118,7 +2118,7 @@ TEST_F(RPCHelpersTest, LedgerHeaderFromSpecifierBySequence) runSpawn([&, this](auto yield) { auto const res = getLedgerHeaderFromLedgerSpecifier( - *backend_, yield, spec::LedgerSpecifier{uint32_t{30}}, kSpecifierRangeMax + *backend_, yield, rpc::spec::LedgerSpecifier{uint32_t{30}}, kSpecifierRangeMax ); ASSERT_TRUE(res.has_value()); EXPECT_EQ(res->seq, 30); @@ -2133,7 +2133,7 @@ TEST_F(RPCHelpersTest, LedgerHeaderFromSpecifierBySequenceBeyondMaxSeqSkipsBacke auto const res = getLedgerHeaderFromLedgerSpecifier( *backend_, yield, - spec::LedgerSpecifier{uint32_t{kSpecifierRangeMax + 1}}, + rpc::spec::LedgerSpecifier{uint32_t{kSpecifierRangeMax + 1}}, kSpecifierRangeMax ); ASSERT_FALSE(res.has_value()); @@ -2150,7 +2150,7 @@ TEST_F(RPCHelpersTest, LedgerHeaderFromSpecifierValidatedUsesMaxSeq) auto const res = getLedgerHeaderFromLedgerSpecifier( *backend_, yield, - spec::LedgerSpecifier{spec::LedgerShortcut::Validated}, + rpc::spec::LedgerSpecifier{rpc::spec::LedgerShortcut::Validated}, kSpecifierRangeMax ); ASSERT_TRUE(res.has_value()); @@ -2170,7 +2170,7 @@ TEST_F(RPCHelpersAssertTest, LedgerHeaderFromSpecifierCurrentAsserts) [[maybe_unused]] auto const res = getLedgerHeaderFromLedgerSpecifier( *backend_, yield, - spec::LedgerSpecifier{spec::LedgerShortcut::Current}, + rpc::spec::LedgerSpecifier{rpc::spec::LedgerShortcut::Current}, kSpecifierRangeMax ); }, @@ -2189,7 +2189,7 @@ TEST_F(RPCHelpersAssertTest, LedgerHeaderFromSpecifierClosedAsserts) [[maybe_unused]] auto const res = getLedgerHeaderFromLedgerSpecifier( *backend_, yield, - spec::LedgerSpecifier{spec::LedgerShortcut::Closed}, + rpc::spec::LedgerSpecifier{rpc::spec::LedgerShortcut::Closed}, kSpecifierRangeMax ); }, @@ -2207,7 +2207,7 @@ TEST_F(RPCHelpersTest, LedgerHeaderFromSpecifierUnspecifiedResolvesToMaxSeq) runSpawn([&, this](auto yield) { auto const res = getLedgerHeaderFromLedgerSpecifier( - *backend_, yield, spec::LedgerSpecifier{}, kSpecifierRangeMax + *backend_, yield, rpc::spec::LedgerSpecifier{}, kSpecifierRangeMax ); ASSERT_TRUE(res.has_value()); EXPECT_EQ(res->seq, kSpecifierRangeMax); From af24033a273085516a3f966e0001c91b2c009747 Mon Sep 17 00:00:00 2001 From: Alex Kremer Date: Tue, 8 Sep 2026 17:42:00 +0100 Subject: [PATCH 20/24] Remove some useless comments --- src/rpc/common/impl/Processors.hpp | 8 +++----- tests/common/rpc/FakesAndMocks.hpp | 4 ---- tests/unit/rpc/RPCHelpersTests.cpp | 3 --- tests/unit/rpc/handlers/DefaultProcessorTests.cpp | 3 --- 4 files changed, 3 insertions(+), 15 deletions(-) diff --git a/src/rpc/common/impl/Processors.hpp b/src/rpc/common/impl/Processors.hpp index 21cff12083..2e4ee28bee 100644 --- a/src/rpc/common/impl/Processors.hpp +++ b/src/rpc/common/impl/Processors.hpp @@ -29,11 +29,8 @@ struct DefaultProcessor final { "decided by the order of the branches below rather than by the handler" ); + // New `rpc-spec`-based handler if constexpr (SomeHandlerWithTypedInput) { - // The shared consteval spec validates and deserializes in a single pass, so there - // is no separate process() step here: RpcSpecView::process() is a no-op for a - // TypedSpec. check() still runs separately because warnings are collected against - // the request as sent, and must be forwarded even when parsing then fails. auto warnings = rpc::spec::toJsonArray(HandlerType::spec(ctx.apiVersion).check(value)); auto input = HandlerType::parseInput(value, ctx.apiVersion); @@ -46,7 +43,8 @@ struct DefaultProcessor final { return ReturnType{value_from(std::move(ret).value()), std::move(warnings)}; } else if constexpr (SomeHandlerWithInput) { - // first we run validation against specified API version + // Old spec-based handler: first we run validation against specified API version + // TODO: This will be eventually removed once fully migraded to new rpc-spec system. auto const spec = handler.spec(ctx.apiVersion); auto warnings = spec.check(value); diff --git a/tests/common/rpc/FakesAndMocks.hpp b/tests/common/rpc/FakesAndMocks.hpp index 56d66fa025..62a0765b22 100644 --- a/tests/common/rpc/FakesAndMocks.hpp +++ b/tests/common/rpc/FakesAndMocks.hpp @@ -178,7 +178,6 @@ inline constexpr auto kInputSpec = rpc::spec::spec( inline constexpr auto kSpec = rpc::spec::versioned(kInputSpec); -/** @brief ADL hook: resolve the versioned spec from the Input type. */ [[nodiscard]] constexpr auto const& specFor(TypedInput const*) noexcept { @@ -187,9 +186,6 @@ specFor(TypedInput const*) noexcept } // namespace typed_fake -// example handler validated by the shared consteval spec rather than by rpc::RpcSpec. -// Note it declares no spec() and no Input of its own: both come from HandlerFor, and there -// is no tag_invoke for TypedInput, which is what keeps it off the legacy path. class TypedHandlerFake : public rpc::spec::HandlerFor { public: using Output = TestOutput; diff --git a/tests/unit/rpc/RPCHelpersTests.cpp b/tests/unit/rpc/RPCHelpersTests.cpp index b7c6055985..990e002869 100644 --- a/tests/unit/rpc/RPCHelpersTests.cpp +++ b/tests/unit/rpc/RPCHelpersTests.cpp @@ -2061,9 +2061,6 @@ INSTANTIATE_TEST_SUITE_P( tests::util::kNameGenerator ); -// getLedgerHeaderFromLedgerSpecifier — the strong-typed counterpart of -// getLedgerHeaderFromHashOrSeq. The fixture's range is [10, 300], so kRangeMax below is 300. - namespace { constexpr auto kSpecifierRangeMax = 300u; } // namespace diff --git a/tests/unit/rpc/handlers/DefaultProcessorTests.cpp b/tests/unit/rpc/handlers/DefaultProcessorTests.cpp index 22afa8cb6f..5c0db62968 100644 --- a/tests/unit/rpc/handlers/DefaultProcessorTests.cpp +++ b/tests/unit/rpc/handlers/DefaultProcessorTests.cpp @@ -70,9 +70,6 @@ TEST_F(RPCDefaultProcessorTest, InvalidInput) }); } -// Pin which path each fake takes. Without this, a change that made a typed handler also -// satisfy SomeHandlerWithInput would silently reroute it through the legacy validators and -// every test below would still pass. static_assert(SomeHandlerWithTypedInput); static_assert(not SomeHandlerWithInput); static_assert(SomeHandlerWithTypedInput); From b13953ee6852753d1b7587e9c0b8723afca9b28e Mon Sep 17 00:00:00 2001 From: Alex Kremer Date: Wed, 9 Sep 2026 15:04:39 +0100 Subject: [PATCH 21/24] Bring back new style processor code --- src/rpc/common/impl/Processors.hpp | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/rpc/common/impl/Processors.hpp b/src/rpc/common/impl/Processors.hpp index 8c7510a231..1604af1d24 100644 --- a/src/rpc/common/impl/Processors.hpp +++ b/src/rpc/common/impl/Processors.hpp @@ -27,21 +27,29 @@ struct DefaultProcessor final { "handler satisfies both the legacy and the typed input path; dispatch would be " "decided by the order of the branches below rather than by the handler" ); + static_assert( + SomeHandlerWithTypedInput or SomeHandlerWithInput or + SomeHandlerWithoutInput, + "handler matches none of the branches below" + ); // New `rpc-spec`-based handler if constexpr (SomeHandlerWithTypedInput) { + auto input = HandlerType::parseInput(value, ctx.apiVersion); auto warnings = rpc::spec::toJsonArray(HandlerType::spec(ctx.apiVersion).check(value)); - auto input = HandlerType::parseInput(value, ctx.apiVersion); - if (not input) + if (not input.has_value()) return ReturnType{Error{std::move(input).error()}, std::move(warnings)}; auto ret = handler.process(*input, ctx); - if (not ret) + + if (not ret.has_value()) return ReturnType{Error{std::move(ret).error()}, std::move(warnings)}; return ReturnType{value_from(std::move(ret).value()), std::move(warnings)}; - } else if constexpr (SomeHandlerWithInput) { + } + + if constexpr (SomeHandlerWithInput) { // Old spec-based handler: first we run validation against specified API version // TODO: This will be eventually removed once fully migraded to new rpc-spec system. auto const spec = handler.spec(ctx.apiVersion); From d485f59aa8bb1e9df14aed924881fad3867a6375 Mon Sep 17 00:00:00 2001 From: Alex Kremer Date: Wed, 9 Sep 2026 23:56:43 +0100 Subject: [PATCH 22/24] Fully qualify rpc::spec --- src/rpc/handlers/AccountCurrencies.hpp | 2 +- src/rpc/handlers/AccountInfo.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rpc/handlers/AccountCurrencies.hpp b/src/rpc/handlers/AccountCurrencies.hpp index 3f262cd769..eecac4fff1 100644 --- a/src/rpc/handlers/AccountCurrencies.hpp +++ b/src/rpc/handlers/AccountCurrencies.hpp @@ -22,7 +22,7 @@ namespace rpc { * For more details see: https://xrpl.org/account_currencies.html */ class AccountCurrenciesHandler - : public spec::HandlerFor { + : public rpc::spec::HandlerFor { // dependencies std::shared_ptr sharedPtrBackend_; diff --git a/src/rpc/handlers/AccountInfo.hpp b/src/rpc/handlers/AccountInfo.hpp index 3077070ecd..08196c0547 100644 --- a/src/rpc/handlers/AccountInfo.hpp +++ b/src/rpc/handlers/AccountInfo.hpp @@ -24,7 +24,7 @@ namespace rpc { * * For more details see: https://xrpl.org/account_info.html */ -class AccountInfoHandler : public spec::HandlerFor { +class AccountInfoHandler : public rpc::spec::HandlerFor { std::shared_ptr sharedPtrBackend_; std::shared_ptr amendmentCenter_; From 401c2854c0bcaf7a38c0d25bf341957ab1a62d90 Mon Sep 17 00:00:00 2001 From: Alex Kremer Date: Thu, 10 Sep 2026 17:06:39 +0100 Subject: [PATCH 23/24] Use 0.1.9 and remove explicit instantiations and includes --- conan.lock | 4 ++-- conanfile.py | 2 +- src/rpc/CMakeLists.txt | 3 +++ src/rpc/handlers/AccountCurrencies.cpp | 5 ----- src/rpc/handlers/AccountInfo.cpp | 5 ----- 5 files changed, 6 insertions(+), 13 deletions(-) diff --git a/conan.lock b/conan.lock index 4ffedb65d0..56fd1cb3dd 100644 --- a/conan.lock +++ b/conan.lock @@ -3,7 +3,7 @@ "requires": [ "zlib/1.3.2#1cb806da49011867778ffb6ac7190fcb%1782392402.122708", "xxhash/0.8.3#681d36a0a6111fc56e5e45ea182c19cc%1782392402.420688", - "xrpl-rpc-spec/0.1.8#173f7685d2e09121cbadabf507c0991a%1788439265.07151", + "xrpl-rpc-spec/0.1.9#b1f262f6fbdd9af105b998ce0391b7d2%1789055324.191799", "xrpl/3.3.0#5e356a24ae1f0d6da6bd617b926f92e6%1786467262.262007", "sqlite3/3.53.0#324ada52333108388a9a6108bfa96734%1782392403.185447", "spdlog/1.17.0#bcbaaf7147bda6ad24ffbd1ac3d7142c%1782736610.443882", @@ -25,7 +25,7 @@ "libarchive/3.8.7#c446109bd1f1d8ba7936c94189bc50e6%1782392403.066892", "http_parser/2.9.4#98d91690d6fd021e9e624218a85d9d97%1782736605.237778", "gtest/1.17.0#5224b3b3ff3b4ce1133cbdd27d53ee7d%1782392402.791979", - "grpc/1.81.1#f729f6d75992d20f9c72828e9142d62f%1783945160.094135", + "grpc/1.81.1#b87796a4269034856cbc1a2522db16eb%1788275071.530512", "fmt/12.1.0#50abab23274d56bb8f42c94b3b9a40c7%1782736606.427131", "ed25519/2015.03#ae761bdc52730a843f0809bdf6c1b1f6%1782307148.15562", "date/3.0.4#862e11e80030356b53c2c38599ceb32b%1782392402.538492", diff --git a/conanfile.py b/conanfile.py index 45f0b502cb..337030362b 100644 --- a/conanfile.py +++ b/conanfile.py @@ -17,7 +17,7 @@ class ClioConan(ConanFile): "fmt/12.1.0", "libbacktrace/cci.20210118", "spdlog/1.17.0", - "xrpl-rpc-spec/0.1.8", + "xrpl-rpc-spec/0.1.9", "xrpl/3.3.0", ] diff --git a/src/rpc/CMakeLists.txt b/src/rpc/CMakeLists.txt index 73dbcade06..28cd5e0988 100644 --- a/src/rpc/CMakeLists.txt +++ b/src/rpc/CMakeLists.txt @@ -61,4 +61,7 @@ target_sources( handlers/VaultInfo.cpp ) +rpcspec_generate_instantiations(OUT_VAR rpcspec_instantiations) +target_sources(clio_rpc PRIVATE ${rpcspec_instantiations}) + target_link_libraries(clio_rpc PUBLIC clio_util clio_data rpcspec::rpcspec) diff --git a/src/rpc/handlers/AccountCurrencies.cpp b/src/rpc/handlers/AccountCurrencies.cpp index 18023b1584..8fbc6265e9 100644 --- a/src/rpc/handlers/AccountCurrencies.cpp +++ b/src/rpc/handlers/AccountCurrencies.cpp @@ -8,9 +8,6 @@ #include #include #include -#include -#include // IWYU pragma: keep -#include // IWYU pragma: keep #include #include #include @@ -26,8 +23,6 @@ #include #include -template struct rpc::spec::HandlerFor; - namespace rpc { AccountCurrenciesHandler::Result AccountCurrenciesHandler::process( diff --git a/src/rpc/handlers/AccountInfo.cpp b/src/rpc/handlers/AccountInfo.cpp index a04984eafa..7bc2fb8633 100644 --- a/src/rpc/handlers/AccountInfo.cpp +++ b/src/rpc/handlers/AccountInfo.cpp @@ -11,9 +11,6 @@ #include #include #include -#include -#include // IWYU pragma: keep -#include // IWYU pragma: keep #include #include #include @@ -33,8 +30,6 @@ #include #include -template struct rpc::spec::HandlerFor; - namespace rpc { AccountInfoHandler::Result AccountInfoHandler::process(AccountInfoHandler::Input const& input, Context const& ctx) const From 2574b082185792de08b4f5cc1267b8dd99a3b5ef Mon Sep 17 00:00:00 2001 From: Alex Kremer Date: Fri, 11 Sep 2026 13:36:29 +0100 Subject: [PATCH 24/24] Use 0.1.10 and hopefully fix clang-tidy --- conan.lock | 2 +- conanfile.py | 2 +- src/rpc/handlers/AccountCurrencies.cpp | 1 - src/rpc/handlers/AccountInfo.cpp | 1 - 4 files changed, 2 insertions(+), 4 deletions(-) diff --git a/conan.lock b/conan.lock index 7143d55054..7c99571082 100644 --- a/conan.lock +++ b/conan.lock @@ -3,7 +3,7 @@ "requires": [ "zlib/1.3.2#1cb806da49011867778ffb6ac7190fcb%1782392402.122708", "xxhash/0.8.3#681d36a0a6111fc56e5e45ea182c19cc%1782392402.420688", - "xrpl-rpc-spec/0.1.9#b1f262f6fbdd9af105b998ce0391b7d2%1789055324.191799", + "xrpl-rpc-spec/0.1.10#38d3a69d1802fbb7af5796fed8326888%1789129776.315907", "xrpl/3.4.0-rc1#19678cbb46117ef8a669558ad19d6a1f%1789050823.813662", "sqlite3/3.53.0#324ada52333108388a9a6108bfa96734%1782392403.185447", "spdlog/1.17.0#bcbaaf7147bda6ad24ffbd1ac3d7142c%1782736610.443882", diff --git a/conanfile.py b/conanfile.py index 858f226427..a055c9f3f5 100644 --- a/conanfile.py +++ b/conanfile.py @@ -17,7 +17,7 @@ class ClioConan(ConanFile): "fmt/12.1.0", "libbacktrace/cci.20210118", "spdlog/1.17.0", - "xrpl-rpc-spec/0.1.9", + "xrpl-rpc-spec/0.1.10", "xrpl/3.4.0-rc1", ] diff --git a/src/rpc/handlers/AccountCurrencies.cpp b/src/rpc/handlers/AccountCurrencies.cpp index 8fbc6265e9..913fb373c2 100644 --- a/src/rpc/handlers/AccountCurrencies.cpp +++ b/src/rpc/handlers/AccountCurrencies.cpp @@ -8,7 +8,6 @@ #include #include #include -#include #include #include #include diff --git a/src/rpc/handlers/AccountInfo.cpp b/src/rpc/handlers/AccountInfo.cpp index 7dcbfa050d..5b7951f381 100644 --- a/src/rpc/handlers/AccountInfo.cpp +++ b/src/rpc/handlers/AccountInfo.cpp @@ -11,7 +11,6 @@ #include #include #include -#include #include #include #include