diff --git a/example/cpp20_chat_room.cpp b/example/cpp20_chat_room.cpp index 89ae7869..562225f5 100644 --- a/example/cpp20_chat_room.cpp +++ b/example/cpp20_chat_room.cpp @@ -1,4 +1,4 @@ -/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com) +/* Copyright (c) 2018-2025 Marcelo Zimbres Silva (mzimbres@gmail.com) * * Distributed under the Boost Software License, Version 1.0. (See * accompanying file LICENSE.txt) @@ -32,7 +32,7 @@ using boost::asio::dynamic_buffer; using boost::asio::redirect_error; using boost::redis::config; using boost::redis::connection; -using boost::redis::resp3::flat_tree; +using boost::redis::generic_flat_response; using boost::redis::request; using boost::system::error_code; using namespace std::chrono_literals; @@ -45,7 +45,7 @@ auto receiver(std::shared_ptr conn) -> awaitable request req; req.push("SUBSCRIBE", "channel"); - flat_tree resp; + generic_flat_response resp; conn->set_receive_response(resp); while (conn->will_reconnect()) { @@ -58,12 +58,12 @@ auto receiver(std::shared_ptr conn) -> awaitable if (ec) break; // Connection lost, break so we can reconnect to channels. - for (auto const& elem: resp.get_view()) + for (auto const& elem: resp.value().get_view()) std::cout << elem.value << "\n"; std::cout << std::endl; - resp.clear(); + resp.value().clear(); } } } diff --git a/example/cpp20_subscriber.cpp b/example/cpp20_subscriber.cpp index 531fdaf9..1f0dd86f 100644 --- a/example/cpp20_subscriber.cpp +++ b/example/cpp20_subscriber.cpp @@ -21,7 +21,7 @@ namespace asio = boost::asio; using namespace std::chrono_literals; using boost::redis::request; -using boost::redis::resp3::flat_tree; +using boost::redis::generic_flat_response; using boost::redis::config; using boost::system::error_code; using boost::redis::connection; @@ -49,7 +49,7 @@ auto receiver(std::shared_ptr conn) -> asio::awaitable request req; req.push("SUBSCRIBE", "channel"); - flat_tree resp; + generic_flat_response resp; conn->set_receive_response(resp); // Loop while reconnection is enabled @@ -66,12 +66,12 @@ auto receiver(std::shared_ptr conn) -> asio::awaitable // The response must be consumed without suspending the // coroutine i.e. without the use of async operations. - for (auto const& elem: resp.get_view()) + for (auto const& elem: resp.value().get_view()) std::cout << elem.value << "\n"; std::cout << std::endl; - resp.clear(); + resp.value().clear(); } } } diff --git a/include/boost/redis/adapter/detail/adapters.hpp b/include/boost/redis/adapter/detail/adapters.hpp index 432fa9bd..7830f0ed 100644 --- a/include/boost/redis/adapter/detail/adapters.hpp +++ b/include/boost/redis/adapter/detail/adapters.hpp @@ -13,6 +13,7 @@ #include #include #include +#include #include @@ -205,6 +206,45 @@ public: } }; +template <> +class general_aggregate { +private: + generic_flat_response* tree_ = nullptr; + +public: + explicit general_aggregate(generic_flat_response* c = nullptr) + : tree_(c) + { } + + void on_init() { } + void on_done() + { + BOOST_ASSERT_MSG(!!tree_, "Unexpected null pointer"); + if (tree_->has_value()) { + tree_->value().notify_done(); + } + } + + template + void on_node(resp3::basic_node const& nd, system::error_code&) + { + BOOST_ASSERT_MSG(!!tree_, "Unexpected null pointer"); + switch (nd.data_type) { + case resp3::type::blob_error: + case resp3::type::simple_error: + *tree_ = error{ + nd.data_type, + std::string{std::cbegin(nd.value), std::cend(nd.value)} + }; + break; + default: + if (tree_->has_value()) { + (**tree_).push(nd); + } + } + } +}; + template <> class general_aggregate { private: diff --git a/include/boost/redis/adapter/detail/response_traits.hpp b/include/boost/redis/adapter/detail/response_traits.hpp index 49385812..d4d8a304 100644 --- a/include/boost/redis/adapter/detail/response_traits.hpp +++ b/include/boost/redis/adapter/detail/response_traits.hpp @@ -115,6 +115,14 @@ struct response_traits { static auto adapt(response_type& v) noexcept { return adapter_type{&v}; } }; +template <> +struct response_traits { + using response_type = generic_flat_response; + using adapter_type = general_aggregate; + + static auto adapt(response_type& v) noexcept { return adapter_type{&v}; } +}; + template struct response_traits> { using response_type = response; diff --git a/include/boost/redis/adapter/detail/result_traits.hpp b/include/boost/redis/adapter/detail/result_traits.hpp index 67131819..83eddcc2 100644 --- a/include/boost/redis/adapter/detail/result_traits.hpp +++ b/include/boost/redis/adapter/detail/result_traits.hpp @@ -71,6 +71,13 @@ struct result_traits> { static auto adapt(response_type& v) noexcept { return adapter_type{&v}; } }; +template <> +struct result_traits { + using response_type = generic_flat_response; + using adapter_type = general_aggregate; + static auto adapt(response_type& v) noexcept { return adapter_type{&v}; } +}; + template <> struct result_traits { using response_type = resp3::flat_tree; diff --git a/include/boost/redis/response.hpp b/include/boost/redis/response.hpp index 5622f3a8..c39dc632 100644 --- a/include/boost/redis/response.hpp +++ b/include/boost/redis/response.hpp @@ -10,6 +10,7 @@ #include #include #include +#include #include @@ -32,6 +33,9 @@ using response = std::tuple...>; */ using generic_response = adapter::result; +/// Similar to @ref boost::redis::generic_response but stores data contiguously. +using generic_flat_response = adapter::result; + /** @brief (Deprecated) Consume on response from a generic response * * This function rotates the elements so that the start of the next diff --git a/test/test_low_level_sync_sans_io.cpp b/test/test_low_level_sync_sans_io.cpp index 800d304f..246cc0f3 100644 --- a/test/test_low_level_sync_sans_io.cpp +++ b/test/test_low_level_sync_sans_io.cpp @@ -24,6 +24,7 @@ using boost::redis::adapter::adapt2; using boost::redis::adapter::result; using boost::redis::resp3::tree; using boost::redis::resp3::flat_tree; +using boost::redis::generic_flat_response; using boost::redis::ignore_t; using boost::redis::resp3::detail::deserialize; using boost::redis::resp3::node; @@ -375,25 +376,45 @@ tree from_flat(flat_tree const& resp) return ret; } +tree from_flat(generic_flat_response const& resp) +{ + tree ret; + for (auto const& e: resp.value().get_view()) + ret.push_back(from_node_view(e)); + + return ret; +} + + // Parses the same data into a tree and a // flat_tree, they should be equal to each other. BOOST_AUTO_TEST_CASE(flat_tree_views_are_set) { tree resp1; - flat_tree fresp; + flat_tree resp2; + generic_flat_response resp3; error_code ec; deserialize(resp3_set, adapt2(resp1), ec); BOOST_CHECK_EQUAL(ec, error_code{}); - deserialize(resp3_set, adapt2(fresp), ec); + deserialize(resp3_set, adapt2(resp2), ec); BOOST_CHECK_EQUAL(ec, error_code{}); - BOOST_CHECK_EQUAL(fresp.get_reallocs(), 1u); - BOOST_CHECK_EQUAL(fresp.get_total_msgs(), 1u); + deserialize(resp3_set, adapt2(resp3), ec); + BOOST_CHECK_EQUAL(ec, error_code{}); - auto const resp2 = from_flat(fresp); - BOOST_CHECK_EQUAL(resp1, resp2); + BOOST_CHECK_EQUAL(resp2.get_reallocs(), 1u); + BOOST_CHECK_EQUAL(resp2.get_total_msgs(), 1u); + + BOOST_CHECK_EQUAL(resp3.value().get_reallocs(), 1u); + BOOST_CHECK_EQUAL(resp3.value().get_total_msgs(), 1u); + + auto const tmp2 = from_flat(resp2); + BOOST_CHECK_EQUAL(resp1, tmp2); + + auto const tmp3 = from_flat(resp3); + BOOST_CHECK_EQUAL(resp1, tmp3); } // The response should be reusable. @@ -446,3 +467,39 @@ BOOST_AUTO_TEST_CASE(flat_tree_copy_assign) BOOST_TEST((copy2 == resp)); BOOST_TEST((copy3 == resp)); } + +BOOST_AUTO_TEST_CASE(generic_flat_response_simple_error) +{ + generic_flat_response resp; + + char const* wire = "-Error\r\n"; + + error_code ec; + deserialize(wire, adapt2(resp), ec); + BOOST_CHECK_EQUAL(ec, error_code{}); + + BOOST_TEST(!resp.has_value()); + BOOST_TEST(resp.has_error()); + auto const error = resp.error(); + + BOOST_CHECK_EQUAL(error.data_type, boost::redis::resp3::type::simple_error); + BOOST_CHECK_EQUAL(error.diagnostic, std::string{"Error"}); +} + +BOOST_AUTO_TEST_CASE(generic_flat_response_blob_error) +{ + generic_flat_response resp; + + char const* wire = "!5\r\nError\r\n"; + + error_code ec; + deserialize(wire, adapt2(resp), ec); + BOOST_CHECK_EQUAL(ec, error_code{}); + + BOOST_TEST(!resp.has_value()); + BOOST_TEST(resp.has_error()); + auto const error = resp.error(); + + BOOST_CHECK_EQUAL(error.data_type, boost::redis::resp3::type::blob_error); + BOOST_CHECK_EQUAL(error.diagnostic, std::string{"Error"}); +}