mirror of
https://github.com/XRPLF/clio.git
synced 2025-11-20 19:56:00 +00:00
@@ -80,13 +80,30 @@ public:
|
||||
return pimpl_->process(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Process incoming JSON by the stored handler in a provided
|
||||
* coroutine
|
||||
*
|
||||
* @param value The JSON to process
|
||||
* @return JSON result or @ref RPC::Status on error
|
||||
*/
|
||||
[[nodiscard]] ReturnType
|
||||
process(
|
||||
boost::json::value const& value,
|
||||
boost::asio::yield_context& ptrYield) const
|
||||
{
|
||||
return pimpl_->process(value, &ptrYield);
|
||||
}
|
||||
|
||||
private:
|
||||
struct Concept
|
||||
{
|
||||
virtual ~Concept() = default;
|
||||
|
||||
[[nodiscard]] virtual ReturnType
|
||||
process(boost::json::value const& value) const = 0;
|
||||
process(
|
||||
boost::json::value const& value,
|
||||
boost::asio::yield_context* ptrYield = nullptr) const = 0;
|
||||
|
||||
[[nodiscard]] virtual std::unique_ptr<Concept>
|
||||
clone() const = 0;
|
||||
@@ -103,9 +120,11 @@ private:
|
||||
}
|
||||
|
||||
[[nodiscard]] ReturnType
|
||||
process(boost::json::value const& value) const override
|
||||
process(
|
||||
boost::json::value const& value,
|
||||
boost::asio::yield_context* ptrYield = nullptr) const override
|
||||
{
|
||||
return processor(handler, value);
|
||||
return processor(handler, value, ptrYield);
|
||||
}
|
||||
|
||||
[[nodiscard]] std::unique_ptr<Concept>
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
#include <rpc/common/Types.h>
|
||||
|
||||
#include <boost/asio/spawn.hpp>
|
||||
#include <boost/json/value_from.hpp>
|
||||
#include <boost/json/value_to.hpp>
|
||||
|
||||
@@ -48,22 +49,30 @@ concept Requirement = requires(T a) {
|
||||
* as per boost::json documentation for these functions.
|
||||
*/
|
||||
// clang-format off
|
||||
template <typename T>
|
||||
concept CoroutineProcess = requires(T a, typename T::Input in, typename T::Output out, boost::asio::yield_context* y) {
|
||||
{ a.process(in, y) } -> std::same_as<HandlerReturnType<decltype(out)>>; };
|
||||
|
||||
template <typename T>
|
||||
concept NonCoroutineProcess = requires(T a, typename T::Input in, typename T::Output out) {
|
||||
{ a.process(in) } -> std::same_as<HandlerReturnType<decltype(out)>>; };
|
||||
|
||||
template <typename T>
|
||||
concept HandlerWithInput = 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_to<typename T::Input>::value;
|
||||
{ a.spec() } -> std::same_as<RpcSpecConstRef>; }
|
||||
and (CoroutineProcess<T> or NonCoroutineProcess<T>)
|
||||
and boost::json::has_value_to<typename T::Input>::value;
|
||||
|
||||
template <typename T>
|
||||
concept HandlerWithoutInput = requires(T a, typename T::Output out) {
|
||||
{ a.process() } -> std::same_as<HandlerReturnType<decltype(out)>>;};
|
||||
{ a.process() } -> std::same_as<HandlerReturnType<decltype(out)>>; };
|
||||
|
||||
template <typename T>
|
||||
concept Handler =
|
||||
(HandlerWithInput<T>
|
||||
||
|
||||
or
|
||||
HandlerWithoutInput<T>)
|
||||
&& boost::json::has_value_from<typename T::Output>::value;
|
||||
and boost::json::has_value_from<typename T::Output>::value;
|
||||
// clang-format on
|
||||
|
||||
} // namespace RPCng
|
||||
|
||||
@@ -31,8 +31,10 @@ template <Handler HandlerType>
|
||||
struct DefaultProcessor final
|
||||
{
|
||||
[[nodiscard]] ReturnType
|
||||
operator()(HandlerType const& handler, boost::json::value const& value)
|
||||
const
|
||||
operator()(
|
||||
HandlerType const& handler,
|
||||
boost::json::value const& value,
|
||||
boost::asio::yield_context* ptrYield = nullptr) const
|
||||
{
|
||||
using boost::json::value_from;
|
||||
using boost::json::value_to;
|
||||
@@ -44,12 +46,24 @@ struct DefaultProcessor final
|
||||
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
|
||||
if constexpr (NonCoroutineProcess<HandlerType>)
|
||||
{
|
||||
auto const ret = handler.process(inData);
|
||||
// real handler is given expected Input, not json
|
||||
if (!ret)
|
||||
return Error{ret.error()}; // forward Status
|
||||
else
|
||||
return value_from(ret.value());
|
||||
}
|
||||
else
|
||||
return value_from(ret.value());
|
||||
{
|
||||
auto const ret = handler.process(inData, ptrYield);
|
||||
// real handler is given expected Input, not json
|
||||
if (!ret)
|
||||
return Error{ret.error()}; // forward Status
|
||||
else
|
||||
return value_from(ret.value());
|
||||
}
|
||||
}
|
||||
else if constexpr (HandlerWithoutInput<HandlerType>)
|
||||
{
|
||||
|
||||
@@ -50,6 +50,24 @@ TEST_F(RPCTestHandlerTest, HandlerSuccess)
|
||||
EXPECT_EQ(val.as_object().at("computed").as_string(), "world_10");
|
||||
}
|
||||
|
||||
TEST_F(RPCTestHandlerTest, CoroutineHandlerSuccess)
|
||||
{
|
||||
auto const handler = AnyHandler{CoroutineHandlerFake{}};
|
||||
auto const input = json::parse(R"({
|
||||
"hello": "world",
|
||||
"limit": 10
|
||||
})");
|
||||
boost::asio::io_context ctx;
|
||||
boost::asio::spawn(ctx, [&](boost::asio::yield_context yield) {
|
||||
auto const output = handler.process(input, yield);
|
||||
ASSERT_TRUE(output);
|
||||
|
||||
auto const val = output.value();
|
||||
EXPECT_EQ(val.as_object().at("computed").as_string(), "world_10");
|
||||
});
|
||||
ctx.run();
|
||||
}
|
||||
|
||||
TEST_F(RPCTestHandlerTest, NoInputHandlerSuccess)
|
||||
{
|
||||
auto const handler = AnyHandler{NoInputHandlerFake{}};
|
||||
|
||||
@@ -98,6 +98,37 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
// example handler
|
||||
class CoroutineHandlerFake
|
||||
{
|
||||
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, boost::asio::yield_context* ptrYield) const
|
||||
{
|
||||
return Output{
|
||||
input.hello + '_' + std::to_string(input.limit.value_or(0))};
|
||||
}
|
||||
};
|
||||
|
||||
class NoInputHandlerFake
|
||||
{
|
||||
public:
|
||||
|
||||
Reference in New Issue
Block a user