From ba8e7188ca148cbe75c15ec9cad1c07d6c22cc63 Mon Sep 17 00:00:00 2001 From: Alex Kremer Date: Mon, 10 Jul 2023 16:09:20 +0100 Subject: [PATCH] Implement the Clamp modifier (#740) Fixes #750 --- CMakeLists.txt | 1 + src/rpc/common/Concepts.h | 14 ++- src/rpc/common/MetaProcessors.cpp | 71 +++++++++++ src/rpc/common/MetaProcessors.h | 175 +++++++++++++++++++++++++++ src/rpc/common/Modifiers.h | 70 +++++++++++ src/rpc/common/Specs.cpp | 8 +- src/rpc/common/Specs.h | 27 ++--- src/rpc/common/Validators.cpp | 47 +------ src/rpc/common/Validators.h | 163 +------------------------ src/rpc/common/impl/Factories.h | 28 +++-- src/rpc/common/impl/Processors.h | 6 +- src/rpc/handlers/AccountCurrencies.h | 3 +- src/rpc/handlers/AccountInfo.h | 3 +- src/rpc/handlers/AccountLines.h | 6 +- src/rpc/handlers/AccountOffers.h | 3 +- src/rpc/handlers/AccountTx.h | 5 +- src/rpc/handlers/BookOffers.h | 19 ++- src/rpc/handlers/GatewayBalances.h | 3 +- src/rpc/handlers/LedgerData.h | 7 +- src/rpc/handlers/LedgerEntry.h | 41 +++---- src/rpc/handlers/NFTHistory.h | 5 +- src/rpc/handlers/NoRippleCheck.h | 3 +- unittests/rpc/BaseTests.cpp | 174 ++++++++++++++------------ 23 files changed, 523 insertions(+), 359 deletions(-) create mode 100644 src/rpc/common/MetaProcessors.cpp create mode 100644 src/rpc/common/MetaProcessors.h create mode 100644 src/rpc/common/Modifiers.h diff --git a/CMakeLists.txt b/CMakeLists.txt index c52f8815..3b3e4a04 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/src/rpc/common/Concepts.h b/src/rpc/common/Concepts.h index 15e97ddc..754f7f42 100644 --- a/src/rpc/common/Concepts.h +++ b/src/rpc/common/Concepts.h @@ -36,11 +36,21 @@ struct RpcSpec; */ // clang-format off template -concept Requirement = requires(T a) { - { a.verify(boost::json::value{}, std::string{}) } -> std::same_as; +concept Requirement = requires(T a, boost::json::value lval) { + { a.verify(lval, std::string{}) } -> std::same_as; }; // clang-format on +// clang-format off +template +concept Modifier = requires(T a, boost::json::value lval) { + { a.modify(lval, std::string{}) } -> std::same_as; +}; +// clang-format on + +template +concept Processor = (Requirement or Modifier); + /** * @brief A concept that specifies what a Handler type must provide * diff --git a/src/rpc/common/MetaProcessors.cpp b/src/rpc/common/MetaProcessors.cpp new file mode 100644 index 00000000..66813d71 --- /dev/null +++ b/src/rpc/common/MetaProcessors.cpp @@ -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 +#include + +#include + +#include + +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 diff --git a/src/rpc/common/MetaProcessors.h b/src/rpc/common/MetaProcessors.h new file mode 100644 index 00000000..76749fd0 --- /dev/null +++ b/src/rpc/common/MetaProcessors.h @@ -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 +#include +#include +#include + +#include + +namespace RPC::meta { + +/** + * @brief A meta-processor that acts as a spec for a sub-object/section + */ +class Section final +{ + std::vector specs; + +public: + /** + * @brief Construct new section validator from a list of specs + * + * @param specs List of specs @ref FieldSpec + */ + explicit Section(std::initializer_list 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 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 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 +class IfType final +{ +public: + /** + * @brief Constructs a validator that validates the specs if the type matches + * @param requirements The requirements to validate against + */ + template + IfType(Requirements&&... requirements) + { + processor_ = [... r = std::forward(requirements)]( + boost::json::value& j, std::string_view key) -> MaybeError { + std::optional 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(value.as_object().at(key.data()))) + return {}; // ignore if type does not match + + return processor_(value, key); + } + +private: + std::function processor_; +}; + +/** + * @brief A meta-processor that wraps another validator and produces a custom error in case the wrapped validator fails + */ +template +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 diff --git a/src/rpc/common/Modifiers.h b/src/rpc/common/Modifiers.h new file mode 100644 index 00000000..3971d411 --- /dev/null +++ b/src/rpc/common/Modifiers.h @@ -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 +#include +#include + +namespace RPC::modifiers { + +/** + * @brief Clamp value between min and max. + */ +template +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(value.as_object().at(key.data())); + value.as_object()[key.data()] = std::clamp(oldValue, min_, max_); + + return {}; + } +}; + +} // namespace RPC::modifiers diff --git a/src/rpc/common/Specs.cpp b/src/rpc/common/Specs.cpp index 9918eb84..2ef0c5cd 100644 --- a/src/rpc/common/Specs.cpp +++ b/src/rpc/common/Specs.cpp @@ -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 {}; diff --git a/src/rpc/common/Specs.h b/src/rpc/common/Specs.h index b3188f98..e49f46c0 100644 --- a/src/rpc/common/Specs.h +++ b/src/rpc/common/Specs.h @@ -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 - FieldSpec(std::string const& key, Requirements&&... requirements) - : validator_{detail::makeFieldValidator(key, std::forward(requirements)...)} + template + FieldSpec(std::string const& key, Processors&&... processors) + : processor_{detail::makeFieldProcessor(key, std::forward(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 validator_; + std::function 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 fields_; diff --git a/src/rpc/common/Validators.cpp b/src/rpc/common/Validators.cpp index c9ab2df2..ce19ea3c 100644 --- a/src/rpc/common/Validators.cpp +++ b/src/rpc/common/Validators.cpp @@ -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); } diff --git a/src/rpc/common/Validators.h b/src/rpc/common/Validators.h index 3525dd4b..060422f8 100644 --- a/src/rpc/common/Validators.h +++ b/src/rpc/common/Validators.h @@ -72,34 +72,6 @@ template return not hasError; } -/** - * @brief A meta-validator that acts as a spec for a sub-object/section - */ -class Section final -{ - std::vector specs; - -public: - /** - * @brief Construct new section validator from a list of specs - * - * @param specs List of specs @ref FieldSpec - */ - explicit Section(std::initializer_list 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(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(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(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(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)->OneOf; -/** - * @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 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 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 -class IfType final -{ -public: - /** - * @brief Constructs a validator that validates the specs if the type - * matches - * @param requirements The requirements to validate against - */ - template - IfType(Requirements&&... requirements) - { - validator_ = [... r = std::forward(requirements)]( - boost::json::value const& j, std::string_view key) -> MaybeError { - std::optional 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(value.as_object().at(key.data()))) - return {}; // ignore if type does not match - - return validator_(value, key); - } - -private: - std::function validator_; -}; - -/** - * @brief A meta-validator that wrapp other validator to send the customized - * error - */ -template -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 */ diff --git a/src/rpc/common/impl/Factories.h b/src/rpc/common/impl/Factories.h index adcbcfb5..e34a0e8f 100644 --- a/src/rpc/common/impl/Factories.h +++ b/src/rpc/common/impl/Factories.h @@ -28,23 +28,33 @@ namespace RPC::detail { -template +template +static constexpr bool unsupported_v = false; + +template [[nodiscard]] auto -makeFieldValidator(std::string const& key, Requirements&&... requirements) +makeFieldProcessor(std::string const& key, Processors&&... procs) { - return [key, ... r = std::forward(requirements)](boost::json::value const& j) -> MaybeError { + return [key, ... proc = std::forward(procs)](boost::json::value& j) -> MaybeError { std::optional 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 (auto const res = req->verify(j, key); not res) - firstFailure = res.error(); + if constexpr (Requirement) { + if (auto const res = req->verify(j, key); not res) + firstFailure = res.error(); + } else if constexpr (Modifier) { + if (auto const res = req->modify(j, key); not res) + firstFailure = res.error(); + } else { + static_assert(unsupported_v); + } + }(), ...); // clang-format on diff --git a/src/rpc/common/impl/Processors.h b/src/rpc/common/impl/Processors.h index 756c6edb..e4a9c146 100644 --- a/src/rpc/common/impl/Processors.h +++ b/src/rpc/common/impl/Processors.h @@ -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(value); + auto const inData = value_to(input); auto const ret = handler.process(inData, ctx); // real handler is given expected Input, not json diff --git a/src/rpc/handlers/AccountCurrencies.h b/src/rpc/handlers/AccountCurrencies.h index 83fddfe1..ca1f7ba4 100644 --- a/src/rpc/handlers/AccountCurrencies.h +++ b/src/rpc/handlers/AccountCurrencies.h @@ -21,6 +21,7 @@ #include #include +#include #include #include @@ -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{validation::NotSupported{false}}}, + {JS(strict), meta::IfType{validation::NotSupported{false}}}, }; return rpcSpec; diff --git a/src/rpc/handlers/AccountInfo.h b/src/rpc/handlers/AccountInfo.h index fb2078fe..ea1858fc 100644 --- a/src/rpc/handlers/AccountInfo.h +++ b/src/rpc/handlers/AccountInfo.h @@ -21,6 +21,7 @@ #include #include +#include #include #include @@ -90,7 +91,7 @@ public: {JS(ledger_hash), validation::Uint256HexStringValidator}, {JS(ledger_index), validation::LedgerIndexValidator}, {JS(signer_lists), validation::Type{}}, - {JS(strict), validation::IfType{validation::NotSupported{false}}}, + {JS(strict), meta::IfType{validation::NotSupported{false}}}, }; return rpcSpec; diff --git a/src/rpc/handlers/AccountLines.h b/src/rpc/handlers/AccountLines.h index ac25201e..01954f78 100644 --- a/src/rpc/handlers/AccountLines.h +++ b/src/rpc/handlers/AccountLines.h @@ -21,6 +21,7 @@ #include #include +#include #include #include @@ -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{}}, {JS(ledger_hash), validation::Uint256HexStringValidator}, {JS(limit), validation::Type{}, validation::Between{10, 400}}, diff --git a/src/rpc/handlers/AccountOffers.h b/src/rpc/handlers/AccountOffers.h index ee26ed8b..14ef9d82 100644 --- a/src/rpc/handlers/AccountOffers.h +++ b/src/rpc/handlers/AccountOffers.h @@ -21,6 +21,7 @@ #include #include +#include #include #include @@ -83,7 +84,7 @@ public: {JS(ledger_index), validation::LedgerIndexValidator}, {JS(marker), validation::AccountMarkerValidator}, {JS(limit), validation::Type{}, validation::Between{10, 400}}, - {JS(strict), validation::IfType{validation::NotSupported{false}}}, + {JS(strict), meta::IfType{validation::NotSupported{false}}}, }; return rpcSpec; diff --git a/src/rpc/handlers/AccountTx.h b/src/rpc/handlers/AccountTx.h index a467e4f7..e8d3229b 100644 --- a/src/rpc/handlers/AccountTx.h +++ b/src/rpc/handlers/AccountTx.h @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -92,11 +93,11 @@ public: {JS(forward), validation::Type{}}, {JS(limit), validation::Type{}, validation::Between{1, std::numeric_limits::max()}}, {JS(marker), - validation::WithCustomError{ + meta::WithCustomError{ validation::Type{}, Status{RippledError::rpcINVALID_PARAMS, "invalidMarker"}, }, - validation::Section{ + meta::Section{ {JS(ledger), validation::Required{}, validation::Type{}}, {JS(seq), validation::Required{}, validation::Type{}}, }}, diff --git a/src/rpc/handlers/BookOffers.h b/src/rpc/handlers/BookOffers.h index 97366d1d..85c731e8 100644 --- a/src/rpc/handlers/BookOffers.h +++ b/src/rpc/handlers/BookOffers.h @@ -20,6 +20,7 @@ #pragma once #include +#include #include #include @@ -71,28 +72,24 @@ public: {JS(taker_gets), validation::Required{}, validation::Type{}, - 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{}, - 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{}, validation::Between{1, 100}}, {JS(ledger_hash), validation::Uint256HexStringValidator}, diff --git a/src/rpc/handlers/GatewayBalances.h b/src/rpc/handlers/GatewayBalances.h index 83b7096e..ddbc6ab6 100644 --- a/src/rpc/handlers/GatewayBalances.h +++ b/src/rpc/handlers/GatewayBalances.h @@ -21,6 +21,7 @@ #include #include +#include #include #include @@ -106,7 +107,7 @@ public: {JS(ledger_hash), validation::Uint256HexStringValidator}, {JS(ledger_index), validation::LedgerIndexValidator}, {JS(hotwallet), hotWalletValidator}, - {JS(strict), validation::IfType{validation::NotSupported{false}}}, + {JS(strict), meta::IfType{validation::NotSupported{false}}}, }; return rpcSpec; diff --git a/src/rpc/handlers/LedgerData.h b/src/rpc/handlers/LedgerData.h index 331bf734..390d52b9 100644 --- a/src/rpc/handlers/LedgerData.h +++ b/src/rpc/handlers/LedgerData.h @@ -21,6 +21,7 @@ #include #include +#include #include #include @@ -94,12 +95,12 @@ public: {JS(limit), validation::Type{}}, {JS(marker), validation::Type{}, - validation::IfType{validation::Uint256HexStringValidator}}, + meta::IfType{validation::Uint256HexStringValidator}}, {JS(type), - validation::WithCustomError{ + meta::WithCustomError{ validation::Type{}, Status{ripple::rpcINVALID_PARAMS, "Invalid field 'type', not string."}}, - validation::WithCustomError{ + meta::WithCustomError{ validation::OneOf(TYPES_KEYS.cbegin(), TYPES_KEYS.cend()), Status{ripple::rpcINVALID_PARAMS, "Invalid field 'type'."}}}, diff --git a/src/rpc/handlers/LedgerEntry.h b/src/rpc/handlers/LedgerEntry.h index 87a18054..3be53fbb 100644 --- a/src/rpc/handlers/LedgerEntry.h +++ b/src/rpc/handlers/LedgerEntry.h @@ -21,6 +21,7 @@ #include #include +#include #include #include @@ -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{}, Status(ClioError::rpcMALFORMED_REQUEST)}; + meta::WithCustomError{validation::Type{}, Status(ClioError::rpcMALFORMED_REQUEST)}; static auto const rpcSpec = RpcSpec{ {JS(binary), validation::Type{}}, @@ -112,40 +113,38 @@ public: {JS(check), malformedRequestHexStringValidator}, {JS(deposit_preauth), validation::Type{}, - validation::IfType{malformedRequestHexStringValidator}, - validation::IfType{ - validation::Section{ + meta::IfType{malformedRequestHexStringValidator}, + meta::IfType{ + 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{}, - validation::IfType{malformedRequestHexStringValidator}, - validation::IfType{validation::Section{ + meta::IfType{malformedRequestHexStringValidator}, + meta::IfType{meta::Section{ {JS(owner), validation::AccountBase58Validator}, {JS(dir_root), validation::Uint256HexStringValidator}, {JS(sub_index), malformedRequestIntValidator}}}}, {JS(escrow), validation::Type{}, - validation::IfType{malformedRequestHexStringValidator}, - validation::IfType{ - validation::Section{ + meta::IfType{malformedRequestHexStringValidator}, + meta::IfType{ + 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{}, - validation::IfType{malformedRequestHexStringValidator}, - validation::IfType{ - validation::Section{ + meta::IfType{malformedRequestHexStringValidator}, + meta::IfType{ + 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{}, - validation::Section{ + meta::Section{ {JS(accounts), validation::Required{}, rippleStateAccountsCheck}, {JS(currency), validation::Required{}, validation::CurrencyValidator}, }}, {JS(ticket), validation::Type{}, - validation::IfType{malformedRequestHexStringValidator}, - validation::IfType{ - validation::Section{ + meta::IfType{malformedRequestHexStringValidator}, + meta::IfType{ + meta::Section{ {JS(account), validation::Required{}, validation::AccountBase58Validator}, {JS(ticket_seq), validation::Required{}, malformedRequestIntValidator}, }, diff --git a/src/rpc/handlers/NFTHistory.h b/src/rpc/handlers/NFTHistory.h index 7a1514ba..5e25330c 100644 --- a/src/rpc/handlers/NFTHistory.h +++ b/src/rpc/handlers/NFTHistory.h @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -92,9 +93,9 @@ public: {JS(forward), validation::Type{}}, {JS(limit), validation::Type{}, validation::Between{1, 100}}, {JS(marker), - validation::WithCustomError{ + meta::WithCustomError{ validation::Type{}, Status{RippledError::rpcINVALID_PARAMS, "invalidMarker"}}, - validation::Section{ + meta::Section{ {JS(ledger), validation::Required{}, validation::Type{}}, {JS(seq), validation::Required{}, validation::Type{}}, }}, diff --git a/src/rpc/handlers/NoRippleCheck.h b/src/rpc/handlers/NoRippleCheck.h index ee282fbf..ef953599 100644 --- a/src/rpc/handlers/NoRippleCheck.h +++ b/src/rpc/handlers/NoRippleCheck.h @@ -21,6 +21,7 @@ #include #include +#include #include #include @@ -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}, diff --git a/unittests/rpc/BaseTests.cpp b/unittests/rpc/BaseTests.cpp index a97b65a1..1d724353 100644 --- a/unittests/rpc/BaseTests.cpp +++ b/unittests/rpc/BaseTests.cpp @@ -21,6 +21,8 @@ #include #include +#include +#include #include #include @@ -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{}, 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{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 +}