#pragma once #include "rpc/Errors.hpp" #include "rpc/JS.hpp" #include "util/Assert.hpp" #include "web/interface/ConnectionBase.hpp" #include #include #include #include #include #include #include #include #include #include #include namespace web::impl { /** * @brief A helper that attempts to match rippled reporting mode HTTP errors as close as possible. */ class ErrorHelper { std::shared_ptr connection_; std::optional request_; public: ErrorHelper( std::shared_ptr const& connection, std::optional request = std::nullopt ) : connection_{connection}, request_{std::move(request)} { } void sendError(rpc::Status const& err) const { if (connection_->upgraded) { connection_->send(boost::json::serialize(composeError(err))); } else { // Note: a collection of crutches to match rippled output follows if (auto const clioCode = std::get_if(&err.code)) { switch (*clioCode) { case rpc::ClioError::RpcInvalidApiVersion: connection_->send( std::string{rpc::getErrorInfo(*clioCode).error}, boost::beast::http::status::bad_request ); break; case rpc::ClioError::RpcCommandIsMissing: connection_->send("Null method", boost::beast::http::status::bad_request); break; case rpc::ClioError::RpcCommandIsEmpty: connection_->send( "method is empty", boost::beast::http::status::bad_request ); break; case rpc::ClioError::RpcCommandNotString: connection_->send( "method is not string", boost::beast::http::status::bad_request ); break; case rpc::ClioError::RpcParamsUnparsable: connection_->send( "params unparsable", boost::beast::http::status::bad_request ); break; // others are not applicable but we want a compilation error next time we add // one case rpc::ClioError::RpcUnknownOption: case rpc::ClioError::RpcMalformedCurrency: case rpc::ClioError::RpcMalformedRequest: case rpc::ClioError::RpcMalformedOwner: case rpc::ClioError::RpcMalformedAddress: case rpc::ClioError::RpcFieldNotFoundTransaction: case rpc::ClioError::RpcMalformedOracleDocumentId: case rpc::ClioError::RpcMalformedAuthorizedCredentials: 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)), boost::beast::http::status::bad_request ); } } } void sendInternalError() const { connection_->send( boost::json::serialize(composeError(rpc::RippledError::RpcInternal)), boost::beast::http::status::internal_server_error ); } void sendNotReadyError() const { connection_->send( boost::json::serialize(composeError(rpc::RippledError::RpcNotReady)), boost::beast::http::status::ok ); } void sendTooBusyError() const { if (connection_->upgraded) { connection_->send( boost::json::serialize(rpc::makeError(rpc::RippledError::RpcTooBusy)), boost::beast::http::status::ok ); } else { connection_->send( boost::json::serialize(rpc::makeError(rpc::RippledError::RpcTooBusy)), boost::beast::http::status::service_unavailable ); } } void sendJsonParsingError() const { if (connection_->upgraded) { connection_->send( boost::json::serialize(rpc::makeError(rpc::RippledError::RpcBadSyntax)) ); } else { connection_->send( fmt::format("Unable to parse JSON from the request"), boost::beast::http::status::bad_request ); } } [[nodiscard]] boost::json::object composeError(auto const& error) const { auto e = rpc::makeError(error); if (request_) { auto const appendFieldIfExist = [&](auto const& field) { if (request_->contains(field) and not request_->at(field).is_null()) e[field] = request_->at(field); }; appendFieldIfExist(JS(id)); if (connection_->upgraded) appendFieldIfExist(JS(api_version)); e[JS(request)] = *request_; } if (connection_->upgraded) { return e; } return {{JS(result), e}}; } }; } // namespace web::impl