Files
clio/src/web/impl/ErrorHandling.hpp
2026-08-12 13:47:44 +01:00

175 lines
5.8 KiB
C++

#pragma once
#include "rpc/Errors.hpp"
#include "rpc/JS.hpp"
#include "util/Assert.hpp"
#include "web/interface/ConnectionBase.hpp"
#include <boost/beast/http/status.hpp>
#include <boost/json/object.hpp>
#include <boost/json/serialize.hpp>
#include <fmt/format.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/jss.h>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <variant>
namespace web::impl {
/**
* @brief A helper that attempts to match rippled reporting mode HTTP errors as close as possible.
*/
class ErrorHelper {
std::shared_ptr<web::ConnectionBase> connection_;
std::optional<boost::json::object> request_;
public:
ErrorHelper(
std::shared_ptr<web::ConnectionBase> const& connection,
std::optional<boost::json::object> 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<rpc::ClioError>(&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<int>(*clioCode)
); // this should never happen
break;
}
} else if (std::get_if<rpc::EtlError>(&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