Implement the Clamp modifier (#740)

Fixes #750
This commit is contained in:
Alex Kremer
2023-07-10 16:09:20 +01:00
committed by GitHub
parent 271323b0f4
commit ba8e7188ca
23 changed files with 523 additions and 359 deletions

View File

@@ -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

View File

@@ -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
*

View 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

View 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

View 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

View File

@@ -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 {};

View File

@@ -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_;

View File

@@ -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);
}

View File

@@ -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
*/

View File

@@ -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

View File

@@ -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

View File

@@ -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;

View File

@@ -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;

View File

@@ -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}},

View File

@@ -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;

View File

@@ -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>{}},
}},

View File

@@ -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},

View File

@@ -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;

View File

@@ -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'."}}},

View File

@@ -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},
},

View File

@@ -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>{}},
}},

View File

@@ -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},

View File

@@ -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
}