mirror of
https://github.com/XRPLF/clio.git
synced 2026-09-28 07:58:10 +00:00
175 lines
5.8 KiB
C++
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
|