Provide coroutine process interface for handler (#521)

Fixes #522
This commit is contained in:
cyan317
2023-02-23 16:35:01 +00:00
committed by GitHub
parent d74ca4940b
commit f6c2008540
5 changed files with 107 additions and 16 deletions

View File

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

View File

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

View File

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

View File

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

View File

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