mirror of
https://github.com/XRPLF/clio.git
synced 2025-11-20 19:56:00 +00:00
@@ -52,6 +52,9 @@ target_sources(clio PRIVATE
|
|||||||
src/rpc/RPCHelpers.cpp
|
src/rpc/RPCHelpers.cpp
|
||||||
src/rpc/Counters.cpp
|
src/rpc/Counters.cpp
|
||||||
src/rpc/WorkQueue.cpp
|
src/rpc/WorkQueue.cpp
|
||||||
|
## NextGen RPC
|
||||||
|
src/rpc/common/Specs.cpp
|
||||||
|
src/rpc/common/Validators.cpp
|
||||||
## RPC Methods
|
## RPC Methods
|
||||||
# Account
|
# Account
|
||||||
src/rpc/handlers/AccountChannels.cpp
|
src/rpc/handlers/AccountChannels.cpp
|
||||||
@@ -97,12 +100,16 @@ target_link_libraries(clio_server PUBLIC clio)
|
|||||||
if(BUILD_TESTS)
|
if(BUILD_TESTS)
|
||||||
set(TEST_TARGET clio_tests)
|
set(TEST_TARGET clio_tests)
|
||||||
add_executable(${TEST_TARGET}
|
add_executable(${TEST_TARGET}
|
||||||
unittests/RPCErrors.cpp
|
unittests/Playground.cpp
|
||||||
unittests/Backend.cpp
|
unittests/Backend.cpp
|
||||||
unittests/Logger.cpp
|
unittests/Logger.cpp
|
||||||
unittests/Config.cpp
|
unittests/Config.cpp
|
||||||
unittests/ProfilerTest.cpp
|
unittests/ProfilerTest.cpp
|
||||||
unittests/DOSGuard.cpp)
|
unittests/DOSGuard.cpp
|
||||||
|
unittests/rpc/ErrorTests.cpp
|
||||||
|
unittests/rpc/BaseTests.cpp
|
||||||
|
unittests/rpc/handlers/TestHandlerTests.cpp
|
||||||
|
unittests/rpc/handlers/DefaultProcessorTests.cpp)
|
||||||
include(CMake/deps/gtest.cmake)
|
include(CMake/deps/gtest.cmake)
|
||||||
|
|
||||||
# if CODE_COVERAGE enable, add clio_test-ccov
|
# if CODE_COVERAGE enable, add clio_test-ccov
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
|
|
||||||
#include <ripple/protocol/ErrorCodes.h>
|
#include <ripple/protocol/ErrorCodes.h>
|
||||||
|
|
||||||
#include <boost/json.hpp>
|
#include <boost/json/object.hpp>
|
||||||
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|||||||
103
src/rpc/common/AnyHandler.h
Normal file
103
src/rpc/common/AnyHandler.h
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/*
|
||||||
|
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/Types.h>
|
||||||
|
#include <rpc/common/impl/Processors.h>
|
||||||
|
|
||||||
|
namespace RPCng {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief A type-erased Handler that can contain any (NextGen) RPC handler class
|
||||||
|
*/
|
||||||
|
class AnyHandler final
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
template <
|
||||||
|
Handler HandlerType,
|
||||||
|
typename ProcessingStrategy = detail::DefaultProcessor<HandlerType>>
|
||||||
|
/* implicit */ AnyHandler(HandlerType&& handler)
|
||||||
|
: pimpl_{std::make_unique<Model<HandlerType, ProcessingStrategy>>(
|
||||||
|
std::forward<HandlerType>(handler))}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
~AnyHandler() = default;
|
||||||
|
AnyHandler(AnyHandler const& other) : pimpl_{other.pimpl_->clone()}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
AnyHandler&
|
||||||
|
operator=(AnyHandler const& rhs)
|
||||||
|
{
|
||||||
|
AnyHandler copy{rhs};
|
||||||
|
pimpl_.swap(copy.pimpl_);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
AnyHandler(AnyHandler&&) = default;
|
||||||
|
AnyHandler&
|
||||||
|
operator=(AnyHandler&&) = default;
|
||||||
|
|
||||||
|
[[nodiscard]] ReturnType
|
||||||
|
process(boost::json::value const& value) const
|
||||||
|
{
|
||||||
|
return pimpl_->process(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Concept
|
||||||
|
{
|
||||||
|
virtual ~Concept() = default;
|
||||||
|
|
||||||
|
[[nodiscard]] virtual ReturnType
|
||||||
|
process(boost::json::value const& value) const = 0;
|
||||||
|
|
||||||
|
[[nodiscard]] virtual std::unique_ptr<Concept>
|
||||||
|
clone() const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename HandlerType, typename ProcessorType>
|
||||||
|
struct Model : Concept
|
||||||
|
{
|
||||||
|
HandlerType handler;
|
||||||
|
ProcessorType processor;
|
||||||
|
|
||||||
|
Model(HandlerType&& handler) : handler{std::move(handler)}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] ReturnType
|
||||||
|
process(boost::json::value const& value) const override
|
||||||
|
{
|
||||||
|
return processor(handler, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::unique_ptr<Concept>
|
||||||
|
clone() const override
|
||||||
|
{
|
||||||
|
return std::make_unique<Model>(*this);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<Concept> pimpl_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace RPCng
|
||||||
60
src/rpc/common/Concepts.h
Normal file
60
src/rpc/common/Concepts.h
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/*
|
||||||
|
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/Types.h>
|
||||||
|
|
||||||
|
#include <boost/json/value_from.hpp>
|
||||||
|
#include <boost/json/value_to.hpp>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace RPCng {
|
||||||
|
|
||||||
|
struct RpcSpec;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief A concept that specifies what a requirement used with @ref FieldSpec
|
||||||
|
* must provide
|
||||||
|
*/
|
||||||
|
// clang-format off
|
||||||
|
template <typename T>
|
||||||
|
concept Requirement = requires(T a) {
|
||||||
|
{ a.verify(boost::json::value{}, std::string{}) } -> std::same_as<MaybeError>;
|
||||||
|
};
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief A concept that specifies what a Handler type must provide
|
||||||
|
*
|
||||||
|
* Note that value_from and value_to should be implemented using tag_invoke
|
||||||
|
* as per boost::json documentation for these functions.
|
||||||
|
*/
|
||||||
|
// clang-format off
|
||||||
|
template <typename T>
|
||||||
|
concept Handler = requires(T a, typename T::Input in, typename T::Output out) {
|
||||||
|
{ a.spec() } -> std::same_as<RpcSpecConstRef>;
|
||||||
|
{ a.process(in) } -> std::same_as<HandlerReturnType<decltype(out)>>;
|
||||||
|
}
|
||||||
|
&& boost::json::has_value_from<typename T::Output>::value
|
||||||
|
&& boost::json::has_value_to<typename T::Input>::value;
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
} // namespace RPCng
|
||||||
42
src/rpc/common/Specs.cpp
Normal file
42
src/rpc/common/Specs.cpp
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/*
|
||||||
|
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/common/Specs.h>
|
||||||
|
|
||||||
|
#include <boost/json/value.hpp>
|
||||||
|
|
||||||
|
namespace RPCng {
|
||||||
|
|
||||||
|
[[nodiscard]] MaybeError
|
||||||
|
FieldSpec::validate(boost::json::value const& value) const
|
||||||
|
{
|
||||||
|
return validator_(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] MaybeError
|
||||||
|
RpcSpec::validate(boost::json::value const& value) const
|
||||||
|
{
|
||||||
|
for (auto const& field : fields_)
|
||||||
|
if (auto ret = field.validate(value); not ret)
|
||||||
|
return Error{ret.error()};
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace RPCng
|
||||||
70
src/rpc/common/Specs.h
Normal file
70
src/rpc/common/Specs.h
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/*
|
||||||
|
This file is part of clio: https://github.com/XRPLF/clio
|
||||||
|
Copyright (c) 2023, the clio developers.
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
//==============================================================================
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <rpc/common/Concepts.h>
|
||||||
|
#include <rpc/common/Types.h>
|
||||||
|
#include <rpc/common/impl/Factories.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace RPCng {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Represents a Specification for one field of an RPC command
|
||||||
|
*/
|
||||||
|
struct FieldSpec final
|
||||||
|
{
|
||||||
|
template <Requirement... Requirements>
|
||||||
|
FieldSpec(std::string const& key, Requirements&&... requirements)
|
||||||
|
: validator_{detail::makeFieldValidator<Requirements...>(
|
||||||
|
key,
|
||||||
|
std::forward<Requirements>(requirements)...)}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] MaybeError
|
||||||
|
validate(boost::json::value const& value) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::function<MaybeError(boost::json::value const&)> validator_;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Represents a Specification of an entire RPC command
|
||||||
|
*
|
||||||
|
* Note: this should really be all constexpr and handlers would expose
|
||||||
|
* static constexpr RpcSpec spec instead. Maybe some day in the future.
|
||||||
|
*/
|
||||||
|
struct RpcSpec final
|
||||||
|
{
|
||||||
|
RpcSpec(std::initializer_list<FieldSpec> fields) : fields_{fields}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] MaybeError
|
||||||
|
validate(boost::json::value const& value) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<FieldSpec> fields_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace RPCng
|
||||||
56
src/rpc/common/Types.h
Normal file
56
src/rpc/common/Types.h
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/*
|
||||||
|
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/Errors.h>
|
||||||
|
#include <util/Expected.h>
|
||||||
|
|
||||||
|
#include <boost/json/value.hpp>
|
||||||
|
|
||||||
|
namespace RPCng {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Return type used for Validators that can return error but don't have
|
||||||
|
* specific value to return
|
||||||
|
*/
|
||||||
|
using MaybeError = util::Expected<void, RPC::Status>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The type that represents just the error part of @ref MaybeError
|
||||||
|
*/
|
||||||
|
using Error = util::Unexpected<RPC::Status>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Return type for each individual handler
|
||||||
|
*/
|
||||||
|
template <typename OutputType>
|
||||||
|
using HandlerReturnType = util::Expected<OutputType, RPC::Status>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The final return type out of RPC engine
|
||||||
|
*/
|
||||||
|
using ReturnType = util::Expected<boost::json::value, RPC::Status>;
|
||||||
|
|
||||||
|
struct RpcSpec;
|
||||||
|
struct FieldSpec;
|
||||||
|
|
||||||
|
using RpcSpecConstRef = RpcSpec const&;
|
||||||
|
|
||||||
|
} // namespace RPCng
|
||||||
89
src/rpc/common/Validators.cpp
Normal file
89
src/rpc/common/Validators.cpp
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/*
|
||||||
|
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/common/Validators.h>
|
||||||
|
|
||||||
|
#include <boost/json/value.hpp>
|
||||||
|
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
namespace RPCng::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());
|
||||||
|
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
|
||||||
|
{
|
||||||
|
if (not value.is_object() or not value.as_object().contains(key.data()))
|
||||||
|
return Error{RPC::Status{
|
||||||
|
RPC::RippledError::rpcINVALID_PARAMS,
|
||||||
|
"Required field '" + std::string{key} + "' missing"}};
|
||||||
|
|
||||||
|
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{RPC::Status{RPC::RippledError::rpcINVALID_PARAMS}};
|
||||||
|
|
||||||
|
auto const& arr = value.as_object().at(key.data()).as_array();
|
||||||
|
if (idx_ >= arr.size())
|
||||||
|
return Error{RPC::Status{RPC::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 validator_(value.as_object().at(key.data()), key);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace RPCng::validation
|
||||||
259
src/rpc/common/Validators.h
Normal file
259
src/rpc/common/Validators.h
Normal file
@@ -0,0 +1,259 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/*
|
||||||
|
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 RPCng::validation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check that the type is the same as what was expected
|
||||||
|
*
|
||||||
|
* @tparam Expected The expected type that value should be convertible to
|
||||||
|
* @param value The json value to check the type of
|
||||||
|
* @return true if convertible; false otherwise
|
||||||
|
*/
|
||||||
|
template <typename Expected>
|
||||||
|
[[nodiscard]] bool static checkType(boost::json::value const& value)
|
||||||
|
{
|
||||||
|
auto hasError = false;
|
||||||
|
if constexpr (std::is_same_v<Expected, bool>)
|
||||||
|
{
|
||||||
|
if (not value.is_bool())
|
||||||
|
hasError = true;
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<Expected, std::string>)
|
||||||
|
{
|
||||||
|
if (not value.is_string())
|
||||||
|
hasError = true;
|
||||||
|
}
|
||||||
|
else if constexpr (
|
||||||
|
std::is_same_v<Expected, double> or std::is_same_v<Expected, float>)
|
||||||
|
{
|
||||||
|
if (not value.is_double())
|
||||||
|
hasError = true;
|
||||||
|
}
|
||||||
|
else if constexpr (
|
||||||
|
std::is_convertible_v<Expected, uint64_t> or
|
||||||
|
std::is_convertible_v<Expected, int64_t>)
|
||||||
|
{
|
||||||
|
if (not value.is_int64() && not value.is_uint64())
|
||||||
|
hasError = true;
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<Expected, boost::json::array>)
|
||||||
|
{
|
||||||
|
if (not value.is_array())
|
||||||
|
hasError = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
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:
|
||||||
|
explicit Section(std::initializer_list<FieldSpec> specs) : specs{specs}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] MaybeError
|
||||||
|
verify(boost::json::value const& value, std::string_view key) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief A validator that simply requires a field to be present
|
||||||
|
*/
|
||||||
|
struct Required final
|
||||||
|
{
|
||||||
|
[[nodiscard]] MaybeError
|
||||||
|
verify(boost::json::value const& value, std::string_view key) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Validates that the type of the value is one of the given types
|
||||||
|
*/
|
||||||
|
template <typename... Types>
|
||||||
|
struct Type final
|
||||||
|
{
|
||||||
|
[[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
|
||||||
|
|
||||||
|
auto const& res = value.as_object().at(key.data());
|
||||||
|
auto const convertible = (checkType<Types>(res) || ...);
|
||||||
|
|
||||||
|
if (not convertible)
|
||||||
|
return Error{RPC::Status{RPC::RippledError::rpcINVALID_PARAMS}};
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Validate that value is between specified min and max
|
||||||
|
*/
|
||||||
|
template <typename Type>
|
||||||
|
class Between final
|
||||||
|
{
|
||||||
|
Type min_;
|
||||||
|
Type max_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit Between(Type min, Type max) : min_{min}, max_{max}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[[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
|
||||||
|
|
||||||
|
using boost::json::value_to;
|
||||||
|
auto const res = value_to<Type>(value.as_object().at(key.data()));
|
||||||
|
// todo: may want a way to make this code more generic (e.g. use a free
|
||||||
|
// function that can be overridden for this comparison)
|
||||||
|
if (res < min_ || res > max_)
|
||||||
|
return Error{RPC::Status{RPC::RippledError::rpcINVALID_PARAMS}};
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Validates that the value is equal to the one passed in
|
||||||
|
*/
|
||||||
|
template <typename Type>
|
||||||
|
class EqualTo final
|
||||||
|
{
|
||||||
|
Type original_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit EqualTo(Type original) : original_{original}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[[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
|
||||||
|
|
||||||
|
using boost::json::value_to;
|
||||||
|
auto const res = value_to<Type>(value.as_object().at(key.data()));
|
||||||
|
if (res != original_)
|
||||||
|
return Error{RPC::Status{RPC::RippledError::rpcINVALID_PARAMS}};
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Deduction guide to help disambiguate what it means to EqualTo a
|
||||||
|
* "string" without specifying the type.
|
||||||
|
*/
|
||||||
|
EqualTo(char const*)->EqualTo<std::string>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Validates that the value is one of the values passed in
|
||||||
|
*/
|
||||||
|
template <typename Type>
|
||||||
|
class OneOf final
|
||||||
|
{
|
||||||
|
std::vector<Type> options_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit OneOf(std::initializer_list<Type> options) : options_{options}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[[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
|
||||||
|
|
||||||
|
using boost::json::value_to;
|
||||||
|
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_))
|
||||||
|
return Error{RPC::Status{RPC::RippledError::rpcINVALID_PARAMS}};
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Deduction guide to help disambiguate what it means to OneOf a
|
||||||
|
* few "strings" without specifying the type.
|
||||||
|
*/
|
||||||
|
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:
|
||||||
|
ValidateArrayAt(std::size_t idx, std::initializer_list<FieldSpec> specs)
|
||||||
|
: idx_{idx}, specs_{specs}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] MaybeError
|
||||||
|
verify(boost::json::value const& value, std::string_view key) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief A meta-validator that allows to specify a custom validation function
|
||||||
|
*/
|
||||||
|
class CustomValidator final
|
||||||
|
{
|
||||||
|
std::function<MaybeError(boost::json::value const&, std::string_view)>
|
||||||
|
validator_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
template <typename Fn>
|
||||||
|
explicit CustomValidator(Fn&& fn) : validator_{std::forward<Fn>(fn)}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] MaybeError
|
||||||
|
verify(boost::json::value const& value, std::string_view key) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace RPCng::validation
|
||||||
59
src/rpc/common/impl/Factories.h
Normal file
59
src/rpc/common/impl/Factories.h
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/*
|
||||||
|
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/Types.h>
|
||||||
|
|
||||||
|
#include <boost/json/value.hpp>
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
namespace RPCng::detail {
|
||||||
|
|
||||||
|
template <Requirement... Requirements>
|
||||||
|
[[nodiscard]] auto
|
||||||
|
makeFieldValidator(std::string const& key, Requirements&&... requirements)
|
||||||
|
{
|
||||||
|
return [key, ... r = std::forward<Requirements>(requirements)](
|
||||||
|
boost::json::value const& j) -> MaybeError {
|
||||||
|
// clang-format off
|
||||||
|
std::optional<RPC::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.
|
||||||
|
([&j, &key, &firstFailure, req = &r]() {
|
||||||
|
if (firstFailure)
|
||||||
|
return; // already failed earlier - skip
|
||||||
|
|
||||||
|
if (auto const res = req->verify(j, key); not res)
|
||||||
|
firstFailure = res.error();
|
||||||
|
}(), ...);
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
if (firstFailure)
|
||||||
|
return Error{firstFailure.value()};
|
||||||
|
|
||||||
|
return {};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace RPCng::detail
|
||||||
52
src/rpc/common/impl/Processors.h
Normal file
52
src/rpc/common/impl/Processors.h
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/*
|
||||||
|
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/Types.h>
|
||||||
|
|
||||||
|
namespace RPCng::detail {
|
||||||
|
|
||||||
|
template <Handler HandlerType>
|
||||||
|
struct DefaultProcessor final
|
||||||
|
{
|
||||||
|
[[nodiscard]] ReturnType
|
||||||
|
operator()(HandlerType const& handler, boost::json::value const& value)
|
||||||
|
const
|
||||||
|
{
|
||||||
|
using boost::json::value_from;
|
||||||
|
using boost::json::value_to;
|
||||||
|
|
||||||
|
// first we run validation
|
||||||
|
auto const spec = handler.spec();
|
||||||
|
if (auto const ret = spec.validate(value); not ret)
|
||||||
|
return Error{ret.error()}; // forward Status
|
||||||
|
|
||||||
|
auto const inData = value_to<typename HandlerType::Input>(value);
|
||||||
|
|
||||||
|
// real handler is given expected Input, not json
|
||||||
|
if (auto const ret = handler.process(inData); not ret)
|
||||||
|
return Error{ret.error()}; // forward Status
|
||||||
|
else
|
||||||
|
return value_from(ret.value());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace RPCng::detail
|
||||||
264
src/util/Expected.h
Normal file
264
src/util/Expected.h
Normal file
@@ -0,0 +1,264 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/*
|
||||||
|
This file is part of clio: https://github.com/XRPLF/clio
|
||||||
|
Copyright (c) 2023, the clio developers.
|
||||||
|
Copyright (c) 2021 Ripple Labs Inc.
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
//==============================================================================
|
||||||
|
|
||||||
|
/*
|
||||||
|
* NOTE:
|
||||||
|
*
|
||||||
|
* This entire file is taken from rippled and modified slightly to fit this
|
||||||
|
* codebase as well as fixing the original issue that made this necessary.
|
||||||
|
*
|
||||||
|
* The reason is that currently there is no easy way to injest the fix that is
|
||||||
|
* required to make this implementation correctly work with boost::json::value.
|
||||||
|
* Since this will be replaced by `std::expected` as soon as possible there is
|
||||||
|
* not much harm done in doing it this way.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ripple/basics/contract.h>
|
||||||
|
#include <boost/outcome.hpp>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
namespace util {
|
||||||
|
|
||||||
|
/** Expected is an approximation of std::expected (hoped for in C++23)
|
||||||
|
|
||||||
|
See: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p0323r10.html
|
||||||
|
|
||||||
|
The implementation is entirely based on boost::outcome_v2::result.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Exception thrown by an invalid access to Expected.
|
||||||
|
struct bad_expected_access : public std::runtime_error
|
||||||
|
{
|
||||||
|
bad_expected_access() : runtime_error("bad expected access")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
// Custom policy for Expected. Always throw on an invalid access.
|
||||||
|
struct throw_policy : public boost::outcome_v2::policy::base
|
||||||
|
{
|
||||||
|
template <class Impl>
|
||||||
|
static constexpr void
|
||||||
|
wide_value_check(Impl&& self)
|
||||||
|
{
|
||||||
|
if (!base::_has_value(std::forward<Impl>(self)))
|
||||||
|
ripple::Throw<bad_expected_access>();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class Impl>
|
||||||
|
static constexpr void
|
||||||
|
wide_error_check(Impl&& self)
|
||||||
|
{
|
||||||
|
if (!base::_has_error(std::forward<Impl>(self)))
|
||||||
|
ripple::Throw<bad_expected_access>();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class Impl>
|
||||||
|
static constexpr void
|
||||||
|
wide_exception_check(Impl&& self)
|
||||||
|
{
|
||||||
|
if (!base::_has_exception(std::forward<Impl>(self)))
|
||||||
|
ripple::Throw<bad_expected_access>();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
// Definition of Unexpected, which is used to construct the unexpected
|
||||||
|
// return type of an Expected.
|
||||||
|
template <class E>
|
||||||
|
class Unexpected
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static_assert(!std::is_same<E, void>::value, "E must not be void");
|
||||||
|
|
||||||
|
Unexpected() = delete;
|
||||||
|
|
||||||
|
constexpr explicit Unexpected(E const& e) : val_(e)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr explicit Unexpected(E&& e) : val_(std::move(e))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr const E&
|
||||||
|
value() const&
|
||||||
|
{
|
||||||
|
return val_;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr E&
|
||||||
|
value() &
|
||||||
|
{
|
||||||
|
return val_;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr E&&
|
||||||
|
value() &&
|
||||||
|
{
|
||||||
|
return std::move(val_);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr const E&&
|
||||||
|
value() const&&
|
||||||
|
{
|
||||||
|
return std::move(val_);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
E val_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Unexpected deduction guide that converts array to const*.
|
||||||
|
template <typename E, std::size_t N>
|
||||||
|
Unexpected(E (&)[N]) -> Unexpected<E const*>;
|
||||||
|
|
||||||
|
// Definition of Expected. All of the machinery comes from boost::result.
|
||||||
|
template <class T, class E>
|
||||||
|
class [[nodiscard]] Expected
|
||||||
|
: private boost::outcome_v2::result<T, E, detail::throw_policy>
|
||||||
|
{
|
||||||
|
using Base = boost::outcome_v2::result<T, E, detail::throw_policy>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
template <
|
||||||
|
typename U,
|
||||||
|
typename = std::enable_if_t<std::is_convertible_v<U, T>>>
|
||||||
|
constexpr Expected(U r) : Base(T(std::forward<U>(r)))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
template <
|
||||||
|
typename U,
|
||||||
|
typename = std::enable_if_t<std::is_convertible_v<U, E>>>
|
||||||
|
constexpr Expected(Unexpected<U> e) : Base(E(std::forward<U>(e.value())))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool
|
||||||
|
has_value() const
|
||||||
|
{
|
||||||
|
return Base::has_value();
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr T const&
|
||||||
|
value() const
|
||||||
|
{
|
||||||
|
return Base::value();
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr T&
|
||||||
|
value()
|
||||||
|
{
|
||||||
|
return Base::value();
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr E const&
|
||||||
|
error() const
|
||||||
|
{
|
||||||
|
return Base::error();
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr E&
|
||||||
|
error()
|
||||||
|
{
|
||||||
|
return Base::error();
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr explicit operator bool() const
|
||||||
|
{
|
||||||
|
return has_value();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add operator* and operator-> so the Expected API looks a bit more like
|
||||||
|
// what std::expected is likely to look like. See:
|
||||||
|
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p0323r10.html
|
||||||
|
[[nodiscard]] constexpr T&
|
||||||
|
operator*()
|
||||||
|
{
|
||||||
|
return this->value();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr T const&
|
||||||
|
operator*() const
|
||||||
|
{
|
||||||
|
return this->value();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr T*
|
||||||
|
operator->()
|
||||||
|
{
|
||||||
|
return &this->value();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr T const*
|
||||||
|
operator->() const
|
||||||
|
{
|
||||||
|
return &this->value();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Specialization of Expected<void, E>. Allows returning either success
|
||||||
|
// (without a value) or the reason for the failure.
|
||||||
|
template <class E>
|
||||||
|
class [[nodiscard]] Expected<void, E>
|
||||||
|
: private boost::outcome_v2::result<void, E, detail::throw_policy>
|
||||||
|
{
|
||||||
|
using Base = boost::outcome_v2::result<void, E, detail::throw_policy>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
// The default constructor makes a successful Expected<void, E>.
|
||||||
|
// This aligns with std::expected behavior proposed in P0323R10.
|
||||||
|
constexpr Expected() : Base(boost::outcome_v2::success())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
template <
|
||||||
|
typename U,
|
||||||
|
typename = std::enable_if_t<std::is_convertible_v<U, E>>>
|
||||||
|
constexpr Expected(Unexpected<U> e) : Base(E(std::forward<U>(e.value())))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr E const&
|
||||||
|
error() const
|
||||||
|
{
|
||||||
|
return Base::error();
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr E&
|
||||||
|
error()
|
||||||
|
{
|
||||||
|
return Base::error();
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr explicit operator bool() const
|
||||||
|
{
|
||||||
|
return Base::has_value();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace util
|
||||||
31
unittests/Playground.cpp
Normal file
31
unittests/Playground.cpp
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/*
|
||||||
|
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 <util/Fixtures.h>
|
||||||
|
|
||||||
|
using namespace clio;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Use this file for temporary tests and implementations.
|
||||||
|
* Note: Please don't push your temporary work to the repo.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// TEST(PlaygroundTest, MyTest)
|
||||||
|
// {
|
||||||
|
// }
|
||||||
245
unittests/rpc/BaseTests.cpp
Normal file
245
unittests/rpc/BaseTests.cpp
Normal file
@@ -0,0 +1,245 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/*
|
||||||
|
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 <util/Fixtures.h>
|
||||||
|
|
||||||
|
#include <rpc/RPC.h>
|
||||||
|
#include <rpc/common/AnyHandler.h>
|
||||||
|
#include <rpc/common/Specs.h>
|
||||||
|
#include <rpc/common/Validators.h>
|
||||||
|
|
||||||
|
#include <boost/json/parse.hpp>
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
using namespace testing;
|
||||||
|
using namespace clio;
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
using namespace RPCng;
|
||||||
|
using namespace RPCng::validation;
|
||||||
|
|
||||||
|
namespace json = boost::json;
|
||||||
|
|
||||||
|
class RPCBaseTest : public NoLoggerFixture
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(RPCBaseTest, CheckType)
|
||||||
|
{
|
||||||
|
auto const jstr = json::value("a string");
|
||||||
|
ASSERT_TRUE(checkType<string>(jstr));
|
||||||
|
ASSERT_FALSE(checkType<int>(jstr));
|
||||||
|
|
||||||
|
auto const juint = json::value(123u);
|
||||||
|
ASSERT_TRUE(checkType<uint32_t>(juint));
|
||||||
|
ASSERT_TRUE(checkType<int32_t>(juint));
|
||||||
|
ASSERT_FALSE(checkType<bool>(juint));
|
||||||
|
|
||||||
|
auto const jint = json::value(123);
|
||||||
|
ASSERT_TRUE(checkType<int32_t>(jint));
|
||||||
|
ASSERT_TRUE(checkType<uint32_t>(jint));
|
||||||
|
ASSERT_FALSE(checkType<bool>(jint));
|
||||||
|
|
||||||
|
auto const jbool = json::value(true);
|
||||||
|
ASSERT_TRUE(checkType<bool>(jbool));
|
||||||
|
ASSERT_FALSE(checkType<int>(jbool));
|
||||||
|
|
||||||
|
auto const jdouble = json::value(0.123);
|
||||||
|
ASSERT_TRUE(checkType<double>(jdouble));
|
||||||
|
ASSERT_TRUE(checkType<float>(jdouble));
|
||||||
|
ASSERT_FALSE(checkType<bool>(jdouble));
|
||||||
|
|
||||||
|
auto const jarr = json::value({1, 2, 3});
|
||||||
|
ASSERT_TRUE(checkType<json::array>(jarr));
|
||||||
|
ASSERT_FALSE(checkType<int>(jarr));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(RPCBaseTest, TypeValidator)
|
||||||
|
{
|
||||||
|
auto spec = RpcSpec{
|
||||||
|
{"uint", Type<uint32_t>{}},
|
||||||
|
{"int", Type<int32_t>{}},
|
||||||
|
{"str", Type<string>{}},
|
||||||
|
{"double", Type<double>{}},
|
||||||
|
{"bool", Type<bool>{}},
|
||||||
|
{"arr", Type<json::array>{}},
|
||||||
|
};
|
||||||
|
|
||||||
|
auto passingInput = json::parse(R"({
|
||||||
|
"uint": 123,
|
||||||
|
"int": 321,
|
||||||
|
"str": "a string",
|
||||||
|
"double": 1.0,
|
||||||
|
"bool": true,
|
||||||
|
"arr": []
|
||||||
|
})");
|
||||||
|
ASSERT_TRUE(spec.validate(passingInput));
|
||||||
|
|
||||||
|
{
|
||||||
|
auto failingInput = json::parse(R"({ "uint": "a string" })");
|
||||||
|
ASSERT_FALSE(spec.validate(failingInput));
|
||||||
|
}
|
||||||
|
{
|
||||||
|
auto failingInput = json::parse(R"({ "int": "a string" })");
|
||||||
|
ASSERT_FALSE(spec.validate(failingInput));
|
||||||
|
}
|
||||||
|
{
|
||||||
|
auto failingInput = json::parse(R"({ "str": 1234 })");
|
||||||
|
ASSERT_FALSE(spec.validate(failingInput));
|
||||||
|
}
|
||||||
|
{
|
||||||
|
auto failingInput = json::parse(R"({ "double": "a string" })");
|
||||||
|
ASSERT_FALSE(spec.validate(failingInput));
|
||||||
|
}
|
||||||
|
{
|
||||||
|
auto failingInput = json::parse(R"({ "bool": "a string" })");
|
||||||
|
ASSERT_FALSE(spec.validate(failingInput));
|
||||||
|
}
|
||||||
|
{
|
||||||
|
auto failingInput = json::parse(R"({ "arr": "a string" })");
|
||||||
|
ASSERT_FALSE(spec.validate(failingInput));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(RPCBaseTest, TypeValidatorMultipleTypes)
|
||||||
|
{
|
||||||
|
auto spec = RpcSpec{
|
||||||
|
// either int or string
|
||||||
|
{"test", Type<uint32_t, string>{}},
|
||||||
|
};
|
||||||
|
|
||||||
|
auto passingInput = json::parse(R"({ "test": "1234" })");
|
||||||
|
ASSERT_TRUE(spec.validate(passingInput));
|
||||||
|
|
||||||
|
auto passingInput2 = json::parse(R"({ "test": 1234 })");
|
||||||
|
ASSERT_TRUE(spec.validate(passingInput2));
|
||||||
|
|
||||||
|
auto failingInput = json::parse(R"({ "test": true })");
|
||||||
|
ASSERT_FALSE(spec.validate(failingInput));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(RPCBaseTest, RequiredValidator)
|
||||||
|
{
|
||||||
|
auto spec = RpcSpec{
|
||||||
|
{"required", Required{}},
|
||||||
|
};
|
||||||
|
|
||||||
|
auto passingInput = json::parse(R"({ "required": "present" })");
|
||||||
|
ASSERT_TRUE(spec.validate(passingInput));
|
||||||
|
|
||||||
|
auto passingInput2 = json::parse(R"({ "required": true })");
|
||||||
|
ASSERT_TRUE(spec.validate(passingInput2));
|
||||||
|
|
||||||
|
auto failingInput = json::parse(R"({})");
|
||||||
|
ASSERT_FALSE(spec.validate(failingInput));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(RPCBaseTest, BetweenValidator)
|
||||||
|
{
|
||||||
|
auto spec = RpcSpec{
|
||||||
|
{"amount", Between<uint32_t>{10u, 20u}},
|
||||||
|
};
|
||||||
|
|
||||||
|
auto passingInput = json::parse(R"({ "amount": 15 })");
|
||||||
|
ASSERT_TRUE(spec.validate(passingInput));
|
||||||
|
|
||||||
|
auto passingInput2 = json::parse(R"({ "amount": 10 })");
|
||||||
|
ASSERT_TRUE(spec.validate(passingInput2));
|
||||||
|
|
||||||
|
auto passingInput3 = json::parse(R"({ "amount": 20 })");
|
||||||
|
ASSERT_TRUE(spec.validate(passingInput3));
|
||||||
|
|
||||||
|
auto failingInput = json::parse(R"({ "amount": 9 })");
|
||||||
|
ASSERT_FALSE(spec.validate(failingInput));
|
||||||
|
|
||||||
|
auto failingInput2 = json::parse(R"({ "amount": 21 })");
|
||||||
|
ASSERT_FALSE(spec.validate(failingInput2));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(RPCBaseTest, OneOfValidator)
|
||||||
|
{
|
||||||
|
auto spec = RpcSpec{
|
||||||
|
{"currency", OneOf{"XRP", "USD"}},
|
||||||
|
};
|
||||||
|
|
||||||
|
auto passingInput = json::parse(R"({ "currency": "XRP" })");
|
||||||
|
ASSERT_TRUE(spec.validate(passingInput));
|
||||||
|
|
||||||
|
auto passingInput2 = json::parse(R"({ "currency": "USD" })");
|
||||||
|
ASSERT_TRUE(spec.validate(passingInput2));
|
||||||
|
|
||||||
|
auto failingInput = json::parse(R"({ "currency": "PRX" })");
|
||||||
|
ASSERT_FALSE(spec.validate(failingInput));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(RPCBaseTest, EqualToValidator)
|
||||||
|
{
|
||||||
|
auto spec = RpcSpec{
|
||||||
|
{"exact", EqualTo{"CaseSensitive"}},
|
||||||
|
};
|
||||||
|
|
||||||
|
auto passingInput = json::parse(R"({ "exact": "CaseSensitive" })");
|
||||||
|
ASSERT_TRUE(spec.validate(passingInput));
|
||||||
|
|
||||||
|
auto failingInput = json::parse(R"({ "exact": "Different" })");
|
||||||
|
ASSERT_FALSE(spec.validate(failingInput));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(RPCBaseTest, ArrayAtValidator)
|
||||||
|
{
|
||||||
|
// clang-format off
|
||||||
|
auto spec = RpcSpec{
|
||||||
|
{"arr", Required{}, Type<json::array>{}, ValidateArrayAt{0, {
|
||||||
|
{"limit", Required{}, Type<uint32_t>{}, Between<uint32_t>{0, 100}},
|
||||||
|
}}},
|
||||||
|
};
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
auto passingInput = json::parse(R"({ "arr": [{"limit": 42}] })");
|
||||||
|
ASSERT_TRUE(spec.validate(passingInput));
|
||||||
|
|
||||||
|
auto failingInput = json::parse(R"({ "arr": [{"limit": "not int"}] })");
|
||||||
|
ASSERT_FALSE(spec.validate(failingInput));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(RPCBaseTest, CustomValidator)
|
||||||
|
{
|
||||||
|
// clang-format off
|
||||||
|
auto customFormatCheck = CustomValidator{
|
||||||
|
[](json::value const& value, std::string_view key) -> MaybeError {
|
||||||
|
return value.as_string().size() == 34 ?
|
||||||
|
MaybeError{} : Error{RPC::Status{"Uh oh"}};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
auto spec = RpcSpec{
|
||||||
|
{"taker", customFormatCheck},
|
||||||
|
};
|
||||||
|
|
||||||
|
auto passingInput =
|
||||||
|
json::parse(R"({ "taker": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59" })");
|
||||||
|
ASSERT_TRUE(spec.validate(passingInput));
|
||||||
|
|
||||||
|
auto failingInput = json::parse(R"({ "taker": "wrongformat" })");
|
||||||
|
ASSERT_FALSE(spec.validate(failingInput));
|
||||||
|
}
|
||||||
66
unittests/rpc/handlers/DefaultProcessorTests.cpp
Normal file
66
unittests/rpc/handlers/DefaultProcessorTests.cpp
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/*
|
||||||
|
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/handlers/impl/FakesAndMocks.h>
|
||||||
|
#include <util/Fixtures.h>
|
||||||
|
|
||||||
|
#include <rpc/common/impl/Processors.h>
|
||||||
|
|
||||||
|
#include <boost/json/parse.hpp>
|
||||||
|
|
||||||
|
using namespace testing;
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
using namespace RPCng;
|
||||||
|
using namespace RPCng::validation;
|
||||||
|
using namespace unittests::detail;
|
||||||
|
|
||||||
|
namespace json = boost::json;
|
||||||
|
|
||||||
|
class RPCDefaultProcessorTest : public NoLoggerFixture
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(RPCDefaultProcessorTest, ValidInput)
|
||||||
|
{
|
||||||
|
HandlerMock handler;
|
||||||
|
RPCng::detail::DefaultProcessor<HandlerMock> processor;
|
||||||
|
|
||||||
|
auto const input = json::parse(R"({ "something": "works" })");
|
||||||
|
auto const spec = RpcSpec{{"something", Required{}}};
|
||||||
|
auto const data = InOutFake{"works"};
|
||||||
|
EXPECT_CALL(handler, spec()).WillOnce(ReturnRef(spec));
|
||||||
|
EXPECT_CALL(handler, process(Eq(data))).WillOnce(Return(data));
|
||||||
|
|
||||||
|
auto const ret = processor(handler, input);
|
||||||
|
ASSERT_TRUE(ret); // no error
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(RPCDefaultProcessorTest, InvalidInput)
|
||||||
|
{
|
||||||
|
HandlerMock handler;
|
||||||
|
RPCng::detail::DefaultProcessor<HandlerMock> processor;
|
||||||
|
|
||||||
|
auto const input = json::parse(R"({ "other": "nope" })");
|
||||||
|
auto const spec = RpcSpec{{"something", Required{}}};
|
||||||
|
EXPECT_CALL(handler, spec()).WillOnce(ReturnRef(spec));
|
||||||
|
|
||||||
|
auto const ret = processor(handler, input);
|
||||||
|
ASSERT_FALSE(ret); // returns error
|
||||||
|
}
|
||||||
84
unittests/rpc/handlers/TestHandlerTests.cpp
Normal file
84
unittests/rpc/handlers/TestHandlerTests.cpp
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/*
|
||||||
|
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/common/AnyHandler.h>
|
||||||
|
#include <rpc/handlers/impl/FakesAndMocks.h>
|
||||||
|
#include <util/Fixtures.h>
|
||||||
|
|
||||||
|
#include <boost/json/parse.hpp>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
using namespace RPCng;
|
||||||
|
using namespace RPCng::validation;
|
||||||
|
using namespace unittests::detail;
|
||||||
|
|
||||||
|
namespace json = boost::json;
|
||||||
|
|
||||||
|
class RPCTestHandlerTest : public NoLoggerFixture
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
// example handler tests
|
||||||
|
TEST_F(RPCTestHandlerTest, HandlerSuccess)
|
||||||
|
{
|
||||||
|
auto const handler = AnyHandler{HandlerFake{}};
|
||||||
|
auto const input = json::parse(R"({
|
||||||
|
"hello": "world",
|
||||||
|
"limit": 10
|
||||||
|
})");
|
||||||
|
|
||||||
|
auto const output = handler.process(input);
|
||||||
|
ASSERT_TRUE(output);
|
||||||
|
|
||||||
|
auto const val = output.value();
|
||||||
|
EXPECT_EQ(val.as_object().at("computed").as_string(), "world_10");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(RPCTestHandlerTest, HandlerErrorHandling)
|
||||||
|
{
|
||||||
|
auto const handler = AnyHandler{HandlerFake{}};
|
||||||
|
auto const input = json::parse(R"({
|
||||||
|
"hello": "not world",
|
||||||
|
"limit": 10
|
||||||
|
})");
|
||||||
|
|
||||||
|
auto const output = handler.process(input);
|
||||||
|
ASSERT_FALSE(output);
|
||||||
|
|
||||||
|
auto const err = RPC::makeError(output.error());
|
||||||
|
EXPECT_EQ(err.at("error").as_string(), "invalidParams");
|
||||||
|
EXPECT_EQ(err.at("error_message").as_string(), "Invalid parameters.");
|
||||||
|
EXPECT_EQ(err.at("error_code").as_uint64(), 31);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(RPCTestHandlerTest, HandlerInnerErrorHandling)
|
||||||
|
{
|
||||||
|
auto const handler = AnyHandler{FailingHandlerFake{}};
|
||||||
|
auto const input = json::parse(R"({
|
||||||
|
"hello": "world",
|
||||||
|
"limit": 10
|
||||||
|
})");
|
||||||
|
|
||||||
|
// validation succeeds but handler itself returns error
|
||||||
|
auto const output = handler.process(input);
|
||||||
|
ASSERT_FALSE(output);
|
||||||
|
|
||||||
|
auto const err = RPC::makeError(output.error());
|
||||||
|
EXPECT_EQ(err.at("error").as_string(), "Very custom error");
|
||||||
|
}
|
||||||
168
unittests/rpc/handlers/impl/FakesAndMocks.h
Normal file
168
unittests/rpc/handlers/impl/FakesAndMocks.h
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/*
|
||||||
|
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/RPC.h>
|
||||||
|
#include <rpc/common/Specs.h>
|
||||||
|
#include <rpc/common/Validators.h>
|
||||||
|
|
||||||
|
#include <boost/json/value.hpp>
|
||||||
|
#include <boost/json/value_from.hpp>
|
||||||
|
#include <boost/json/value_to.hpp>
|
||||||
|
#include <gmock/gmock.h>
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace unittests::detail {
|
||||||
|
|
||||||
|
// input data for the test handlers below
|
||||||
|
struct TestInput
|
||||||
|
{
|
||||||
|
std::string hello;
|
||||||
|
std::optional<uint32_t> limit;
|
||||||
|
};
|
||||||
|
|
||||||
|
// output data produced by the test handlers below
|
||||||
|
struct TestOutput
|
||||||
|
{
|
||||||
|
std::string computed;
|
||||||
|
};
|
||||||
|
|
||||||
|
// must be implemented as per rpc/common/Concepts.h
|
||||||
|
inline TestInput
|
||||||
|
tag_invoke(boost::json::value_to_tag<TestInput>, boost::json::value const& jv)
|
||||||
|
{
|
||||||
|
std::optional<uint32_t> optLimit;
|
||||||
|
if (jv.as_object().contains("limit"))
|
||||||
|
optLimit = jv.at("limit").as_int64();
|
||||||
|
|
||||||
|
return {jv.as_object().at("hello").as_string().c_str(), optLimit};
|
||||||
|
}
|
||||||
|
|
||||||
|
// must be implemented as per rpc/common/Concepts.h
|
||||||
|
inline void
|
||||||
|
tag_invoke(
|
||||||
|
boost::json::value_from_tag,
|
||||||
|
boost::json::value& jv,
|
||||||
|
TestOutput output)
|
||||||
|
{
|
||||||
|
jv = {{"computed", output.computed}};
|
||||||
|
}
|
||||||
|
|
||||||
|
// example handler
|
||||||
|
class HandlerFake
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using Input = TestInput;
|
||||||
|
using Output = TestOutput;
|
||||||
|
using Result = RPCng::HandlerReturnType<Output>;
|
||||||
|
|
||||||
|
RPCng::RpcSpecConstRef
|
||||||
|
spec() const
|
||||||
|
{
|
||||||
|
using namespace RPCng::validation;
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
static const RPCng::RpcSpec rpcSpec = {
|
||||||
|
{"hello", Required{}, Type<std::string>{}, EqualTo{"world"}},
|
||||||
|
{"limit", Type<uint32_t>{}, Between<uint32_t>{0, 100}} // optional field
|
||||||
|
};
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
return rpcSpec;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result
|
||||||
|
process(Input input) const
|
||||||
|
{
|
||||||
|
return Output{
|
||||||
|
input.hello + '_' + std::to_string(input.limit.value_or(0))};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// example handler that returns custom error
|
||||||
|
class FailingHandlerFake
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using Input = TestInput;
|
||||||
|
using Output = TestOutput;
|
||||||
|
using Result = RPCng::HandlerReturnType<Output>;
|
||||||
|
|
||||||
|
RPCng::RpcSpecConstRef
|
||||||
|
spec() const
|
||||||
|
{
|
||||||
|
using namespace RPCng::validation;
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
static const RPCng::RpcSpec rpcSpec = {
|
||||||
|
{"hello", Required{}, Type<std::string>{}, EqualTo{"world"}},
|
||||||
|
{"limit", Type<uint32_t>{}, Between<uint32_t>{0u, 100u}} // optional field
|
||||||
|
};
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
return rpcSpec;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result
|
||||||
|
process([[maybe_unused]] Input input) const
|
||||||
|
{
|
||||||
|
// always fail
|
||||||
|
return RPCng::Error{RPC::Status{"Very custom error"}};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct InOutFake
|
||||||
|
{
|
||||||
|
std::string something;
|
||||||
|
|
||||||
|
// Note: no spaceship comparison possible for std::string
|
||||||
|
friend bool
|
||||||
|
operator==(InOutFake const& lhs, InOutFake const& rhs) = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
// must be implemented as per rpc/common/Concepts.h
|
||||||
|
inline InOutFake
|
||||||
|
tag_invoke(boost::json::value_to_tag<InOutFake>, boost::json::value const& jv)
|
||||||
|
{
|
||||||
|
return {jv.as_object().at("something").as_string().c_str()};
|
||||||
|
}
|
||||||
|
|
||||||
|
// must be implemented as per rpc/common/Concepts.h
|
||||||
|
inline void
|
||||||
|
tag_invoke(
|
||||||
|
boost::json::value_from_tag,
|
||||||
|
boost::json::value& jv,
|
||||||
|
InOutFake output)
|
||||||
|
{
|
||||||
|
jv = {{"something", output.something}};
|
||||||
|
}
|
||||||
|
|
||||||
|
struct HandlerMock
|
||||||
|
{
|
||||||
|
using Input = InOutFake;
|
||||||
|
using Output = InOutFake;
|
||||||
|
using Result = RPCng::HandlerReturnType<Output>;
|
||||||
|
|
||||||
|
MOCK_METHOD(RPCng::RpcSpecConstRef, spec, (), (const));
|
||||||
|
MOCK_METHOD(Result, process, (Input), (const));
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace unittests::detail
|
||||||
Reference in New Issue
Block a user