diff --git a/src/rpc/common/AnyHandler.h b/src/rpc/common/AnyHandler.h index 016da497..bc891bef 100644 --- a/src/rpc/common/AnyHandler.h +++ b/src/rpc/common/AnyHandler.h @@ -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 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 diff --git a/src/rpc/common/Concepts.h b/src/rpc/common/Concepts.h index c483db52..67f41d1f 100644 --- a/src/rpc/common/Concepts.h +++ b/src/rpc/common/Concepts.h @@ -21,6 +21,7 @@ #include +#include #include #include @@ -48,22 +49,30 @@ concept Requirement = requires(T a) { * as per boost::json documentation for these functions. */ // clang-format off +template +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>; }; + +template +concept NonCoroutineProcess = requires(T a, typename T::Input in, typename T::Output out) { + { a.process(in) } -> std::same_as>; }; + template concept HandlerWithInput = requires(T a, typename T::Input in, typename T::Output out) { - { a.spec() } -> std::same_as; - { a.process(in) } -> std::same_as>;} - && boost::json::has_value_to::value; + { a.spec() } -> std::same_as; } + and (CoroutineProcess or NonCoroutineProcess) + and boost::json::has_value_to::value; template concept HandlerWithoutInput = requires(T a, typename T::Output out) { - { a.process() } -> std::same_as>;}; + { a.process() } -> std::same_as>; }; template concept Handler = (HandlerWithInput -|| +or HandlerWithoutInput) -&& boost::json::has_value_from::value; +and boost::json::has_value_from::value; // clang-format on } // namespace RPCng diff --git a/src/rpc/common/impl/Processors.h b/src/rpc/common/impl/Processors.h index 215f6ed8..9f4c77d5 100644 --- a/src/rpc/common/impl/Processors.h +++ b/src/rpc/common/impl/Processors.h @@ -31,8 +31,10 @@ template 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(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) + { + 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) { diff --git a/unittests/rpc/handlers/TestHandlerTests.cpp b/unittests/rpc/handlers/TestHandlerTests.cpp index bf4f37ce..89f602ad 100644 --- a/unittests/rpc/handlers/TestHandlerTests.cpp +++ b/unittests/rpc/handlers/TestHandlerTests.cpp @@ -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{}}; diff --git a/unittests/rpc/handlers/impl/FakesAndMocks.h b/unittests/rpc/handlers/impl/FakesAndMocks.h index 4d2a7c49..59c6d2f4 100644 --- a/unittests/rpc/handlers/impl/FakesAndMocks.h +++ b/unittests/rpc/handlers/impl/FakesAndMocks.h @@ -98,6 +98,37 @@ public: } }; +// example handler +class CoroutineHandlerFake +{ +public: + using Input = TestInput; + using Output = TestOutput; + using Result = RPCng::HandlerReturnType; + + RPCng::RpcSpecConstRef + spec() const + { + using namespace RPCng::validation; + + // clang-format off + static const RPCng::RpcSpec rpcSpec = { + {"hello", Required{}, Type{}, EqualTo{"world"}}, + {"limit", Type{}, Between{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: