#include "rpc/Errors.hpp" #include "rpc/JS.hpp" #include "util/OverloadSet.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include 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) { static constexpr WarningInfo kINFOS[]{ {WarnUnknown, "Unknown warning"}, {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"}, {WarnRpcOutdated, "This server may be out of date"}, {WarnRpcRateLimit, "You are about to be rate limited"}, {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."} }; auto matchByCode = [code](auto const& info) { return info.code == code; }; if (auto it = ranges::find_if(kINFOS, matchByCode); 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"] = code; json["message"] = info.message; return json; } 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."}, // 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."} }; auto matchByCode = [code](auto const& info) { return info.code == code; }; if (auto it = ranges::find_if(kINFOS, matchByCode); it != end(kINFOS)) return *it; throw(out_of_range("Invalid error code")); } boost::json::object makeError( RippledError err, std::optional customError, std::optional customMessage ) { boost::json::object json; auto const& info = ripple::RPC::get_error_info(err); json["error"] = customError.value_or(info.token.c_str()).data(); json["error_code"] = static_cast(err); json["error_message"] = customMessage.value_or(info.message.c_str()).data(); json["status"] = "error"; json["type"] = "response"; return json; } boost::json::object makeError( ClioError err, std::optional customError, std::optional customMessage ) { boost::json::object json; auto const& info = getErrorInfo(err); json["error"] = customError.value_or(info.error); json["error_code"] = static_cast(info.code); json["error_message"] = customMessage.value_or(info.message); json["status"] = "error"; json["type"] = "response"; return json; } boost::json::object makeError(Status const& status) { auto wrapOptional = [](string_view const& str) { return str.empty() ? nullopt : make_optional(str); }; auto res = visit( util::OverloadSet{ [&status, &wrapOptional](RippledError err) { if (err == ripple::rpcUNKNOWN) { return boost::json::object{ {"error", status.message}, {"type", "response"}, {"status", "error"} }; } return makeError(err, wrapOptional(status.error), wrapOptional(status.message)); }, [&status, &wrapOptional](ClioError err) { return makeError(err, wrapOptional(status.error), wrapOptional(status.message)); }, }, status.code ); if (status.extraInfo) { for (auto& [key, value] : status.extraInfo.value()) res[key] = value; } return res; } } // namespace rpc