mirror of
https://github.com/XRPLF/clio.git
synced 2025-11-21 04:05:51 +00:00
@@ -80,13 +80,30 @@ public:
|
|||||||
return pimpl_->process(value);
|
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:
|
private:
|
||||||
struct Concept
|
struct Concept
|
||||||
{
|
{
|
||||||
virtual ~Concept() = default;
|
virtual ~Concept() = default;
|
||||||
|
|
||||||
[[nodiscard]] virtual ReturnType
|
[[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>
|
[[nodiscard]] virtual std::unique_ptr<Concept>
|
||||||
clone() const = 0;
|
clone() const = 0;
|
||||||
@@ -103,9 +120,11 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] ReturnType
|
[[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>
|
[[nodiscard]] std::unique_ptr<Concept>
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
|
|
||||||
#include <rpc/common/Types.h>
|
#include <rpc/common/Types.h>
|
||||||
|
|
||||||
|
#include <boost/asio/spawn.hpp>
|
||||||
#include <boost/json/value_from.hpp>
|
#include <boost/json/value_from.hpp>
|
||||||
#include <boost/json/value_to.hpp>
|
#include <boost/json/value_to.hpp>
|
||||||
|
|
||||||
@@ -48,11 +49,19 @@ concept Requirement = requires(T a) {
|
|||||||
* as per boost::json documentation for these functions.
|
* as per boost::json documentation for these functions.
|
||||||
*/
|
*/
|
||||||
// clang-format off
|
// 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>
|
template <typename T>
|
||||||
concept HandlerWithInput = requires(T a, typename T::Input in, typename T::Output out) {
|
concept HandlerWithInput = requires(T a, typename T::Input in, typename T::Output out) {
|
||||||
{ a.spec() } -> std::same_as<RpcSpecConstRef>;
|
{ a.spec() } -> std::same_as<RpcSpecConstRef>; }
|
||||||
{ a.process(in) } -> std::same_as<HandlerReturnType<decltype(out)>>;}
|
and (CoroutineProcess<T> or NonCoroutineProcess<T>)
|
||||||
&& boost::json::has_value_to<typename T::Input>::value;
|
and boost::json::has_value_to<typename T::Input>::value;
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
concept HandlerWithoutInput = requires(T a, typename T::Output out) {
|
concept HandlerWithoutInput = requires(T a, typename T::Output out) {
|
||||||
@@ -61,9 +70,9 @@ concept HandlerWithoutInput = requires(T a, typename T::Output out) {
|
|||||||
template <typename T>
|
template <typename T>
|
||||||
concept Handler =
|
concept Handler =
|
||||||
(HandlerWithInput<T>
|
(HandlerWithInput<T>
|
||||||
||
|
or
|
||||||
HandlerWithoutInput<T>)
|
HandlerWithoutInput<T>)
|
||||||
&& boost::json::has_value_from<typename T::Output>::value;
|
and boost::json::has_value_from<typename T::Output>::value;
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
} // namespace RPCng
|
} // namespace RPCng
|
||||||
|
|||||||
@@ -31,8 +31,10 @@ template <Handler HandlerType>
|
|||||||
struct DefaultProcessor final
|
struct DefaultProcessor final
|
||||||
{
|
{
|
||||||
[[nodiscard]] ReturnType
|
[[nodiscard]] ReturnType
|
||||||
operator()(HandlerType const& handler, boost::json::value const& value)
|
operator()(
|
||||||
const
|
HandlerType const& handler,
|
||||||
|
boost::json::value const& value,
|
||||||
|
boost::asio::yield_context* ptrYield = nullptr) const
|
||||||
{
|
{
|
||||||
using boost::json::value_from;
|
using boost::json::value_from;
|
||||||
using boost::json::value_to;
|
using boost::json::value_to;
|
||||||
@@ -44,13 +46,25 @@ struct DefaultProcessor final
|
|||||||
return Error{ret.error()}; // forward Status
|
return Error{ret.error()}; // forward Status
|
||||||
|
|
||||||
auto const inData = value_to<typename HandlerType::Input>(value);
|
auto const inData = value_to<typename HandlerType::Input>(value);
|
||||||
|
if constexpr (NonCoroutineProcess<HandlerType>)
|
||||||
|
{
|
||||||
|
auto const ret = handler.process(inData);
|
||||||
// real handler is given expected Input, not json
|
// real handler is given expected Input, not json
|
||||||
if (auto const ret = handler.process(inData); not ret)
|
if (!ret)
|
||||||
return Error{ret.error()}; // forward Status
|
return Error{ret.error()}; // forward Status
|
||||||
else
|
else
|
||||||
return value_from(ret.value());
|
return value_from(ret.value());
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
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>)
|
else if constexpr (HandlerWithoutInput<HandlerType>)
|
||||||
{
|
{
|
||||||
// no input to pass, ignore the value
|
// no input to pass, ignore the value
|
||||||
|
|||||||
@@ -50,6 +50,24 @@ TEST_F(RPCTestHandlerTest, HandlerSuccess)
|
|||||||
EXPECT_EQ(val.as_object().at("computed").as_string(), "world_10");
|
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)
|
TEST_F(RPCTestHandlerTest, NoInputHandlerSuccess)
|
||||||
{
|
{
|
||||||
auto const handler = AnyHandler{NoInputHandlerFake{}};
|
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
|
class NoInputHandlerFake
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|||||||
Reference in New Issue
Block a user