From d2de240389397a0dc54c3be8c04d89531b1500b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?emrear=C4=B1y=C3=BCrek?= Date: Mon, 22 Sep 2025 14:45:04 +0200 Subject: [PATCH] fix: Print out error details of web context (#2351) --- src/rpc/Errors.cpp | 33 ++++++++++++++++ src/rpc/Errors.hpp | 10 +++++ tests/unit/rpc/ErrorTests.cpp | 71 ++++++++++++++++++++++++++++++++++- 3 files changed, 112 insertions(+), 2 deletions(-) diff --git a/src/rpc/Errors.cpp b/src/rpc/Errors.cpp index a7ad6a13..633a8b83 100644 --- a/src/rpc/Errors.cpp +++ b/src/rpc/Errors.cpp @@ -39,6 +39,39 @@ using namespace std; namespace rpc { +std::ostream& +operator<<(std::ostream& stream, Status const& status) +{ + std::visit( + util::OverloadSet{ + [&stream, &status](RippledError 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: " << ripple::RPC::get_error_info(err).message; + }, + [&stream, &status](ClioError 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: " << getErrorInfo(err).message; + } + }, + status.code + ); + + if (status.extraInfo.has_value()) + stream << ", Extra Info: " << *status.extraInfo; + + return stream; +} + WarningInfo const& getWarningInfo(WarningCode code) { diff --git a/src/rpc/Errors.hpp b/src/rpc/Errors.hpp index 0bff5d26..0aaa7830 100644 --- a/src/rpc/Errors.hpp +++ b/src/rpc/Errors.hpp @@ -182,6 +182,16 @@ struct Status { 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. */ diff --git a/tests/unit/rpc/ErrorTests.cpp b/tests/unit/rpc/ErrorTests.cpp index f970c6d8..a3645d32 100644 --- a/tests/unit/rpc/ErrorTests.cpp +++ b/tests/unit/rpc/ErrorTests.cpp @@ -18,6 +18,7 @@ //============================================================================== #include "rpc/Errors.hpp" +#include "util/NameGenerator.hpp" #include #include @@ -139,7 +140,7 @@ TEST(RPCErrorsTest, InvalidClioErrorToJSON) } struct WarningCodeTestBundle { - std::string name; + std::string testName; WarningCode code; std::string message; }; @@ -166,7 +167,7 @@ INSTANTIATE_TEST_SUITE_P( "https://xrpl.org/docs/references/http-websocket-apis/ and update your request." } ), - [](testing::TestParamInfo const& info) { return info.param.name; } + tests::util::kNAME_GENERATOR ); TEST_P(WarningCodeTest, WarningToJSON) @@ -189,3 +190,69 @@ TEST(RPCErrorsTest, InvalidWarningToJSON) }; EXPECT_ANY_THROW((void)notSanitizedMakeWarning()); } + +struct StatusStreamTestBundle { + std::string testName; + rpc::Status status; + std::string expectedOutput; +}; + +struct RPCErrorsStatusStreamTest : public ::testing::TestWithParam { +protected: + std::ostringstream oss; +}; + +TEST_P(RPCErrorsStatusStreamTest, StatusStreamOperator) +{ + auto const param = GetParam(); + oss << param.status; + EXPECT_EQ(oss.str(), param.expectedOutput); +} + +INSTANTIATE_TEST_SUITE_P( + RPCErrorsTest, + RPCErrorsStatusStreamTest, + testing::Values( + StatusStreamTestBundle{ + .testName = "EmptyStatus", + .status = Status{}, + .expectedOutput = "Code: 0, Message: An unknown error code." + }, + StatusStreamTestBundle{ + .testName = "StatusWithRippledError", + .status = Status{RippledError::rpcSUCCESS}, + .expectedOutput = "Code: 0, Message: An unknown error code." + }, + StatusStreamTestBundle{ + .testName = "StatusWithClioError", + .status = Status{ClioError::RpcParamsUnparsable}, + .expectedOutput = "Code: 6004, Message: Params must be an array holding exactly one object." + }, + StatusStreamTestBundle{ + .testName = "StatusWithCodeAndExtraInfo", + .status = Status{ClioError::EtlConnectionError, boost::json::object{}}, + .expectedOutput = "Code: 7000, Message: Couldn't connect to rippled., Extra Info: {}" + }, + StatusStreamTestBundle{ + .testName = "StatusWithMessage", + .status = Status{"test message."}, + .expectedOutput = "Code: -1, Message: test message." + }, + StatusStreamTestBundle{ + .testName = "StatusWithRippledErrorAndMessage", + .status = Status{RippledError::rpcSUCCESS, "test message."}, + .expectedOutput = "Code: 0, Message: test message." + }, + StatusStreamTestBundle{ + .testName = "StatusWithClioErrorAndMessage", + .status = Status{ClioError::RpcParamsUnparsable, "Missing params array."}, + .expectedOutput = "Code: 6004, Message: Missing params array." + }, + StatusStreamTestBundle{ + .testName = "StatusWithCodeErrorMessage", + .status = Status{ClioError::EtlInvalidResponse, "invalidResponse", "Rippled returned an invalid response."}, + .expectedOutput = "Code: 7003, Error: invalidResponse, Message: Rippled returned an invalid response." + } + ), + tests::util::kNAME_GENERATOR +);