mirror of
https://github.com/XRPLF/clio.git
synced 2025-12-02 17:45:57 +00:00
@@ -59,6 +59,9 @@ getWarningInfo(WarningCode code)
|
||||
"'ledger_index':'current' in your request"},
|
||||
{warnRPC_OUTDATED, "This server may be out of date"},
|
||||
{warnRPC_RATE_LIMIT, "You are about to be rate limited"},
|
||||
{warnRPC_DEPRECATED,
|
||||
"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; };
|
||||
@@ -73,10 +76,8 @@ makeWarning(WarningCode code)
|
||||
{
|
||||
auto json = boost::json::object{};
|
||||
auto const& info = getWarningInfo(code);
|
||||
|
||||
json["id"] = code;
|
||||
json["message"] = static_cast<string>(info.message);
|
||||
|
||||
json["message"] = info.message;
|
||||
return json;
|
||||
}
|
||||
|
||||
@@ -128,9 +129,9 @@ makeError(ClioError err, std::optional<std::string_view> customError, std::optio
|
||||
boost::json::object json;
|
||||
auto const& info = getErrorInfo(err);
|
||||
|
||||
json["error"] = customError.value_or(info.error).data();
|
||||
json["error"] = customError.value_or(info.error);
|
||||
json["error_code"] = static_cast<uint32_t>(info.code);
|
||||
json["error_message"] = customMessage.value_or(info.message).data();
|
||||
json["error_message"] = customMessage.value_or(info.message);
|
||||
json["status"] = "error";
|
||||
json["type"] = "response";
|
||||
|
||||
|
||||
@@ -128,6 +128,9 @@ struct Status {
|
||||
{
|
||||
}
|
||||
|
||||
bool
|
||||
operator==(Status const& other) const = default;
|
||||
|
||||
/**
|
||||
* @brief Check if the status is not OK
|
||||
*
|
||||
@@ -173,7 +176,13 @@ struct Status {
|
||||
};
|
||||
|
||||
/** @brief Warning codes that can be returned by clio. */
|
||||
enum WarningCode { warnUNKNOWN = -1, warnRPC_CLIO = 2001, warnRPC_OUTDATED = 2002, warnRPC_RATE_LIMIT = 2003 };
|
||||
enum WarningCode {
|
||||
warnUNKNOWN = -1,
|
||||
warnRPC_CLIO = 2001,
|
||||
warnRPC_OUTDATED = 2002,
|
||||
warnRPC_RATE_LIMIT = 2003,
|
||||
warnRPC_DEPRECATED = 2004
|
||||
};
|
||||
|
||||
/** @brief Holds information about a clio warning. */
|
||||
struct WarningInfo {
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
// forward declarations
|
||||
namespace feed {
|
||||
@@ -139,38 +140,37 @@ public:
|
||||
if (backend_->isTooBusy()) {
|
||||
LOG(log_.error()) << "Database is too busy. Rejecting request";
|
||||
notifyTooBusy(); // TODO: should we add ctx.method if we have it?
|
||||
return Status{RippledError::rpcTOO_BUSY};
|
||||
return Result{Status{RippledError::rpcTOO_BUSY}};
|
||||
}
|
||||
|
||||
auto const method = handlerProvider_->getHandler(ctx.method);
|
||||
if (!method) {
|
||||
notifyUnknownCommand();
|
||||
return Status{RippledError::rpcUNKNOWN_COMMAND};
|
||||
return Result{Status{RippledError::rpcUNKNOWN_COMMAND}};
|
||||
}
|
||||
|
||||
try {
|
||||
LOG(perfLog_.debug()) << ctx.tag() << " start executing rpc `" << ctx.method << '`';
|
||||
|
||||
auto const context = Context{ctx.yield, ctx.session, ctx.isAdmin, ctx.clientIp, ctx.apiVersion};
|
||||
auto const v = (*method).process(ctx.params, context);
|
||||
auto v = (*method).process(ctx.params, context);
|
||||
|
||||
LOG(perfLog_.debug()) << ctx.tag() << " finish executing rpc `" << ctx.method << '`';
|
||||
|
||||
if (v)
|
||||
return v->as_object();
|
||||
if (not v)
|
||||
notifyErrored(ctx.method);
|
||||
|
||||
notifyErrored(ctx.method);
|
||||
return Status{v.error()};
|
||||
return Result{std::move(v)};
|
||||
} catch (data::DatabaseTimeout const& t) {
|
||||
LOG(log_.error()) << "Database timeout";
|
||||
notifyTooBusy();
|
||||
|
||||
return Status{RippledError::rpcTOO_BUSY};
|
||||
return Result{Status{RippledError::rpcTOO_BUSY}};
|
||||
} catch (std::exception const& ex) {
|
||||
LOG(log_.error()) << ctx.tag() << "Caught exception: " << ex.what();
|
||||
notifyInternalError();
|
||||
|
||||
return Status{RippledError::rpcINTERNAL};
|
||||
return Result{Status{RippledError::rpcINTERNAL}};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
134
src/rpc/common/Checkers.hpp
Normal file
134
src/rpc/common/Checkers.hpp
Normal file
@@ -0,0 +1,134 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of clio: https://github.com/XRPLF/clio
|
||||
Copyright (c) 2024, the clio developers.
|
||||
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "rpc/Errors.hpp"
|
||||
#include "rpc/common/ValidationHelpers.hpp"
|
||||
|
||||
#include <boost/json/value.hpp>
|
||||
#include <boost/json/value_to.hpp>
|
||||
#include <fmt/core.h>
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace rpc::check {
|
||||
/**
|
||||
* @brief Warning that checks can return
|
||||
*/
|
||||
struct Warning {
|
||||
/**
|
||||
* @brief Construct a new Warning object
|
||||
*
|
||||
* @param code The warning code
|
||||
* @param message The warning message
|
||||
*/
|
||||
Warning(WarningCode code, std::string message) : warningCode(code), extraMessage(std::move(message))
|
||||
{
|
||||
}
|
||||
|
||||
bool
|
||||
operator==(Warning const& other) const = default;
|
||||
|
||||
WarningCode warningCode;
|
||||
std::string extraMessage;
|
||||
};
|
||||
using Warnings = std::vector<Warning>;
|
||||
|
||||
/**
|
||||
* @brief Check for a deprecated fields
|
||||
*/
|
||||
template <typename... T>
|
||||
class Deprecated;
|
||||
|
||||
/**
|
||||
* @brief Check if a field is deprecated
|
||||
*/
|
||||
template <>
|
||||
class Deprecated<> final {
|
||||
public:
|
||||
/**
|
||||
* @brief Check if a field is deprecated
|
||||
*
|
||||
* @param value The json value to check
|
||||
* @param key The key to check
|
||||
* @return A warning if the field is deprecated or std::nullopt otherwise
|
||||
*/
|
||||
[[nodiscard]] static std::optional<Warning>
|
||||
check(boost::json::value const& value, std::string_view key)
|
||||
{
|
||||
if (value.is_object() and value.as_object().contains(key))
|
||||
return Warning{WarningCode::warnRPC_DEPRECATED, fmt::format("Field '{}' is deprecated.", key)};
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Check if a value of a field is deprecated
|
||||
* @tparam T The type of the field
|
||||
*/
|
||||
template <typename T>
|
||||
class Deprecated<T> final {
|
||||
T value_;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new Deprecated object
|
||||
*
|
||||
* @param val The value that is deprecated
|
||||
*/
|
||||
Deprecated(T val) : value_(val)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if a value of a field is deprecated
|
||||
*
|
||||
* @param value The json value to check
|
||||
* @param key The key to check
|
||||
* @return A warning if the field is deprecated or std::nullopt otherwise
|
||||
*/
|
||||
[[nodiscard]] std::optional<Warning>
|
||||
check(boost::json::value const& value, std::string_view key) const
|
||||
{
|
||||
if (value.is_object() and value.as_object().contains(key) and
|
||||
validation::checkType<T>(value.as_object().at(key))) {
|
||||
using boost::json::value_to;
|
||||
auto const res = value_to<T>(value.as_object().at(key));
|
||||
if (value_ == res) {
|
||||
return Warning{
|
||||
WarningCode::warnRPC_DEPRECATED, fmt::format("Value '{}' for field '{}' is deprecated", value_, key)
|
||||
};
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Deduction guide for Deprecated
|
||||
*/
|
||||
template <typename... T>
|
||||
Deprecated(T&&...) -> Deprecated<T...>;
|
||||
|
||||
} // namespace rpc::check
|
||||
@@ -19,6 +19,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "rpc/Errors.hpp"
|
||||
#include "rpc/common/Checkers.hpp"
|
||||
#include "rpc/common/Types.hpp"
|
||||
|
||||
#include <boost/json/value.hpp>
|
||||
@@ -26,6 +28,7 @@
|
||||
#include <boost/json/value_to.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
namespace rpc {
|
||||
@@ -52,6 +55,16 @@ concept SomeModifier = requires(T a, boost::json::value lval) {
|
||||
} -> std::same_as<MaybeError>;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Specifies what a check used with @ref rpc::FieldSpec must provide.
|
||||
*/
|
||||
template <typename T>
|
||||
concept SomeCheck = requires(T a, boost::json::value lval) {
|
||||
{
|
||||
a.check(lval, std::string{})
|
||||
} -> std::same_as<std::optional<check::Warning>>;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The requirements of a processor to be used with @ref rpc::FieldSpec.
|
||||
*/
|
||||
|
||||
@@ -106,7 +106,7 @@ public:
|
||||
* @param requirements The requirements to validate against
|
||||
*/
|
||||
template <SomeRequirement... Requirements>
|
||||
IfType(Requirements&&... requirements)
|
||||
explicit IfType(Requirements&&... requirements)
|
||||
: processor_(
|
||||
[... r = std::forward<Requirements>(requirements
|
||||
)](boost::json::value& j, std::string_view key) -> MaybeError {
|
||||
@@ -133,6 +133,9 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
IfType(IfType const&) = default;
|
||||
IfType(IfType&&) = default;
|
||||
|
||||
/**
|
||||
* @brief Verify that the element is valid according to the stored requirements when type matches.
|
||||
*
|
||||
|
||||
@@ -19,10 +19,18 @@
|
||||
|
||||
#include "rpc/common/Specs.hpp"
|
||||
|
||||
#include "rpc/Errors.hpp"
|
||||
#include "rpc/common/Checkers.hpp"
|
||||
#include "rpc/common/Types.hpp"
|
||||
|
||||
#include <boost/json/array.hpp>
|
||||
#include <boost/json/value.hpp>
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace rpc {
|
||||
|
||||
[[nodiscard]] MaybeError
|
||||
@@ -31,6 +39,12 @@ FieldSpec::process(boost::json::value& value) const
|
||||
return processor_(value);
|
||||
}
|
||||
|
||||
[[nodiscard]] check::Warnings
|
||||
FieldSpec::check(boost::json::value const& value) const
|
||||
{
|
||||
return checker_(value);
|
||||
}
|
||||
|
||||
[[nodiscard]] MaybeError
|
||||
RpcSpec::process(boost::json::value& value) const
|
||||
{
|
||||
@@ -42,4 +56,27 @@ RpcSpec::process(boost::json::value& value) const
|
||||
return {};
|
||||
}
|
||||
|
||||
[[nodiscard]] boost::json::array
|
||||
RpcSpec::check(boost::json::value const& value) const
|
||||
{
|
||||
std::unordered_map<WarningCode, std::vector<std::string>> warnings;
|
||||
for (auto const& field : fields_) {
|
||||
auto fieldWarnings = field.check(value);
|
||||
for (auto& fw : fieldWarnings) {
|
||||
warnings[fw.warningCode].push_back(std::move(fw.extraMessage));
|
||||
}
|
||||
}
|
||||
|
||||
boost::json::array result;
|
||||
for (auto const& [code, messages] : warnings) {
|
||||
auto warningObject = makeWarning(code);
|
||||
auto& warningMessage = warningObject["message"].as_string();
|
||||
for (auto const& message : messages) {
|
||||
warningMessage.append(" ").append(message);
|
||||
}
|
||||
result.push_back(std::move(warningObject));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace rpc
|
||||
|
||||
@@ -19,15 +19,17 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "rpc/common/Checkers.hpp"
|
||||
#include "rpc/common/Concepts.hpp"
|
||||
#include "rpc/common/Types.hpp"
|
||||
#include "rpc/common/impl/Factories.hpp"
|
||||
|
||||
#include <boost/json/array.hpp>
|
||||
#include <boost/json/value.hpp>
|
||||
|
||||
#include <functional>
|
||||
#include <initializer_list>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace rpc {
|
||||
@@ -46,11 +48,26 @@ struct FieldSpec final {
|
||||
template <SomeProcessor... Processors>
|
||||
FieldSpec(std::string const& key, Processors&&... processors)
|
||||
: processor_{impl::makeFieldProcessor<Processors...>(key, std::forward<Processors>(processors)...)}
|
||||
, checker_{impl::EMPTY_FIELD_CHECKER}
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Processos the passed JSON value using the stored processors.
|
||||
* @brief Construct a field specification out of a set of checkers.
|
||||
*
|
||||
* @tparam Checks The types of checkers
|
||||
* @param key The key in a JSON object that the field validates
|
||||
* @param checks The checks, each of them have to fulfil the @ref rpc::SomeCheck concept
|
||||
*/
|
||||
template <SomeCheck... Checks>
|
||||
FieldSpec(std::string const& key, Checks&&... checks)
|
||||
: processor_{impl::EMPTY_FIELD_PROCESSOR}
|
||||
, checker_{impl::makeFieldChecker<Checks...>(key, std::forward<Checks>(checks)...)}
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Processes the passed JSON value using the stored processors.
|
||||
*
|
||||
* @param value The JSON value to validate and/or modify
|
||||
* @return Nothing on success; @ref Status on error
|
||||
@@ -58,8 +75,18 @@ struct FieldSpec final {
|
||||
[[nodiscard]] MaybeError
|
||||
process(boost::json::value& value) const;
|
||||
|
||||
/**
|
||||
* @brief Checks the passed JSON value using the stored checkers.
|
||||
*
|
||||
* @param value The JSON value to validate
|
||||
* @return A vector of warnings (empty if no warnings)
|
||||
*/
|
||||
[[nodiscard]] check::Warnings
|
||||
check(boost::json::value const& value) const;
|
||||
|
||||
private:
|
||||
std::function<MaybeError(boost::json::value&)> processor_;
|
||||
impl::FieldSpecProcessor processor_;
|
||||
impl::FieldChecker checker_;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -91,7 +118,7 @@ struct RpcSpec final {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Processos the passed JSON value using the stored field specs.
|
||||
* @brief Processes the passed JSON value using the stored field specs.
|
||||
*
|
||||
* @param value The JSON value to validate and/or modify
|
||||
* @return Nothing on success; @ref Status on error
|
||||
@@ -99,6 +126,15 @@ struct RpcSpec final {
|
||||
[[nodiscard]] MaybeError
|
||||
process(boost::json::value& value) const;
|
||||
|
||||
/**
|
||||
* @brief Checks the passed JSON value using the stored field specs.
|
||||
*
|
||||
* @param value The JSON value to validate
|
||||
* @return JSON array of warnings (empty if no warnings)
|
||||
*/
|
||||
[[nodiscard]] boost::json::array
|
||||
check(boost::json::value const& value) const;
|
||||
|
||||
private:
|
||||
std::vector<FieldSpec> fields_;
|
||||
};
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "rpc/Errors.hpp"
|
||||
|
||||
#include <boost/asio/spawn.hpp>
|
||||
#include <boost/json/array.hpp>
|
||||
#include <boost/json/conversion.hpp>
|
||||
#include <boost/json/object.hpp>
|
||||
#include <boost/json/value.hpp>
|
||||
@@ -33,6 +34,7 @@
|
||||
#include <expected>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
|
||||
namespace etl {
|
||||
@@ -55,6 +57,21 @@ class Counters;
|
||||
*/
|
||||
using MaybeError = std::expected<void, Status>;
|
||||
|
||||
/**
|
||||
* @brief Check if two MaybeError objects are equal
|
||||
*
|
||||
* @param lhs The first MaybeError object
|
||||
* @param rhs The second MaybeError object
|
||||
* @return true if the two MaybeError objects are equal, false otherwise
|
||||
*/
|
||||
inline bool
|
||||
operator==(MaybeError const& lhs, MaybeError const& rhs)
|
||||
{
|
||||
bool const lhsHasError = !static_cast<bool>(lhs);
|
||||
bool const rhsHasError = !static_cast<bool>(rhs);
|
||||
return lhsHasError == rhsHasError && (!lhsHasError || lhs.error() == rhs.error());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief The type that represents just the error part of @ref MaybeError
|
||||
*/
|
||||
@@ -69,7 +86,29 @@ using HandlerReturnType = std::expected<OutputType, Status>;
|
||||
/**
|
||||
* @brief The final return type out of RPC engine
|
||||
*/
|
||||
using ReturnType = std::expected<boost::json::value, Status>;
|
||||
struct ReturnType {
|
||||
/**
|
||||
* @brief Construct a new Return Type object
|
||||
*
|
||||
* @param result The result of the RPC call
|
||||
* @param warnings The warnings generated by the RPC call
|
||||
*/
|
||||
ReturnType(std::expected<boost::json::value, Status> result, boost::json::array warnings = {})
|
||||
: result{std::move(result)}, warnings{std::move(warnings)}
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief operator bool to check whether the ReturnType contains a response
|
||||
*/
|
||||
operator bool() const
|
||||
{
|
||||
return result.operator bool();
|
||||
}
|
||||
|
||||
std::expected<boost::json::value, Status> result;
|
||||
boost::json::array warnings;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief An empty type used as Output for handlers than don't actually produce output.
|
||||
@@ -90,7 +129,43 @@ struct Context {
|
||||
/**
|
||||
* @brief Result type used to return responses or error statuses to the Webserver subsystem.
|
||||
*/
|
||||
using Result = std::variant<Status, boost::json::object>;
|
||||
struct Result {
|
||||
/**
|
||||
* @brief Construct a new Result object from ReturnType
|
||||
*
|
||||
* @param returnType The ReturnType to construct the result from
|
||||
*/
|
||||
explicit Result(ReturnType returnType)
|
||||
{
|
||||
if (returnType) {
|
||||
response = std::move(returnType.result).value().as_object();
|
||||
} else {
|
||||
response = std::move(returnType.result).error();
|
||||
}
|
||||
warnings = std::move(returnType.warnings);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct a new Result object from Status
|
||||
*
|
||||
* @param status The status to construct the result from
|
||||
*/
|
||||
explicit Result(Status status) : response{std::move(status)}
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct a new Result object from a response object
|
||||
*
|
||||
* @param response The response to construct the result from
|
||||
*/
|
||||
explicit Result(boost::json::object response) : response{std::move(response)}
|
||||
{
|
||||
}
|
||||
|
||||
std::variant<Status, boost::json::object> response;
|
||||
boost::json::array warnings;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A cursor object used to traverse nodes owned by an account.
|
||||
|
||||
70
src/rpc/common/ValidationHelpers.hpp
Normal file
70
src/rpc/common/ValidationHelpers.hpp
Normal file
@@ -0,0 +1,70 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of clio: https://github.com/XRPLF/clio
|
||||
Copyright (c) 2024, the clio developers.
|
||||
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <boost/json/array.hpp>
|
||||
#include <boost/json/object.hpp>
|
||||
#include <boost/json/value.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace rpc::validation {
|
||||
|
||||
/**
|
||||
* @brief Check that the type is the same as what was expected.
|
||||
*
|
||||
* @tparam Expected The expected type that value should be convertible to
|
||||
* @param value The json value to check the type of
|
||||
* @return true if convertible; false otherwise
|
||||
*/
|
||||
template <typename Expected>
|
||||
[[nodiscard]] bool static checkType(boost::json::value const& value)
|
||||
{
|
||||
auto hasError = false;
|
||||
if constexpr (std::is_same_v<Expected, bool>) {
|
||||
if (not value.is_bool())
|
||||
hasError = true;
|
||||
} else if constexpr (std::is_same_v<Expected, std::string>) {
|
||||
if (not value.is_string())
|
||||
hasError = true;
|
||||
} else if constexpr (std::is_same_v<Expected, double> or std::is_same_v<Expected, float>) {
|
||||
if (not value.is_double())
|
||||
hasError = true;
|
||||
} else if constexpr (std::is_same_v<Expected, boost::json::array>) {
|
||||
if (not value.is_array())
|
||||
hasError = true;
|
||||
} else if constexpr (std::is_same_v<Expected, boost::json::object>) {
|
||||
if (not value.is_object())
|
||||
hasError = true;
|
||||
} else if constexpr (std::is_convertible_v<Expected, uint64_t> or std::is_convertible_v<Expected, int64_t>) {
|
||||
if (not value.is_int64() && not value.is_uint64())
|
||||
hasError = true;
|
||||
// specify the type is unsigened, it can not be negative
|
||||
if constexpr (std::is_unsigned_v<Expected>) {
|
||||
if (value.is_int64() and value.as_int64() < 0)
|
||||
hasError = true;
|
||||
}
|
||||
}
|
||||
|
||||
return not hasError;
|
||||
}
|
||||
|
||||
} // namespace rpc::validation
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
#include "rpc/Errors.hpp"
|
||||
#include "rpc/common/Types.hpp"
|
||||
#include "rpc/common/ValidationHelpers.hpp"
|
||||
|
||||
#include <boost/json/array.hpp>
|
||||
#include <boost/json/object.hpp>
|
||||
@@ -38,45 +39,6 @@
|
||||
|
||||
namespace rpc::validation {
|
||||
|
||||
/**
|
||||
* @brief Check that the type is the same as what was expected.
|
||||
*
|
||||
* @tparam Expected The expected type that value should be convertible to
|
||||
* @param value The json value to check the type of
|
||||
* @return true if convertible; false otherwise
|
||||
*/
|
||||
template <typename Expected>
|
||||
[[nodiscard]] bool static checkType(boost::json::value const& value)
|
||||
{
|
||||
auto hasError = false;
|
||||
if constexpr (std::is_same_v<Expected, bool>) {
|
||||
if (not value.is_bool())
|
||||
hasError = true;
|
||||
} else if constexpr (std::is_same_v<Expected, std::string>) {
|
||||
if (not value.is_string())
|
||||
hasError = true;
|
||||
} else if constexpr (std::is_same_v<Expected, double> or std::is_same_v<Expected, float>) {
|
||||
if (not value.is_double())
|
||||
hasError = true;
|
||||
} else if constexpr (std::is_same_v<Expected, boost::json::array>) {
|
||||
if (not value.is_array())
|
||||
hasError = true;
|
||||
} else if constexpr (std::is_same_v<Expected, boost::json::object>) {
|
||||
if (not value.is_object())
|
||||
hasError = true;
|
||||
} else if constexpr (std::is_convertible_v<Expected, uint64_t> or std::is_convertible_v<Expected, int64_t>) {
|
||||
if (not value.is_int64() && not value.is_uint64())
|
||||
hasError = true;
|
||||
// specify the type is unsigened, it can not be negative
|
||||
if constexpr (std::is_unsigned_v<Expected>) {
|
||||
if (value.is_int64() and value.as_int64() < 0)
|
||||
hasError = true;
|
||||
}
|
||||
}
|
||||
|
||||
return not hasError;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief A validator that simply requires a field to be present.
|
||||
*/
|
||||
|
||||
@@ -20,21 +20,31 @@
|
||||
#pragma once
|
||||
|
||||
#include "rpc/Errors.hpp"
|
||||
#include "rpc/common/Checkers.hpp"
|
||||
#include "rpc/common/Concepts.hpp"
|
||||
#include "rpc/common/Types.hpp"
|
||||
|
||||
#include <boost/json/array.hpp>
|
||||
#include <boost/json/value.hpp>
|
||||
|
||||
#include <expected>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace rpc::impl {
|
||||
|
||||
template <typename>
|
||||
static constexpr bool unsupported_v = false;
|
||||
|
||||
using FieldSpecProcessor = std::function<MaybeError(boost::json::value&)>;
|
||||
|
||||
static FieldSpecProcessor const EMPTY_FIELD_PROCESSOR = [](boost::json::value&) -> MaybeError { return {}; };
|
||||
|
||||
template <SomeProcessor... Processors>
|
||||
[[nodiscard]] auto
|
||||
[[nodiscard]] FieldSpecProcessor
|
||||
makeFieldProcessor(std::string const& key, Processors&&... procs)
|
||||
{
|
||||
return [key, ... proc = std::forward<Processors>(procs)](boost::json::value& j) -> MaybeError {
|
||||
@@ -61,10 +71,32 @@ makeFieldProcessor(std::string const& key, Processors&&... procs)
|
||||
);
|
||||
|
||||
if (firstFailure)
|
||||
return Error{firstFailure.value()};
|
||||
return std::unexpected{std::move(firstFailure).value()};
|
||||
|
||||
return {};
|
||||
};
|
||||
}
|
||||
|
||||
using FieldChecker = std::function<check::Warnings(boost::json::value const&)>;
|
||||
|
||||
static FieldChecker const EMPTY_FIELD_CHECKER = [](boost::json::value const&) -> check::Warnings { return {}; };
|
||||
|
||||
template <SomeCheck... Checks>
|
||||
[[nodiscard]] FieldChecker
|
||||
makeFieldChecker(std::string const& key, Checks&&... checks)
|
||||
{
|
||||
return [key, ... checks = std::forward<Checks>(checks)](boost::json::value const& j) -> check::Warnings {
|
||||
check::Warnings warnings;
|
||||
// This expands in order of Checks and collects all warnings into a WarningsCollection
|
||||
(
|
||||
[&j, &key, &warnings, req = &checks]() {
|
||||
if (auto res = req->check(j, key); res)
|
||||
warnings.push_back(std::move(res).value());
|
||||
}(),
|
||||
...
|
||||
);
|
||||
return warnings;
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace rpc::impl
|
||||
|
||||
@@ -91,14 +91,14 @@ public:
|
||||
auto toForward = ctx.params;
|
||||
toForward["command"] = ctx.method;
|
||||
|
||||
auto const res = balancer_->forwardToRippled(toForward, ctx.clientIp, ctx.yield);
|
||||
auto res = balancer_->forwardToRippled(toForward, ctx.clientIp, ctx.yield);
|
||||
if (not res) {
|
||||
notifyFailedToForward(ctx.method);
|
||||
return Status{RippledError::rpcFAILED_TO_FORWARD};
|
||||
return Result{Status{RippledError::rpcFAILED_TO_FORWARD}};
|
||||
}
|
||||
|
||||
notifyForwarded(ctx.method);
|
||||
return *res;
|
||||
return Result{std::move(res).value()};
|
||||
}
|
||||
|
||||
bool
|
||||
|
||||
@@ -38,27 +38,29 @@ struct DefaultProcessor final {
|
||||
using boost::json::value_to;
|
||||
if constexpr (SomeHandlerWithInput<HandlerType>) {
|
||||
// first we run validation against specified API version
|
||||
|
||||
auto const spec = handler.spec(ctx.apiVersion);
|
||||
auto warnings = spec.check(value);
|
||||
auto input = value; // copy here, spec require mutable data
|
||||
|
||||
if (auto const ret = spec.process(input); not ret)
|
||||
return Error{ret.error()}; // forward Status
|
||||
return ReturnType{Error{ret.error()}, std::move(warnings)}; // forward Status
|
||||
|
||||
auto const inData = value_to<typename HandlerType::Input>(input);
|
||||
auto const ret = handler.process(inData, ctx);
|
||||
auto ret = handler.process(inData, ctx);
|
||||
|
||||
// real handler is given expected Input, not json
|
||||
if (!ret) {
|
||||
return Error{ret.error()}; // forward Status
|
||||
return ReturnType{Error{std::move(ret).error()}, std::move(warnings)}; // forward Status
|
||||
}
|
||||
return value_from(ret.value());
|
||||
return ReturnType{value_from(std::move(ret).value()), std::move(warnings)};
|
||||
} else if constexpr (SomeHandlerWithoutInput<HandlerType>) {
|
||||
// no input to pass, ignore the value
|
||||
auto const ret = handler.process(ctx);
|
||||
if (not ret) {
|
||||
return Error{ret.error()}; // forward Status
|
||||
return ReturnType{Error{ret.error()}}; // forward Status
|
||||
}
|
||||
return value_from(ret.value());
|
||||
return ReturnType{value_from(ret.value())};
|
||||
} else {
|
||||
// when concept SomeHandlerWithInput and SomeHandlerWithoutInput not cover all Handler case
|
||||
static_assert(unsupported_handler_v<HandlerType>);
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
#include "data/BackendInterface.hpp"
|
||||
#include "rpc/JS.hpp"
|
||||
#include "rpc/common/Checkers.hpp"
|
||||
#include "rpc/common/Specs.hpp"
|
||||
#include "rpc/common/Types.hpp"
|
||||
#include "rpc/common/Validators.hpp"
|
||||
@@ -93,7 +94,9 @@ public:
|
||||
static auto const rpcSpec = RpcSpec{
|
||||
{JS(account), validation::Required{}, validation::AccountValidator},
|
||||
{JS(ledger_hash), validation::Uint256HexStringValidator},
|
||||
{JS(ledger_index), validation::LedgerIndexValidator}
|
||||
{JS(ledger_index), validation::LedgerIndexValidator},
|
||||
{"account_index", check::Deprecated{}},
|
||||
{JS(strict), check::Deprecated{}}
|
||||
};
|
||||
|
||||
return rpcSpec;
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
#include "data/BackendInterface.hpp"
|
||||
#include "rpc/JS.hpp"
|
||||
#include "rpc/common/Checkers.hpp"
|
||||
#include "rpc/common/JsonBool.hpp"
|
||||
#include "rpc/common/Specs.hpp"
|
||||
#include "rpc/common/Types.hpp"
|
||||
@@ -131,8 +132,11 @@ public:
|
||||
static auto const rpcSpecV1 = RpcSpec{
|
||||
{JS(account), validation::AccountValidator},
|
||||
{JS(ident), validation::AccountValidator},
|
||||
{JS(ident), check::Deprecated{}},
|
||||
{JS(ledger_hash), validation::Uint256HexStringValidator},
|
||||
{JS(ledger_index), validation::LedgerIndexValidator}
|
||||
{JS(ledger_index), validation::LedgerIndexValidator},
|
||||
{JS(ledger), check::Deprecated{}},
|
||||
{JS(strict), check::Deprecated{}}
|
||||
};
|
||||
|
||||
static auto const rpcSpec = RpcSpec{rpcSpecV1, {{JS(signer_lists), validation::Type<bool>{}}}};
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "data/BackendInterface.hpp"
|
||||
#include "rpc/Errors.hpp"
|
||||
#include "rpc/JS.hpp"
|
||||
#include "rpc/common/Checkers.hpp"
|
||||
#include "rpc/common/MetaProcessors.hpp"
|
||||
#include "rpc/common/Modifiers.hpp"
|
||||
#include "rpc/common/Types.hpp"
|
||||
@@ -136,6 +137,8 @@ public:
|
||||
modifiers::Clamp<int32_t>{LIMIT_MIN, LIMIT_MAX}},
|
||||
{JS(ledger_index), validation::LedgerIndexValidator},
|
||||
{JS(marker), validation::AccountMarkerValidator},
|
||||
{JS(ledger), check::Deprecated{}},
|
||||
{"peer_index", check::Deprecated{}},
|
||||
};
|
||||
|
||||
return rpcSpec;
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
#include "data/BackendInterface.hpp"
|
||||
#include "rpc/JS.hpp"
|
||||
#include "rpc/common/Checkers.hpp"
|
||||
#include "rpc/common/Modifiers.hpp"
|
||||
#include "rpc/common/Specs.hpp"
|
||||
#include "rpc/common/Types.hpp"
|
||||
@@ -118,7 +119,9 @@ public:
|
||||
{JS(limit),
|
||||
validation::Type<uint32_t>{},
|
||||
validation::Min(1u),
|
||||
modifiers::Clamp<int32_t>{LIMIT_MIN, LIMIT_MAX}}
|
||||
modifiers::Clamp<int32_t>{LIMIT_MIN, LIMIT_MAX}},
|
||||
{JS(ledger), check::Deprecated{}},
|
||||
{JS(strict), check::Deprecated{}},
|
||||
};
|
||||
|
||||
return rpcSpec;
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
#include "data/BackendInterface.hpp"
|
||||
#include "rpc/JS.hpp"
|
||||
#include "rpc/common/Checkers.hpp"
|
||||
#include "rpc/common/Specs.hpp"
|
||||
#include "rpc/common/Types.hpp"
|
||||
#include "rpc/common/Validators.hpp"
|
||||
@@ -102,7 +103,9 @@ public:
|
||||
{
|
||||
static auto const rpcSpec = RpcSpec{
|
||||
{JS(full), validation::Type<bool>{}, validation::NotSupported{true}},
|
||||
{JS(full), check::Deprecated{}},
|
||||
{JS(accounts), validation::Type<bool>{}, validation::NotSupported{true}},
|
||||
{JS(accounts), check::Deprecated{}},
|
||||
{JS(owner_funds), validation::Type<bool>{}},
|
||||
{JS(queue), validation::Type<bool>{}, validation::NotSupported{true}},
|
||||
{JS(ledger_hash), validation::Uint256HexStringValidator},
|
||||
@@ -111,6 +114,8 @@ public:
|
||||
{JS(expand), validation::Type<bool>{}},
|
||||
{JS(binary), validation::Type<bool>{}},
|
||||
{"diff", validation::Type<bool>{}},
|
||||
{JS(ledger), check::Deprecated{}},
|
||||
{JS(type), check::Deprecated{}},
|
||||
};
|
||||
|
||||
return rpcSpec;
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "data/BackendInterface.hpp"
|
||||
#include "rpc/Errors.hpp"
|
||||
#include "rpc/JS.hpp"
|
||||
#include "rpc/common/Checkers.hpp"
|
||||
#include "rpc/common/MetaProcessors.hpp"
|
||||
#include "rpc/common/Specs.hpp"
|
||||
#include "rpc/common/Types.hpp"
|
||||
@@ -128,7 +129,7 @@ public:
|
||||
validation::Type<std::string>{}, Status{ripple::rpcINVALID_PARAMS, "Invalid field 'type', not string."}
|
||||
},
|
||||
validation::OneOf<std::string>(ledgerTypeStrs.cbegin(), ledgerTypeStrs.cend())},
|
||||
|
||||
{JS(ledger), check::Deprecated{}},
|
||||
};
|
||||
return rpcSpec;
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "data/BackendInterface.hpp"
|
||||
#include "rpc/Errors.hpp"
|
||||
#include "rpc/JS.hpp"
|
||||
#include "rpc/common/Checkers.hpp"
|
||||
#include "rpc/common/MetaProcessors.hpp"
|
||||
#include "rpc/common/Specs.hpp"
|
||||
#include "rpc/common/Types.hpp"
|
||||
@@ -297,7 +298,8 @@ public:
|
||||
meta::WithCustomError{
|
||||
validation::Type<uint32_t>{}, Status(ClioError::rpcMALFORMED_ORACLE_DOCUMENT_ID)
|
||||
}},
|
||||
}}}
|
||||
}}},
|
||||
{JS(ledger), check::Deprecated{}},
|
||||
};
|
||||
|
||||
return rpcSpec;
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include "rpc/Errors.hpp"
|
||||
#include "rpc/JS.hpp"
|
||||
#include "rpc/RPCHelpers.hpp"
|
||||
#include "rpc/common/Checkers.hpp"
|
||||
#include "rpc/common/MetaProcessors.hpp"
|
||||
#include "rpc/common/Types.hpp"
|
||||
#include "rpc/common/Validators.hpp"
|
||||
@@ -122,8 +123,8 @@ public:
|
||||
* @param apiVersion The api version to return the spec for
|
||||
* @return The spec for the given apiVersion
|
||||
*/
|
||||
RpcSpecConstRef
|
||||
spec([[maybe_unused]] uint32_t apiVersion) const
|
||||
static RpcSpecConstRef
|
||||
spec([[maybe_unused]] uint32_t apiVersion)
|
||||
{
|
||||
static auto const booksValidator =
|
||||
validation::CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError {
|
||||
@@ -163,6 +164,9 @@ public:
|
||||
{JS(accounts), validation::SubscribeAccountsValidator},
|
||||
{JS(accounts_proposed), validation::SubscribeAccountsValidator},
|
||||
{JS(books), booksValidator},
|
||||
{"user", check::Deprecated{}},
|
||||
{JS(password), check::Deprecated{}},
|
||||
{JS(rt_accounts), check::Deprecated{}}
|
||||
};
|
||||
|
||||
return rpcSpec;
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "rpc/Errors.hpp"
|
||||
#include "rpc/JS.hpp"
|
||||
#include "rpc/RPCHelpers.hpp"
|
||||
#include "rpc/common/Checkers.hpp"
|
||||
#include "rpc/common/Specs.hpp"
|
||||
#include "rpc/common/Types.hpp"
|
||||
#include "rpc/common/Validators.hpp"
|
||||
@@ -98,8 +99,8 @@ public:
|
||||
* @param apiVersion The api version to return the spec for
|
||||
* @return The spec for the given apiVersion
|
||||
*/
|
||||
RpcSpecConstRef
|
||||
spec([[maybe_unused]] uint32_t apiVersion) const
|
||||
static RpcSpecConstRef
|
||||
spec([[maybe_unused]] uint32_t apiVersion)
|
||||
{
|
||||
static auto const booksValidator =
|
||||
validation::CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError {
|
||||
@@ -126,6 +127,9 @@ public:
|
||||
{JS(accounts), validation::SubscribeAccountsValidator},
|
||||
{JS(accounts_proposed), validation::SubscribeAccountsValidator},
|
||||
{JS(books), booksValidator},
|
||||
{JS(url), check::Deprecated{}},
|
||||
{JS(rt_accounts), check::Deprecated{}},
|
||||
{"rt_transactions", check::Deprecated{}},
|
||||
};
|
||||
|
||||
return rpcSpec;
|
||||
|
||||
@@ -194,7 +194,8 @@ private:
|
||||
rpc::logDuration(*context, us);
|
||||
|
||||
boost::json::object response;
|
||||
if (auto const status = std::get_if<rpc::Status>(&result)) {
|
||||
|
||||
if (auto const status = std::get_if<rpc::Status>(&result.response)) {
|
||||
// note: error statuses are counted/notified in buildResponse itself
|
||||
response = web::impl::ErrorHelper(connection, request).composeError(*status);
|
||||
auto const responseStr = boost::json::serialize(response);
|
||||
@@ -205,7 +206,7 @@ private:
|
||||
// This can still technically be an error. Clio counts forwarded requests as successful.
|
||||
rpcEngine_->notifyComplete(context->method, us);
|
||||
|
||||
auto& json = std::get<boost::json::object>(result);
|
||||
auto& json = std::get<boost::json::object>(result.response);
|
||||
auto const isForwarded =
|
||||
json.contains("forwarded") && json.at("forwarded").is_bool() && json.at("forwarded").as_bool();
|
||||
|
||||
@@ -246,7 +247,7 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
boost::json::array warnings;
|
||||
boost::json::array warnings = std::move(result.warnings);
|
||||
warnings.emplace_back(rpc::makeWarning(rpc::warnRPC_CLIO));
|
||||
|
||||
if (etl_->lastCloseAgeSeconds() >= 60)
|
||||
|
||||
Reference in New Issue
Block a user