mirror of
https://github.com/XRPLF/clio.git
synced 2025-11-21 04:05:51 +00:00
@@ -68,6 +68,7 @@ target_sources(clio PRIVATE
|
||||
src/rpc/WorkQueue.cpp
|
||||
src/rpc/common/Specs.cpp
|
||||
src/rpc/common/Validators.cpp
|
||||
src/rpc/common/MetaProcessors.cpp
|
||||
src/rpc/common/impl/APIVersionParser.cpp
|
||||
# RPC impl
|
||||
src/rpc/common/impl/HandlerProvider.cpp
|
||||
|
||||
@@ -36,11 +36,21 @@ struct RpcSpec;
|
||||
*/
|
||||
// clang-format off
|
||||
template <typename T>
|
||||
concept Requirement = requires(T a) {
|
||||
{ a.verify(boost::json::value{}, std::string{}) } -> std::same_as<MaybeError>;
|
||||
concept Requirement = requires(T a, boost::json::value lval) {
|
||||
{ a.verify(lval, std::string{}) } -> std::same_as<MaybeError>;
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
// clang-format off
|
||||
template <typename T>
|
||||
concept Modifier = requires(T a, boost::json::value lval) {
|
||||
{ a.modify(lval, std::string{}) } -> std::same_as<MaybeError>;
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
template <typename T>
|
||||
concept Processor = (Requirement<T> or Modifier<T>);
|
||||
|
||||
/**
|
||||
* @brief A concept that specifies what a Handler type must provide
|
||||
*
|
||||
|
||||
71
src/rpc/common/MetaProcessors.cpp
Normal file
71
src/rpc/common/MetaProcessors.cpp
Normal file
@@ -0,0 +1,71 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of clio: https://github.com/XRPLF/clio
|
||||
Copyright (c) 2023, 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.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <rpc/RPCHelpers.h>
|
||||
#include <rpc/common/MetaProcessors.h>
|
||||
|
||||
#include <boost/json/value.hpp>
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace RPC::meta {
|
||||
|
||||
[[nodiscard]] MaybeError
|
||||
Section::verify(boost::json::value& value, std::string_view key) const
|
||||
{
|
||||
if (not value.is_object() or not value.as_object().contains(key.data()))
|
||||
return {}; // ignore. field does not exist, let 'required' fail instead
|
||||
|
||||
auto& res = value.as_object().at(key.data());
|
||||
|
||||
// if it is not a json object, let other validators fail
|
||||
if (!res.is_object())
|
||||
return {};
|
||||
|
||||
for (auto const& spec : specs)
|
||||
{
|
||||
if (auto const ret = spec.process(res); not ret)
|
||||
return Error{ret.error()};
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
[[nodiscard]] MaybeError
|
||||
ValidateArrayAt::verify(boost::json::value& value, std::string_view key) const
|
||||
{
|
||||
if (not value.is_object() or not value.as_object().contains(key.data()))
|
||||
return {}; // ignore. field does not exist, let 'required' fail instead
|
||||
|
||||
if (not value.as_object().at(key.data()).is_array())
|
||||
return Error{Status{RippledError::rpcINVALID_PARAMS}};
|
||||
|
||||
auto& arr = value.as_object().at(key.data()).as_array();
|
||||
if (idx_ >= arr.size())
|
||||
return Error{Status{RippledError::rpcINVALID_PARAMS}};
|
||||
|
||||
auto& res = arr.at(idx_);
|
||||
for (auto const& spec : specs_)
|
||||
if (auto const ret = spec.process(res); not ret)
|
||||
return Error{ret.error()};
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace RPC::meta
|
||||
175
src/rpc/common/MetaProcessors.h
Normal file
175
src/rpc/common/MetaProcessors.h
Normal file
@@ -0,0 +1,175 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of clio: https://github.com/XRPLF/clio
|
||||
Copyright (c) 2023, 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/common/Concepts.h>
|
||||
#include <rpc/common/Specs.h>
|
||||
#include <rpc/common/Types.h>
|
||||
#include <rpc/common/Validators.h>
|
||||
|
||||
#include <fmt/core.h>
|
||||
|
||||
namespace RPC::meta {
|
||||
|
||||
/**
|
||||
* @brief A meta-processor that acts as a spec for a sub-object/section
|
||||
*/
|
||||
class Section final
|
||||
{
|
||||
std::vector<FieldSpec> specs;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Construct new section validator from a list of specs
|
||||
*
|
||||
* @param specs List of specs @ref FieldSpec
|
||||
*/
|
||||
explicit Section(std::initializer_list<FieldSpec> specs) : specs{specs}
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Verify that the JSON value representing the section is valid according to the given specs
|
||||
*
|
||||
* @param value The JSON value representing the outer object
|
||||
* @param key The key used to retrieve the section from the outer object
|
||||
*/
|
||||
[[nodiscard]] MaybeError
|
||||
verify(boost::json::value& value, std::string_view key) const;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A meta-processor that specifies a list of specs to run against the object at the given index in the array
|
||||
*/
|
||||
class ValidateArrayAt final
|
||||
{
|
||||
std::size_t idx_;
|
||||
std::vector<FieldSpec> specs_;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a processor that validates the specified element of a JSON array
|
||||
*
|
||||
* @param idx The index inside the array to validate
|
||||
* @param specs The specifications to validate against
|
||||
*/
|
||||
ValidateArrayAt(std::size_t idx, std::initializer_list<FieldSpec> specs) : idx_{idx}, specs_{specs}
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Verify that the JSON array element at given index is valid according the stored specs
|
||||
*
|
||||
* @param value The JSON value representing the outer object
|
||||
* @param key The key used to retrieve the array from the outer object
|
||||
*/
|
||||
[[nodiscard]] MaybeError
|
||||
verify(boost::json::value& value, std::string_view key) const;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A meta-processor that specifies a list of requirements to run against when the type matches the template
|
||||
* parameter
|
||||
*/
|
||||
template <typename Type>
|
||||
class IfType final
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a validator that validates the specs if the type matches
|
||||
* @param requirements The requirements to validate against
|
||||
*/
|
||||
template <Requirement... Requirements>
|
||||
IfType(Requirements&&... requirements)
|
||||
{
|
||||
processor_ = [... r = std::forward<Requirements>(requirements)](
|
||||
boost::json::value& j, std::string_view key) -> MaybeError {
|
||||
std::optional<Status> firstFailure = std::nullopt;
|
||||
|
||||
// the check logic is the same as fieldspec
|
||||
// clang-format off
|
||||
([&j, &key, &firstFailure, req = &r]() {
|
||||
if (firstFailure)
|
||||
return;
|
||||
|
||||
if (auto const res = req->verify(j, key); not res)
|
||||
firstFailure = res.error();
|
||||
}(), ...);
|
||||
// clang-format on
|
||||
|
||||
if (firstFailure)
|
||||
return Error{firstFailure.value()};
|
||||
|
||||
return {};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Verify that the element is valid
|
||||
* according the stored requirements when type matches
|
||||
*
|
||||
* @param value The JSON value representing the outer object
|
||||
* @param key The key used to retrieve the element from the outer object
|
||||
*/
|
||||
[[nodiscard]] MaybeError
|
||||
verify(boost::json::value& value, std::string_view key) const
|
||||
{
|
||||
if (not value.is_object() or not value.as_object().contains(key.data()))
|
||||
return {}; // ignore. field does not exist, let 'required' fail instead
|
||||
|
||||
if (not RPC::validation::checkType<Type>(value.as_object().at(key.data())))
|
||||
return {}; // ignore if type does not match
|
||||
|
||||
return processor_(value, key);
|
||||
}
|
||||
|
||||
private:
|
||||
std::function<MaybeError(boost::json::value&, std::string_view)> processor_;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A meta-processor that wraps another validator and produces a custom error in case the wrapped validator fails
|
||||
*/
|
||||
template <typename Requirement>
|
||||
class WithCustomError final
|
||||
{
|
||||
Requirement requirement;
|
||||
Status error;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a validator that calls the given validator `req` and returns a custom error `err` in case `req`
|
||||
* fails
|
||||
*/
|
||||
WithCustomError(Requirement req, Status err) : requirement{std::move(req)}, error{err}
|
||||
{
|
||||
}
|
||||
|
||||
[[nodiscard]] MaybeError
|
||||
verify(boost::json::value const& value, std::string_view key) const
|
||||
{
|
||||
if (auto const res = requirement.verify(value, key); not res)
|
||||
return Error{error};
|
||||
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace RPC::meta
|
||||
70
src/rpc/common/Modifiers.h
Normal file
70
src/rpc/common/Modifiers.h
Normal file
@@ -0,0 +1,70 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of clio: https://github.com/XRPLF/clio
|
||||
Copyright (c) 2023, 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/common/Concepts.h>
|
||||
#include <rpc/common/Specs.h>
|
||||
#include <rpc/common/Types.h>
|
||||
|
||||
namespace RPC::modifiers {
|
||||
|
||||
/**
|
||||
* @brief Clamp value between min and max.
|
||||
*/
|
||||
template <typename Type>
|
||||
class Clamp final
|
||||
{
|
||||
Type min_;
|
||||
Type max_;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Construct the modifier storing min and max values
|
||||
*
|
||||
* @param min
|
||||
* @param max
|
||||
*/
|
||||
explicit Clamp(Type min, Type max) : min_{min}, max_{max}
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Clamp the value to stored min and max values.
|
||||
*
|
||||
* @param value The JSON value representing the outer object
|
||||
* @param key The key used to retrieve the modified value from the outer object
|
||||
*/
|
||||
[[nodiscard]] MaybeError
|
||||
modify(boost::json::value& value, std::string_view key) const
|
||||
{
|
||||
using boost::json::value_to;
|
||||
|
||||
if (not value.is_object() or not value.as_object().contains(key.data()))
|
||||
return {}; // ignore. field does not exist, let 'required' fail instead
|
||||
|
||||
// clamp to min_ and max_
|
||||
auto const oldValue = value_to<Type>(value.as_object().at(key.data()));
|
||||
value.as_object()[key.data()] = std::clamp<Type>(oldValue, min_, max_);
|
||||
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace RPC::modifiers
|
||||
@@ -24,16 +24,16 @@
|
||||
namespace RPC {
|
||||
|
||||
[[nodiscard]] MaybeError
|
||||
FieldSpec::validate(boost::json::value const& value) const
|
||||
FieldSpec::process(boost::json::value& value) const
|
||||
{
|
||||
return validator_(value);
|
||||
return processor_(value);
|
||||
}
|
||||
|
||||
[[nodiscard]] MaybeError
|
||||
RpcSpec::validate(boost::json::value const& value) const
|
||||
RpcSpec::process(boost::json::value& value) const
|
||||
{
|
||||
for (auto const& field : fields_)
|
||||
if (auto ret = field.validate(value); not ret)
|
||||
if (auto ret = field.process(value); not ret)
|
||||
return Error{ret.error()};
|
||||
|
||||
return {};
|
||||
|
||||
@@ -34,30 +34,29 @@ namespace RPC {
|
||||
struct FieldSpec final
|
||||
{
|
||||
/**
|
||||
* @brief Construct a field specification out of a set of requirements
|
||||
* @brief Construct a field specification out of a set of processors
|
||||
*
|
||||
* @tparam Requirements The types of requirements @ref Requirement
|
||||
* @tparam Processors The types of processors @ref Processor
|
||||
* @param key The key in a JSON object that the field validates
|
||||
* @param requirements The requirements, each of them have to fulfil
|
||||
* the @ref Requirement concept
|
||||
* @param processors The processors, each of them have to fulfil the @ref Processor concept
|
||||
*/
|
||||
template <Requirement... Requirements>
|
||||
FieldSpec(std::string const& key, Requirements&&... requirements)
|
||||
: validator_{detail::makeFieldValidator<Requirements...>(key, std::forward<Requirements>(requirements)...)}
|
||||
template <Processor... Processors>
|
||||
FieldSpec(std::string const& key, Processors&&... processors)
|
||||
: processor_{detail::makeFieldProcessor<Processors...>(key, std::forward<Processors>(processors)...)}
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Validates the passed JSON value using the stored requirements
|
||||
* @brief Processos the passed JSON value using the stored processors
|
||||
*
|
||||
* @param value The JSON value to validate
|
||||
* @param value The JSON value to validate and/or modify
|
||||
* @return Nothing on success; @ref Status on error
|
||||
*/
|
||||
[[nodiscard]] MaybeError
|
||||
validate(boost::json::value const& value) const;
|
||||
process(boost::json::value& value) const;
|
||||
|
||||
private:
|
||||
std::function<MaybeError(boost::json::value const&)> validator_;
|
||||
std::function<MaybeError(boost::json::value&)> processor_;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -78,13 +77,13 @@ struct RpcSpec final
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Validates the passed JSON value using the stored field specs
|
||||
* @brief Processos the passed JSON value using the stored field specs
|
||||
*
|
||||
* @param value The JSON value to validate
|
||||
* @param value The JSON value to validate and/or modify
|
||||
* @return Nothing on success; @ref Status on error
|
||||
*/
|
||||
[[nodiscard]] MaybeError
|
||||
validate(boost::json::value const& value) const;
|
||||
process(boost::json::value& value) const;
|
||||
|
||||
private:
|
||||
std::vector<FieldSpec> fields_;
|
||||
|
||||
@@ -28,28 +28,6 @@
|
||||
|
||||
namespace RPC::validation {
|
||||
|
||||
[[nodiscard]] MaybeError
|
||||
Section::verify(boost::json::value const& value, std::string_view key) const
|
||||
{
|
||||
if (not value.is_object() or not value.as_object().contains(key.data()))
|
||||
return {}; // ignore. field does not exist, let 'required' fail
|
||||
// instead
|
||||
|
||||
auto const& res = value.at(key.data());
|
||||
|
||||
// if it is not a json object, let other validators fail
|
||||
if (!res.is_object())
|
||||
return {};
|
||||
|
||||
for (auto const& spec : specs)
|
||||
{
|
||||
if (auto const ret = spec.validate(res); not ret)
|
||||
return Error{ret.error()};
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
[[nodiscard]] MaybeError
|
||||
Required::verify(boost::json::value const& value, std::string_view key) const
|
||||
{
|
||||
@@ -59,34 +37,11 @@ Required::verify(boost::json::value const& value, std::string_view key) const
|
||||
return {};
|
||||
}
|
||||
|
||||
[[nodiscard]] MaybeError
|
||||
ValidateArrayAt::verify(boost::json::value const& value, std::string_view key) const
|
||||
{
|
||||
if (not value.is_object() or not value.as_object().contains(key.data()))
|
||||
return {}; // ignore. field does not exist, let 'required' fail
|
||||
// instead
|
||||
|
||||
if (not value.as_object().at(key.data()).is_array())
|
||||
return Error{Status{RippledError::rpcINVALID_PARAMS}};
|
||||
|
||||
auto const& arr = value.as_object().at(key.data()).as_array();
|
||||
if (idx_ >= arr.size())
|
||||
return Error{Status{RippledError::rpcINVALID_PARAMS}};
|
||||
|
||||
auto const& res = arr.at(idx_);
|
||||
for (auto const& spec : specs_)
|
||||
if (auto const ret = spec.validate(res); not ret)
|
||||
return Error{ret.error()};
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
[[nodiscard]] MaybeError
|
||||
CustomValidator::verify(boost::json::value const& value, std::string_view key) const
|
||||
{
|
||||
if (not value.is_object() or not value.as_object().contains(key.data()))
|
||||
return {}; // ignore. field does not exist, let 'required' fail
|
||||
// instead
|
||||
return {}; // ignore. field does not exist, let 'required' fail instead
|
||||
|
||||
return validator_(value.as_object().at(key.data()), key);
|
||||
}
|
||||
|
||||
@@ -72,34 +72,6 @@ template <typename Expected>
|
||||
return not hasError;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief A meta-validator that acts as a spec for a sub-object/section
|
||||
*/
|
||||
class Section final
|
||||
{
|
||||
std::vector<FieldSpec> specs;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Construct new section validator from a list of specs
|
||||
*
|
||||
* @param specs List of specs @ref FieldSpec
|
||||
*/
|
||||
explicit Section(std::initializer_list<FieldSpec> specs) : specs{specs}
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Verify that the JSON value representing the section is valid
|
||||
* according to the given specs
|
||||
*
|
||||
* @param value The JSON value representing the outer object
|
||||
* @param key The key used to retrieve the section from the outer object
|
||||
*/
|
||||
[[nodiscard]] MaybeError
|
||||
verify(boost::json::value const& value, std::string_view key) const;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A validator that simply requires a field to be present
|
||||
*/
|
||||
@@ -184,8 +156,7 @@ struct Type final
|
||||
verify(boost::json::value const& value, std::string_view key) const
|
||||
{
|
||||
if (not value.is_object() or not value.as_object().contains(key.data()))
|
||||
return {}; // ignore. field does not exist, let 'required' fail
|
||||
// instead
|
||||
return {}; // ignore. field does not exist, let 'required' fail instead
|
||||
|
||||
auto const& res = value.as_object().at(key.data());
|
||||
auto const convertible = (checkType<Types>(res) || ...);
|
||||
@@ -230,8 +201,7 @@ public:
|
||||
using boost::json::value_to;
|
||||
|
||||
if (not value.is_object() or not value.as_object().contains(key.data()))
|
||||
return {}; // ignore. field does not exist, let 'required' fail
|
||||
// instead
|
||||
return {}; // ignore. field does not exist, let 'required' fail instead
|
||||
|
||||
auto const res = value_to<Type>(value.as_object().at(key.data()));
|
||||
|
||||
@@ -275,8 +245,7 @@ public:
|
||||
using boost::json::value_to;
|
||||
|
||||
if (not value.is_object() or not value.as_object().contains(key.data()))
|
||||
return {}; // ignore. field does not exist, let 'required' fail
|
||||
// instead
|
||||
return {}; // ignore. field does not exist, let 'required' fail instead
|
||||
|
||||
auto const res = value_to<Type>(value.as_object().at(key.data()));
|
||||
if (res != original_)
|
||||
@@ -333,8 +302,7 @@ public:
|
||||
using boost::json::value_to;
|
||||
|
||||
if (not value.is_object() or not value.as_object().contains(key.data()))
|
||||
return {}; // ignore. field does not exist, let 'required' fail
|
||||
// instead
|
||||
return {}; // ignore. field does not exist, let 'required' fail instead
|
||||
|
||||
auto const res = value_to<Type>(value.as_object().at(key.data()));
|
||||
if (std::find(std::begin(options_), std::end(options_), res) == std::end(options_))
|
||||
@@ -350,129 +318,6 @@ public:
|
||||
*/
|
||||
OneOf(std::initializer_list<char const*>)->OneOf<std::string>;
|
||||
|
||||
/**
|
||||
* @brief A meta-validator that specifies a list of specs to run against the
|
||||
* object at the given index in the array
|
||||
*/
|
||||
class ValidateArrayAt final
|
||||
{
|
||||
std::size_t idx_;
|
||||
std::vector<FieldSpec> specs_;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a validator that validates the specified element of a
|
||||
* JSON array
|
||||
*
|
||||
* @param idx The index inside the array to validate
|
||||
* @param specs The specifications to validate against
|
||||
*/
|
||||
ValidateArrayAt(std::size_t idx, std::initializer_list<FieldSpec> specs) : idx_{idx}, specs_{specs}
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Verify that the JSON array element at given index is valid
|
||||
* according the stored specs
|
||||
*
|
||||
* @param value The JSON value representing the outer object
|
||||
* @param key The key used to retrieve the array from the outer object
|
||||
*/
|
||||
[[nodiscard]] MaybeError
|
||||
verify(boost::json::value const& value, std::string_view key) const;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A meta-validator that specifies a list of requirements to run against
|
||||
* when the type matches the template parameter
|
||||
*/
|
||||
template <typename Type>
|
||||
class IfType final
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a validator that validates the specs if the type
|
||||
* matches
|
||||
* @param requirements The requirements to validate against
|
||||
*/
|
||||
template <Requirement... Requirements>
|
||||
IfType(Requirements&&... requirements)
|
||||
{
|
||||
validator_ = [... r = std::forward<Requirements>(requirements)](
|
||||
boost::json::value const& j, std::string_view key) -> MaybeError {
|
||||
std::optional<Status> firstFailure = std::nullopt;
|
||||
|
||||
// the check logic is the same as fieldspec
|
||||
// clang-format off
|
||||
([&j, &key, &firstFailure, req = &r]() {
|
||||
if (firstFailure)
|
||||
return;
|
||||
|
||||
if (auto const res = req->verify(j, key); not res)
|
||||
firstFailure = res.error();
|
||||
}(), ...);
|
||||
// clang-format on
|
||||
|
||||
if (firstFailure)
|
||||
return Error{firstFailure.value()};
|
||||
|
||||
return {};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Verify that the element is valid
|
||||
* according the stored requirements when type matches
|
||||
*
|
||||
* @param value The JSON value representing the outer object
|
||||
* @param key The key used to retrieve the element from the outer object
|
||||
*/
|
||||
[[nodiscard]] MaybeError
|
||||
verify(boost::json::value const& value, std::string_view key) const
|
||||
{
|
||||
if (not value.is_object() or not value.as_object().contains(key.data()))
|
||||
return {}; // ignore. field does not exist, let 'required' fail
|
||||
// instead
|
||||
|
||||
if (not checkType<Type>(value.as_object().at(key.data())))
|
||||
return {}; // ignore if type does not match
|
||||
|
||||
return validator_(value, key);
|
||||
}
|
||||
|
||||
private:
|
||||
std::function<MaybeError(boost::json::value const&, std::string_view)> validator_;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A meta-validator that wrapp other validator to send the customized
|
||||
* error
|
||||
*/
|
||||
template <typename Requirement>
|
||||
class WithCustomError final
|
||||
{
|
||||
Requirement requirement;
|
||||
Status error;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a validator that calls the given validator "req" and
|
||||
* return customized error "err"
|
||||
*/
|
||||
WithCustomError(Requirement req, Status err) : requirement{std::move(req)}, error{err}
|
||||
{
|
||||
}
|
||||
|
||||
[[nodiscard]] MaybeError
|
||||
verify(boost::json::value const& value, std::string_view key) const
|
||||
{
|
||||
if (auto const res = requirement.verify(value, key); not res)
|
||||
return Error{error};
|
||||
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A meta-validator that allows to specify a custom validation function
|
||||
*/
|
||||
|
||||
@@ -28,23 +28,33 @@
|
||||
|
||||
namespace RPC::detail {
|
||||
|
||||
template <Requirement... Requirements>
|
||||
template <typename>
|
||||
static constexpr bool unsupported_v = false;
|
||||
|
||||
template <Processor... Processors>
|
||||
[[nodiscard]] auto
|
||||
makeFieldValidator(std::string const& key, Requirements&&... requirements)
|
||||
makeFieldProcessor(std::string const& key, Processors&&... procs)
|
||||
{
|
||||
return [key, ... r = std::forward<Requirements>(requirements)](boost::json::value const& j) -> MaybeError {
|
||||
return [key, ... proc = std::forward<Processors>(procs)](boost::json::value& j) -> MaybeError {
|
||||
std::optional<Status> firstFailure = std::nullopt;
|
||||
|
||||
// This expands in order of Requirements and stops evaluating after
|
||||
// first failure which is stored in `firstFailure` and can be checked
|
||||
// later on to see whether the verification failed as a whole or not.
|
||||
// This expands in order of Requirements and stops evaluating after first failure which is stored in
|
||||
// `firstFailure` and can be checked later on to see whether the verification failed as a whole or not.
|
||||
// clang-format off
|
||||
([&j, &key, &firstFailure, req = &r]() {
|
||||
([&j, &key, &firstFailure, req = &proc]() {
|
||||
if (firstFailure)
|
||||
return; // already failed earlier - skip
|
||||
|
||||
if constexpr (Requirement<decltype(*req)>) {
|
||||
if (auto const res = req->verify(j, key); not res)
|
||||
firstFailure = res.error();
|
||||
} else if constexpr (Modifier<decltype(*req)>) {
|
||||
if (auto const res = req->modify(j, key); not res)
|
||||
firstFailure = res.error();
|
||||
} else {
|
||||
static_assert(unsupported_v<decltype(*req)>);
|
||||
}
|
||||
|
||||
}(), ...);
|
||||
// clang-format on
|
||||
|
||||
|
||||
@@ -40,10 +40,12 @@ struct DefaultProcessor final
|
||||
{
|
||||
// first we run validation against specified API version
|
||||
auto const spec = handler.spec(ctx.apiVersion);
|
||||
if (auto const ret = spec.validate(value); not ret)
|
||||
auto input = value; // copy here, spec require mutable data
|
||||
|
||||
if (auto const ret = spec.process(input); not ret)
|
||||
return Error{ret.error()}; // forward Status
|
||||
|
||||
auto const inData = value_to<typename HandlerType::Input>(value);
|
||||
auto const inData = value_to<typename HandlerType::Input>(input);
|
||||
auto const ret = handler.process(inData, ctx);
|
||||
|
||||
// real handler is given expected Input, not json
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
#include <backend/BackendInterface.h>
|
||||
#include <rpc/RPCHelpers.h>
|
||||
#include <rpc/common/MetaProcessors.h>
|
||||
#include <rpc/common/Types.h>
|
||||
#include <rpc/common/Validators.h>
|
||||
|
||||
@@ -72,7 +73,7 @@ public:
|
||||
{JS(account), validation::Required{}, validation::AccountValidator},
|
||||
{JS(ledger_hash), validation::Uint256HexStringValidator},
|
||||
{JS(ledger_index), validation::LedgerIndexValidator},
|
||||
{JS(strict), validation::IfType<bool>{validation::NotSupported{false}}},
|
||||
{JS(strict), meta::IfType<bool>{validation::NotSupported{false}}},
|
||||
};
|
||||
|
||||
return rpcSpec;
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
#include <backend/BackendInterface.h>
|
||||
#include <rpc/RPCHelpers.h>
|
||||
#include <rpc/common/MetaProcessors.h>
|
||||
#include <rpc/common/Types.h>
|
||||
#include <rpc/common/Validators.h>
|
||||
|
||||
@@ -90,7 +91,7 @@ public:
|
||||
{JS(ledger_hash), validation::Uint256HexStringValidator},
|
||||
{JS(ledger_index), validation::LedgerIndexValidator},
|
||||
{JS(signer_lists), validation::Type<bool>{}},
|
||||
{JS(strict), validation::IfType<bool>{validation::NotSupported{false}}},
|
||||
{JS(strict), meta::IfType<bool>{validation::NotSupported{false}}},
|
||||
};
|
||||
|
||||
return rpcSpec;
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
#include <backend/BackendInterface.h>
|
||||
#include <rpc/RPCHelpers.h>
|
||||
#include <rpc/common/MetaProcessors.h>
|
||||
#include <rpc/common/Types.h>
|
||||
#include <rpc/common/Validators.h>
|
||||
|
||||
@@ -92,9 +93,8 @@ public:
|
||||
static auto const rpcSpec = RpcSpec{
|
||||
{JS(account),
|
||||
validation::Required{},
|
||||
validation::WithCustomError{validation::AccountValidator, Status(RippledError::rpcACT_MALFORMED)}},
|
||||
{JS(peer),
|
||||
validation::WithCustomError{validation::AccountValidator, Status(RippledError::rpcACT_MALFORMED)}},
|
||||
meta::WithCustomError{validation::AccountValidator, Status(RippledError::rpcACT_MALFORMED)}},
|
||||
{JS(peer), meta::WithCustomError{validation::AccountValidator, Status(RippledError::rpcACT_MALFORMED)}},
|
||||
{JS(ignore_default), validation::Type<bool>{}},
|
||||
{JS(ledger_hash), validation::Uint256HexStringValidator},
|
||||
{JS(limit), validation::Type<uint32_t>{}, validation::Between{10, 400}},
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
#include <backend/BackendInterface.h>
|
||||
#include <rpc/RPCHelpers.h>
|
||||
#include <rpc/common/MetaProcessors.h>
|
||||
#include <rpc/common/Types.h>
|
||||
#include <rpc/common/Validators.h>
|
||||
|
||||
@@ -83,7 +84,7 @@ public:
|
||||
{JS(ledger_index), validation::LedgerIndexValidator},
|
||||
{JS(marker), validation::AccountMarkerValidator},
|
||||
{JS(limit), validation::Type<uint32_t>{}, validation::Between{10, 400}},
|
||||
{JS(strict), validation::IfType<bool>{validation::NotSupported{false}}},
|
||||
{JS(strict), meta::IfType<bool>{validation::NotSupported{false}}},
|
||||
};
|
||||
|
||||
return rpcSpec;
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include <backend/BackendInterface.h>
|
||||
#include <log/Logger.h>
|
||||
#include <rpc/RPCHelpers.h>
|
||||
#include <rpc/common/MetaProcessors.h>
|
||||
#include <rpc/common/Types.h>
|
||||
#include <rpc/common/Validators.h>
|
||||
|
||||
@@ -92,11 +93,11 @@ public:
|
||||
{JS(forward), validation::Type<bool>{}},
|
||||
{JS(limit), validation::Type<uint32_t>{}, validation::Between{1, std::numeric_limits<int32_t>::max()}},
|
||||
{JS(marker),
|
||||
validation::WithCustomError{
|
||||
meta::WithCustomError{
|
||||
validation::Type<boost::json::object>{},
|
||||
Status{RippledError::rpcINVALID_PARAMS, "invalidMarker"},
|
||||
},
|
||||
validation::Section{
|
||||
meta::Section{
|
||||
{JS(ledger), validation::Required{}, validation::Type<uint32_t>{}},
|
||||
{JS(seq), validation::Required{}, validation::Type<uint32_t>{}},
|
||||
}},
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <backend/BackendInterface.h>
|
||||
#include <rpc/common/MetaProcessors.h>
|
||||
#include <rpc/common/Types.h>
|
||||
#include <rpc/common/Validators.h>
|
||||
|
||||
@@ -71,28 +72,24 @@ public:
|
||||
{JS(taker_gets),
|
||||
validation::Required{},
|
||||
validation::Type<boost::json::object>{},
|
||||
validation::Section{
|
||||
meta::Section{
|
||||
{JS(currency),
|
||||
validation::Required{},
|
||||
validation::WithCustomError{
|
||||
validation::CurrencyValidator, Status(RippledError::rpcDST_AMT_MALFORMED)}},
|
||||
meta::WithCustomError{validation::CurrencyValidator, Status(RippledError::rpcDST_AMT_MALFORMED)}},
|
||||
{JS(issuer),
|
||||
validation::WithCustomError{
|
||||
validation::IssuerValidator, Status(RippledError::rpcDST_ISR_MALFORMED)}}}},
|
||||
meta::WithCustomError{validation::IssuerValidator, Status(RippledError::rpcDST_ISR_MALFORMED)}}}},
|
||||
{JS(taker_pays),
|
||||
validation::Required{},
|
||||
validation::Type<boost::json::object>{},
|
||||
validation::Section{
|
||||
meta::Section{
|
||||
{JS(currency),
|
||||
validation::Required{},
|
||||
validation::WithCustomError{
|
||||
validation::CurrencyValidator, Status(RippledError::rpcSRC_CUR_MALFORMED)}},
|
||||
meta::WithCustomError{validation::CurrencyValidator, Status(RippledError::rpcSRC_CUR_MALFORMED)}},
|
||||
{JS(issuer),
|
||||
validation::WithCustomError{
|
||||
validation::IssuerValidator, Status(RippledError::rpcSRC_ISR_MALFORMED)}}}},
|
||||
meta::WithCustomError{validation::IssuerValidator, Status(RippledError::rpcSRC_ISR_MALFORMED)}}}},
|
||||
// return INVALID_PARAMS if account format is wrong for "taker"
|
||||
{JS(taker),
|
||||
validation::WithCustomError{
|
||||
meta::WithCustomError{
|
||||
validation::AccountValidator, Status(RippledError::rpcINVALID_PARAMS, "Invalid field 'taker'")}},
|
||||
{JS(limit), validation::Type<uint32_t>{}, validation::Between{1, 100}},
|
||||
{JS(ledger_hash), validation::Uint256HexStringValidator},
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
#include <backend/BackendInterface.h>
|
||||
#include <rpc/RPCHelpers.h>
|
||||
#include <rpc/common/MetaProcessors.h>
|
||||
#include <rpc/common/Types.h>
|
||||
#include <rpc/common/Validators.h>
|
||||
|
||||
@@ -106,7 +107,7 @@ public:
|
||||
{JS(ledger_hash), validation::Uint256HexStringValidator},
|
||||
{JS(ledger_index), validation::LedgerIndexValidator},
|
||||
{JS(hotwallet), hotWalletValidator},
|
||||
{JS(strict), validation::IfType<bool>{validation::NotSupported{false}}},
|
||||
{JS(strict), meta::IfType<bool>{validation::NotSupported{false}}},
|
||||
};
|
||||
|
||||
return rpcSpec;
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
#include <backend/BackendInterface.h>
|
||||
#include <rpc/RPCHelpers.h>
|
||||
#include <rpc/common/MetaProcessors.h>
|
||||
#include <rpc/common/Types.h>
|
||||
#include <rpc/common/Validators.h>
|
||||
|
||||
@@ -94,12 +95,12 @@ public:
|
||||
{JS(limit), validation::Type<uint32_t>{}},
|
||||
{JS(marker),
|
||||
validation::Type<uint32_t, std::string>{},
|
||||
validation::IfType<std::string>{validation::Uint256HexStringValidator}},
|
||||
meta::IfType<std::string>{validation::Uint256HexStringValidator}},
|
||||
{JS(type),
|
||||
validation::WithCustomError{
|
||||
meta::WithCustomError{
|
||||
validation::Type<std::string>{},
|
||||
Status{ripple::rpcINVALID_PARAMS, "Invalid field 'type', not string."}},
|
||||
validation::WithCustomError{
|
||||
meta::WithCustomError{
|
||||
validation::OneOf<std::string>(TYPES_KEYS.cbegin(), TYPES_KEYS.cend()),
|
||||
Status{ripple::rpcINVALID_PARAMS, "Invalid field 'type'."}}},
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
#include <backend/BackendInterface.h>
|
||||
#include <rpc/RPCHelpers.h>
|
||||
#include <rpc/common/MetaProcessors.h>
|
||||
#include <rpc/common/Types.h>
|
||||
#include <rpc/common/Validators.h>
|
||||
|
||||
@@ -98,10 +99,10 @@ public:
|
||||
}};
|
||||
|
||||
static auto const malformedRequestHexStringValidator =
|
||||
validation::WithCustomError{validation::Uint256HexStringValidator, Status(ClioError::rpcMALFORMED_REQUEST)};
|
||||
meta::WithCustomError{validation::Uint256HexStringValidator, Status(ClioError::rpcMALFORMED_REQUEST)};
|
||||
|
||||
static auto const malformedRequestIntValidator =
|
||||
validation::WithCustomError{validation::Type<uint32_t>{}, Status(ClioError::rpcMALFORMED_REQUEST)};
|
||||
meta::WithCustomError{validation::Type<uint32_t>{}, Status(ClioError::rpcMALFORMED_REQUEST)};
|
||||
|
||||
static auto const rpcSpec = RpcSpec{
|
||||
{JS(binary), validation::Type<bool>{}},
|
||||
@@ -112,40 +113,38 @@ public:
|
||||
{JS(check), malformedRequestHexStringValidator},
|
||||
{JS(deposit_preauth),
|
||||
validation::Type<std::string, boost::json::object>{},
|
||||
validation::IfType<std::string>{malformedRequestHexStringValidator},
|
||||
validation::IfType<boost::json::object>{
|
||||
validation::Section{
|
||||
meta::IfType<std::string>{malformedRequestHexStringValidator},
|
||||
meta::IfType<boost::json::object>{
|
||||
meta::Section{
|
||||
{JS(owner),
|
||||
validation::Required{},
|
||||
validation::WithCustomError{
|
||||
validation::AccountBase58Validator, Status(ClioError::rpcMALFORMED_OWNER)}},
|
||||
meta::WithCustomError{validation::AccountBase58Validator, Status(ClioError::rpcMALFORMED_OWNER)}},
|
||||
{JS(authorized), validation::Required{}, validation::AccountBase58Validator},
|
||||
},
|
||||
}},
|
||||
{JS(directory),
|
||||
validation::Type<std::string, boost::json::object>{},
|
||||
validation::IfType<std::string>{malformedRequestHexStringValidator},
|
||||
validation::IfType<boost::json::object>{validation::Section{
|
||||
meta::IfType<std::string>{malformedRequestHexStringValidator},
|
||||
meta::IfType<boost::json::object>{meta::Section{
|
||||
{JS(owner), validation::AccountBase58Validator},
|
||||
{JS(dir_root), validation::Uint256HexStringValidator},
|
||||
{JS(sub_index), malformedRequestIntValidator}}}},
|
||||
{JS(escrow),
|
||||
validation::Type<std::string, boost::json::object>{},
|
||||
validation::IfType<std::string>{malformedRequestHexStringValidator},
|
||||
validation::IfType<boost::json::object>{
|
||||
validation::Section{
|
||||
meta::IfType<std::string>{malformedRequestHexStringValidator},
|
||||
meta::IfType<boost::json::object>{
|
||||
meta::Section{
|
||||
{JS(owner),
|
||||
validation::Required{},
|
||||
validation::WithCustomError{
|
||||
validation::AccountBase58Validator, Status(ClioError::rpcMALFORMED_OWNER)}},
|
||||
meta::WithCustomError{validation::AccountBase58Validator, Status(ClioError::rpcMALFORMED_OWNER)}},
|
||||
{JS(seq), validation::Required{}, malformedRequestIntValidator},
|
||||
},
|
||||
}},
|
||||
{JS(offer),
|
||||
validation::Type<std::string, boost::json::object>{},
|
||||
validation::IfType<std::string>{malformedRequestHexStringValidator},
|
||||
validation::IfType<boost::json::object>{
|
||||
validation::Section{
|
||||
meta::IfType<std::string>{malformedRequestHexStringValidator},
|
||||
meta::IfType<boost::json::object>{
|
||||
meta::Section{
|
||||
{JS(account), validation::Required{}, validation::AccountBase58Validator},
|
||||
{JS(seq), validation::Required{}, malformedRequestIntValidator},
|
||||
},
|
||||
@@ -153,15 +152,15 @@ public:
|
||||
{JS(payment_channel), malformedRequestHexStringValidator},
|
||||
{JS(ripple_state),
|
||||
validation::Type<boost::json::object>{},
|
||||
validation::Section{
|
||||
meta::Section{
|
||||
{JS(accounts), validation::Required{}, rippleStateAccountsCheck},
|
||||
{JS(currency), validation::Required{}, validation::CurrencyValidator},
|
||||
}},
|
||||
{JS(ticket),
|
||||
validation::Type<std::string, boost::json::object>{},
|
||||
validation::IfType<std::string>{malformedRequestHexStringValidator},
|
||||
validation::IfType<boost::json::object>{
|
||||
validation::Section{
|
||||
meta::IfType<std::string>{malformedRequestHexStringValidator},
|
||||
meta::IfType<boost::json::object>{
|
||||
meta::Section{
|
||||
{JS(account), validation::Required{}, validation::AccountBase58Validator},
|
||||
{JS(ticket_seq), validation::Required{}, malformedRequestIntValidator},
|
||||
},
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include <backend/BackendInterface.h>
|
||||
#include <log/Logger.h>
|
||||
#include <rpc/RPCHelpers.h>
|
||||
#include <rpc/common/MetaProcessors.h>
|
||||
#include <rpc/common/Types.h>
|
||||
#include <rpc/common/Validators.h>
|
||||
|
||||
@@ -92,9 +93,9 @@ public:
|
||||
{JS(forward), validation::Type<bool>{}},
|
||||
{JS(limit), validation::Type<uint32_t>{}, validation::Between{1, 100}},
|
||||
{JS(marker),
|
||||
validation::WithCustomError{
|
||||
meta::WithCustomError{
|
||||
validation::Type<boost::json::object>{}, Status{RippledError::rpcINVALID_PARAMS, "invalidMarker"}},
|
||||
validation::Section{
|
||||
meta::Section{
|
||||
{JS(ledger), validation::Required{}, validation::Type<uint32_t>{}},
|
||||
{JS(seq), validation::Required{}, validation::Type<uint32_t>{}},
|
||||
}},
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
#include <backend/BackendInterface.h>
|
||||
#include <rpc/RPCHelpers.h>
|
||||
#include <rpc/common/MetaProcessors.h>
|
||||
#include <rpc/common/Types.h>
|
||||
#include <rpc/common/Validators.h>
|
||||
|
||||
@@ -73,7 +74,7 @@ public:
|
||||
{JS(account), validation::Required{}, validation::AccountValidator},
|
||||
{JS(role),
|
||||
validation::Required{},
|
||||
validation::WithCustomError{
|
||||
meta::WithCustomError{
|
||||
validation::OneOf{"gateway", "user"},
|
||||
Status{RippledError::rpcINVALID_PARAMS, "role field is invalid"}}},
|
||||
{JS(ledger_hash), validation::Uint256HexStringValidator},
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
|
||||
#include <rpc/Factories.h>
|
||||
#include <rpc/common/AnyHandler.h>
|
||||
#include <rpc/common/MetaProcessors.h>
|
||||
#include <rpc/common/Modifiers.h>
|
||||
#include <rpc/common/Specs.h>
|
||||
#include <rpc/common/Validators.h>
|
||||
|
||||
@@ -36,6 +38,8 @@ using namespace std;
|
||||
|
||||
using namespace RPC;
|
||||
using namespace RPC::validation;
|
||||
using namespace RPC::meta;
|
||||
using namespace RPC::modifiers;
|
||||
|
||||
namespace json = boost::json;
|
||||
|
||||
@@ -92,31 +96,31 @@ TEST_F(RPCBaseTest, TypeValidator)
|
||||
"bool": true,
|
||||
"arr": []
|
||||
})");
|
||||
ASSERT_TRUE(spec.validate(passingInput));
|
||||
ASSERT_TRUE(spec.process(passingInput));
|
||||
|
||||
{
|
||||
auto failingInput = json::parse(R"({ "uint": "a string" })");
|
||||
ASSERT_FALSE(spec.validate(failingInput));
|
||||
ASSERT_FALSE(spec.process(failingInput));
|
||||
}
|
||||
{
|
||||
auto failingInput = json::parse(R"({ "int": "a string" })");
|
||||
ASSERT_FALSE(spec.validate(failingInput));
|
||||
ASSERT_FALSE(spec.process(failingInput));
|
||||
}
|
||||
{
|
||||
auto failingInput = json::parse(R"({ "str": 1234 })");
|
||||
ASSERT_FALSE(spec.validate(failingInput));
|
||||
ASSERT_FALSE(spec.process(failingInput));
|
||||
}
|
||||
{
|
||||
auto failingInput = json::parse(R"({ "double": "a string" })");
|
||||
ASSERT_FALSE(spec.validate(failingInput));
|
||||
ASSERT_FALSE(spec.process(failingInput));
|
||||
}
|
||||
{
|
||||
auto failingInput = json::parse(R"({ "bool": "a string" })");
|
||||
ASSERT_FALSE(spec.validate(failingInput));
|
||||
ASSERT_FALSE(spec.process(failingInput));
|
||||
}
|
||||
{
|
||||
auto failingInput = json::parse(R"({ "arr": "a string" })");
|
||||
ASSERT_FALSE(spec.validate(failingInput));
|
||||
ASSERT_FALSE(spec.process(failingInput));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,13 +132,13 @@ TEST_F(RPCBaseTest, TypeValidatorMultipleTypes)
|
||||
};
|
||||
|
||||
auto passingInput = json::parse(R"({ "test": "1234" })");
|
||||
ASSERT_TRUE(spec.validate(passingInput));
|
||||
ASSERT_TRUE(spec.process(passingInput));
|
||||
|
||||
auto passingInput2 = json::parse(R"({ "test": 1234 })");
|
||||
ASSERT_TRUE(spec.validate(passingInput2));
|
||||
ASSERT_TRUE(spec.process(passingInput2));
|
||||
|
||||
auto failingInput = json::parse(R"({ "test": true })");
|
||||
ASSERT_FALSE(spec.validate(failingInput));
|
||||
ASSERT_FALSE(spec.process(failingInput));
|
||||
}
|
||||
|
||||
TEST_F(RPCBaseTest, RequiredValidator)
|
||||
@@ -144,13 +148,13 @@ TEST_F(RPCBaseTest, RequiredValidator)
|
||||
};
|
||||
|
||||
auto passingInput = json::parse(R"({ "required": "present" })");
|
||||
ASSERT_TRUE(spec.validate(passingInput));
|
||||
ASSERT_TRUE(spec.process(passingInput));
|
||||
|
||||
auto passingInput2 = json::parse(R"({ "required": true })");
|
||||
ASSERT_TRUE(spec.validate(passingInput2));
|
||||
ASSERT_TRUE(spec.process(passingInput2));
|
||||
|
||||
auto failingInput = json::parse(R"({})");
|
||||
ASSERT_FALSE(spec.validate(failingInput));
|
||||
ASSERT_FALSE(spec.process(failingInput));
|
||||
}
|
||||
|
||||
TEST_F(RPCBaseTest, BetweenValidator)
|
||||
@@ -160,19 +164,19 @@ TEST_F(RPCBaseTest, BetweenValidator)
|
||||
};
|
||||
|
||||
auto passingInput = json::parse(R"({ "amount": 15 })");
|
||||
ASSERT_TRUE(spec.validate(passingInput));
|
||||
ASSERT_TRUE(spec.process(passingInput));
|
||||
|
||||
auto passingInput2 = json::parse(R"({ "amount": 10 })");
|
||||
ASSERT_TRUE(spec.validate(passingInput2));
|
||||
ASSERT_TRUE(spec.process(passingInput2));
|
||||
|
||||
auto passingInput3 = json::parse(R"({ "amount": 20 })");
|
||||
ASSERT_TRUE(spec.validate(passingInput3));
|
||||
ASSERT_TRUE(spec.process(passingInput3));
|
||||
|
||||
auto failingInput = json::parse(R"({ "amount": 9 })");
|
||||
ASSERT_FALSE(spec.validate(failingInput));
|
||||
ASSERT_FALSE(spec.process(failingInput));
|
||||
|
||||
auto failingInput2 = json::parse(R"({ "amount": 21 })");
|
||||
ASSERT_FALSE(spec.validate(failingInput2));
|
||||
ASSERT_FALSE(spec.process(failingInput2));
|
||||
}
|
||||
|
||||
TEST_F(RPCBaseTest, OneOfValidator)
|
||||
@@ -182,13 +186,13 @@ TEST_F(RPCBaseTest, OneOfValidator)
|
||||
};
|
||||
|
||||
auto passingInput = json::parse(R"({ "currency": "XRP" })");
|
||||
ASSERT_TRUE(spec.validate(passingInput));
|
||||
ASSERT_TRUE(spec.process(passingInput));
|
||||
|
||||
auto passingInput2 = json::parse(R"({ "currency": "USD" })");
|
||||
ASSERT_TRUE(spec.validate(passingInput2));
|
||||
ASSERT_TRUE(spec.process(passingInput2));
|
||||
|
||||
auto failingInput = json::parse(R"({ "currency": "PRX" })");
|
||||
ASSERT_FALSE(spec.validate(failingInput));
|
||||
ASSERT_FALSE(spec.process(failingInput));
|
||||
}
|
||||
|
||||
TEST_F(RPCBaseTest, EqualToValidator)
|
||||
@@ -198,10 +202,10 @@ TEST_F(RPCBaseTest, EqualToValidator)
|
||||
};
|
||||
|
||||
auto passingInput = json::parse(R"({ "exact": "CaseSensitive" })");
|
||||
ASSERT_TRUE(spec.validate(passingInput));
|
||||
ASSERT_TRUE(spec.process(passingInput));
|
||||
|
||||
auto failingInput = json::parse(R"({ "exact": "Different" })");
|
||||
ASSERT_FALSE(spec.validate(failingInput));
|
||||
ASSERT_FALSE(spec.process(failingInput));
|
||||
}
|
||||
|
||||
TEST_F(RPCBaseTest, ArrayAtValidator)
|
||||
@@ -218,16 +222,16 @@ TEST_F(RPCBaseTest, ArrayAtValidator)
|
||||
// clang-format on
|
||||
|
||||
auto passingInput = json::parse(R"({ "arr": [{"limit": 42}] })");
|
||||
ASSERT_TRUE(spec.validate(passingInput));
|
||||
ASSERT_TRUE(spec.process(passingInput));
|
||||
|
||||
auto failingInput = json::parse(R"({ "arr": [{"limit": "not int"}] })");
|
||||
ASSERT_FALSE(spec.validate(failingInput));
|
||||
ASSERT_FALSE(spec.process(failingInput));
|
||||
|
||||
failingInput = json::parse(R"({ "arr": [{"limit": 42}] ,"arr2": "not array type" })");
|
||||
ASSERT_FALSE(spec.validate(failingInput));
|
||||
ASSERT_FALSE(spec.process(failingInput));
|
||||
|
||||
failingInput = json::parse(R"({ "arr": [] })");
|
||||
ASSERT_FALSE(spec.validate(failingInput));
|
||||
ASSERT_FALSE(spec.process(failingInput));
|
||||
}
|
||||
|
||||
TEST_F(RPCBaseTest, IfTypeValidator)
|
||||
@@ -253,28 +257,28 @@ TEST_F(RPCBaseTest, IfTypeValidator)
|
||||
|
||||
// if json object pass
|
||||
auto passingInput = json::parse(R"({ "mix": {"limit": 42, "limit2": 22} })");
|
||||
ASSERT_TRUE(spec.validate(passingInput));
|
||||
ASSERT_TRUE(spec.process(passingInput));
|
||||
// if string pass
|
||||
passingInput = json::parse(R"({ "mix": "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC" })");
|
||||
ASSERT_TRUE(spec.validate(passingInput));
|
||||
ASSERT_TRUE(spec.process(passingInput));
|
||||
|
||||
// if json object fail at first requirement
|
||||
auto failingInput = json::parse(R"({ "mix": {"limit": "not int"} })");
|
||||
ASSERT_FALSE(spec.validate(failingInput));
|
||||
ASSERT_FALSE(spec.process(failingInput));
|
||||
// if json object fail at second requirement
|
||||
failingInput = json::parse(R"({ "mix": {"limit": 22, "limit2": "y"} })");
|
||||
ASSERT_FALSE(spec.validate(failingInput));
|
||||
ASSERT_FALSE(spec.process(failingInput));
|
||||
|
||||
// if string fail
|
||||
failingInput = json::parse(R"({ "mix": "not hash" })");
|
||||
ASSERT_FALSE(spec.validate(failingInput));
|
||||
ASSERT_FALSE(spec.process(failingInput));
|
||||
|
||||
// type check fail
|
||||
failingInput = json::parse(R"({ "mix": 1213 })");
|
||||
ASSERT_FALSE(spec.validate(failingInput));
|
||||
ASSERT_FALSE(spec.process(failingInput));
|
||||
|
||||
failingInput = json::parse(R"({ "mix": {"limit": 42, "limit2": 22} , "mix2": 1213 })");
|
||||
ASSERT_FALSE(spec.validate(failingInput));
|
||||
ASSERT_FALSE(spec.process(failingInput));
|
||||
}
|
||||
|
||||
TEST_F(RPCBaseTest, WithCustomError)
|
||||
@@ -284,19 +288,19 @@ TEST_F(RPCBaseTest, WithCustomError)
|
||||
WithCustomError{Uint256HexStringValidator, RPC::Status{ripple::rpcBAD_FEATURE, "MyCustomError"}}},
|
||||
{"other", WithCustomError{Type<std::string>{}, RPC::Status{ripple::rpcALREADY_MULTISIG, "MyCustomError2"}}}};
|
||||
|
||||
auto const passingInput = json::parse(
|
||||
auto passingInput = json::parse(
|
||||
R"({ "transaction": "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC", "other": "1"})");
|
||||
ASSERT_TRUE(spec.validate(passingInput));
|
||||
ASSERT_TRUE(spec.process(passingInput));
|
||||
|
||||
auto failingInput =
|
||||
json::parse(R"({ "transaction": "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515B"})");
|
||||
auto err = spec.validate(failingInput);
|
||||
auto err = spec.process(failingInput);
|
||||
ASSERT_FALSE(err);
|
||||
ASSERT_EQ(err.error().message, "MyCustomError");
|
||||
ASSERT_EQ(err.error(), ripple::rpcBAD_FEATURE);
|
||||
|
||||
failingInput = json::parse(R"({ "other": 1})");
|
||||
err = spec.validate(failingInput);
|
||||
err = spec.process(failingInput);
|
||||
ASSERT_FALSE(err);
|
||||
ASSERT_EQ(err.error().message, "MyCustomError2");
|
||||
ASSERT_EQ(err.error(), ripple::rpcALREADY_MULTISIG);
|
||||
@@ -318,10 +322,10 @@ TEST_F(RPCBaseTest, CustomValidator)
|
||||
};
|
||||
|
||||
auto passingInput = json::parse(R"({ "taker": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59" })");
|
||||
ASSERT_TRUE(spec.validate(passingInput));
|
||||
ASSERT_TRUE(spec.process(passingInput));
|
||||
|
||||
auto failingInput = json::parse(R"({ "taker": "wrongformat" })");
|
||||
ASSERT_FALSE(spec.validate(failingInput));
|
||||
ASSERT_FALSE(spec.process(failingInput));
|
||||
}
|
||||
|
||||
TEST_F(RPCBaseTest, NotSupported)
|
||||
@@ -332,13 +336,13 @@ TEST_F(RPCBaseTest, NotSupported)
|
||||
};
|
||||
|
||||
auto passingInput = json::parse(R"({ "taker": 2 })");
|
||||
ASSERT_TRUE(spec.validate(passingInput));
|
||||
ASSERT_TRUE(spec.process(passingInput));
|
||||
|
||||
auto failingInput = json::parse(R"({ "taker": 123 })");
|
||||
ASSERT_FALSE(spec.validate(failingInput));
|
||||
ASSERT_FALSE(spec.process(failingInput));
|
||||
|
||||
failingInput = json::parse(R"({ "taker": 2, "getter": 2 })");
|
||||
ASSERT_FALSE(spec.validate(failingInput));
|
||||
ASSERT_FALSE(spec.process(failingInput));
|
||||
}
|
||||
|
||||
TEST_F(RPCBaseTest, LedgerIndexValidator)
|
||||
@@ -347,21 +351,21 @@ TEST_F(RPCBaseTest, LedgerIndexValidator)
|
||||
{"ledgerIndex", LedgerIndexValidator},
|
||||
};
|
||||
auto passingInput = json::parse(R"({ "ledgerIndex": "validated" })");
|
||||
ASSERT_TRUE(spec.validate(passingInput));
|
||||
ASSERT_TRUE(spec.process(passingInput));
|
||||
|
||||
passingInput = json::parse(R"({ "ledgerIndex": "256" })");
|
||||
ASSERT_TRUE(spec.validate(passingInput));
|
||||
ASSERT_TRUE(spec.process(passingInput));
|
||||
|
||||
passingInput = json::parse(R"({ "ledgerIndex": 256 })");
|
||||
ASSERT_TRUE(spec.validate(passingInput));
|
||||
ASSERT_TRUE(spec.process(passingInput));
|
||||
|
||||
auto failingInput = json::parse(R"({ "ledgerIndex": "wrongformat" })");
|
||||
auto err = spec.validate(failingInput);
|
||||
auto err = spec.process(failingInput);
|
||||
ASSERT_FALSE(err);
|
||||
ASSERT_EQ(err.error().message, "ledgerIndexMalformed");
|
||||
|
||||
failingInput = json::parse(R"({ "ledgerIndex": true })");
|
||||
err = spec.validate(failingInput);
|
||||
err = spec.process(failingInput);
|
||||
ASSERT_FALSE(err);
|
||||
ASSERT_EQ(err.error().message, "ledgerIndexMalformed");
|
||||
}
|
||||
@@ -372,20 +376,20 @@ TEST_F(RPCBaseTest, AccountValidator)
|
||||
{"account", AccountValidator},
|
||||
};
|
||||
auto failingInput = json::parse(R"({ "account": 256 })");
|
||||
ASSERT_FALSE(spec.validate(failingInput));
|
||||
ASSERT_FALSE(spec.process(failingInput));
|
||||
|
||||
failingInput = json::parse(R"({ "account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jp" })");
|
||||
ASSERT_FALSE(spec.validate(failingInput));
|
||||
ASSERT_FALSE(spec.process(failingInput));
|
||||
|
||||
failingInput = json::parse(R"({ "account": "02000000000000000000000000000000000000000000000000000000000000000" })");
|
||||
ASSERT_FALSE(spec.validate(failingInput));
|
||||
ASSERT_FALSE(spec.process(failingInput));
|
||||
|
||||
auto passingInput = json::parse(R"({ "account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn" })");
|
||||
ASSERT_TRUE(spec.validate(passingInput));
|
||||
ASSERT_TRUE(spec.process(passingInput));
|
||||
|
||||
passingInput =
|
||||
json::parse(R"({ "account": "020000000000000000000000000000000000000000000000000000000000000000" })");
|
||||
ASSERT_TRUE(spec.validate(passingInput));
|
||||
ASSERT_TRUE(spec.process(passingInput));
|
||||
}
|
||||
|
||||
TEST_F(RPCBaseTest, AccountMarkerValidator)
|
||||
@@ -394,32 +398,32 @@ TEST_F(RPCBaseTest, AccountMarkerValidator)
|
||||
{"marker", AccountMarkerValidator},
|
||||
};
|
||||
auto failingInput = json::parse(R"({ "marker": 256 })");
|
||||
ASSERT_FALSE(spec.validate(failingInput));
|
||||
ASSERT_FALSE(spec.process(failingInput));
|
||||
|
||||
failingInput = json::parse(R"({ "marker": "testtest" })");
|
||||
ASSERT_FALSE(spec.validate(failingInput));
|
||||
ASSERT_FALSE(spec.process(failingInput));
|
||||
|
||||
failingInput = json::parse(R"({ "marker": "ABAB1234:1H" })");
|
||||
ASSERT_FALSE(spec.validate(failingInput));
|
||||
ASSERT_FALSE(spec.process(failingInput));
|
||||
|
||||
auto passingInput = json::parse(R"({ "account": "ABAB1234:123" })");
|
||||
ASSERT_TRUE(spec.validate(passingInput));
|
||||
ASSERT_TRUE(spec.process(passingInput));
|
||||
}
|
||||
|
||||
TEST_F(RPCBaseTest, Uint256HexStringValidator)
|
||||
{
|
||||
auto const spec = RpcSpec{{"transaction", Uint256HexStringValidator}};
|
||||
auto const passingInput =
|
||||
auto passingInput =
|
||||
json::parse(R"({ "transaction": "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC"})");
|
||||
ASSERT_TRUE(spec.validate(passingInput));
|
||||
ASSERT_TRUE(spec.process(passingInput));
|
||||
|
||||
auto failingInput = json::parse(R"({ "transaction": 256})");
|
||||
auto err = spec.validate(failingInput);
|
||||
auto err = spec.process(failingInput);
|
||||
ASSERT_FALSE(err);
|
||||
ASSERT_EQ(err.error().message, "transactionNotString");
|
||||
|
||||
failingInput = json::parse(R"({ "transaction": "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC"})");
|
||||
err = spec.validate(failingInput);
|
||||
err = spec.process(failingInput);
|
||||
ASSERT_FALSE(err);
|
||||
ASSERT_EQ(err.error().message, "transactionMalformed");
|
||||
}
|
||||
@@ -428,18 +432,18 @@ TEST_F(RPCBaseTest, CurrencyValidator)
|
||||
{
|
||||
auto const spec = RpcSpec{{"currency", CurrencyValidator}};
|
||||
auto passingInput = json::parse(R"({ "currency": "GBP"})");
|
||||
ASSERT_TRUE(spec.validate(passingInput));
|
||||
ASSERT_TRUE(spec.process(passingInput));
|
||||
|
||||
passingInput = json::parse(R"({ "currency": "0158415500000000C1F76FF6ECB0BAC600000000"})");
|
||||
ASSERT_TRUE(spec.validate(passingInput));
|
||||
ASSERT_TRUE(spec.process(passingInput));
|
||||
|
||||
auto failingInput = json::parse(R"({ "currency": 256})");
|
||||
auto err = spec.validate(failingInput);
|
||||
auto err = spec.process(failingInput);
|
||||
ASSERT_FALSE(err);
|
||||
ASSERT_EQ(err.error().message, "currencyNotString");
|
||||
|
||||
failingInput = json::parse(R"({ "currency": "12314"})");
|
||||
err = spec.validate(failingInput);
|
||||
err = spec.process(failingInput);
|
||||
ASSERT_FALSE(err);
|
||||
ASSERT_EQ(err.error().message, "malformedCurrency");
|
||||
}
|
||||
@@ -448,15 +452,15 @@ TEST_F(RPCBaseTest, IssuerValidator)
|
||||
{
|
||||
auto const spec = RpcSpec{{"issuer", IssuerValidator}};
|
||||
auto passingInput = json::parse(R"({ "issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn"})");
|
||||
ASSERT_TRUE(spec.validate(passingInput));
|
||||
ASSERT_TRUE(spec.process(passingInput));
|
||||
|
||||
auto failingInput = json::parse(R"({ "issuer": 256})");
|
||||
auto err = spec.validate(failingInput);
|
||||
auto err = spec.process(failingInput);
|
||||
ASSERT_FALSE(err);
|
||||
ASSERT_EQ(err.error().message, "issuerNotString");
|
||||
|
||||
failingInput = json::parse(fmt::format(R"({{ "issuer": "{}"}})", toBase58(ripple::noAccount())));
|
||||
err = spec.validate(failingInput);
|
||||
err = spec.process(failingInput);
|
||||
ASSERT_FALSE(err);
|
||||
}
|
||||
|
||||
@@ -476,18 +480,18 @@ TEST_F(RPCBaseTest, SubscribeStreamValidator)
|
||||
"book_changes"
|
||||
]
|
||||
})");
|
||||
ASSERT_TRUE(spec.validate(passingInput));
|
||||
ASSERT_TRUE(spec.process(passingInput));
|
||||
|
||||
auto failingInput = json::parse(R"({ "streams": 256})");
|
||||
auto err = spec.validate(failingInput);
|
||||
auto err = spec.process(failingInput);
|
||||
ASSERT_FALSE(err);
|
||||
|
||||
failingInput = json::parse(R"({ "streams": ["test"]})");
|
||||
err = spec.validate(failingInput);
|
||||
err = spec.process(failingInput);
|
||||
ASSERT_FALSE(err);
|
||||
|
||||
failingInput = json::parse(R"({ "streams": [123]})");
|
||||
err = spec.validate(failingInput);
|
||||
err = spec.process(failingInput);
|
||||
ASSERT_FALSE(err);
|
||||
}
|
||||
|
||||
@@ -496,17 +500,35 @@ TEST_F(RPCBaseTest, SubscribeAccountsValidator)
|
||||
auto const spec = RpcSpec{{"accounts", SubscribeAccountsValidator}};
|
||||
auto passingInput =
|
||||
json::parse(R"({ "accounts": ["rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun"]})");
|
||||
ASSERT_TRUE(spec.validate(passingInput));
|
||||
ASSERT_TRUE(spec.process(passingInput));
|
||||
|
||||
auto failingInput = json::parse(R"({ "accounts": 256})");
|
||||
auto err = spec.validate(failingInput);
|
||||
auto err = spec.process(failingInput);
|
||||
ASSERT_FALSE(err);
|
||||
|
||||
failingInput = json::parse(R"({ "accounts": ["test"]})");
|
||||
err = spec.validate(failingInput);
|
||||
err = spec.process(failingInput);
|
||||
ASSERT_FALSE(err);
|
||||
|
||||
failingInput = json::parse(R"({ "accounts": [123]})");
|
||||
err = spec.validate(failingInput);
|
||||
err = spec.process(failingInput);
|
||||
ASSERT_FALSE(err);
|
||||
}
|
||||
|
||||
TEST_F(RPCBaseTest, ClampingModifier)
|
||||
{
|
||||
auto spec = RpcSpec{
|
||||
{"amount", Clamp<uint32_t>{10u, 20u}},
|
||||
};
|
||||
|
||||
auto passingInput = json::parse(R"({ "amount": 15 })");
|
||||
ASSERT_TRUE(spec.process(passingInput));
|
||||
|
||||
auto passingInput2 = json::parse(R"({ "amount": 5 })");
|
||||
ASSERT_TRUE(spec.process(passingInput2));
|
||||
ASSERT_EQ(passingInput2.at("amount").as_uint64(), 10u); // clamped
|
||||
|
||||
auto passingInput3 = json::parse(R"({ "amount": 25 })");
|
||||
ASSERT_TRUE(spec.process(passingInput3));
|
||||
ASSERT_EQ(passingInput3.at("amount").as_uint64(), 20u); // clamped
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user