From 682bec618d2ff90842f687da798e5d97fdc3a915 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anarthal=20=28Rub=C3=A9n=20P=C3=A9rez=29?= <34971811+anarthal@users.noreply.github.com> Date: Mon, 27 Oct 2025 15:18:46 +0100 Subject: [PATCH] Adds request::append() (#342) close #341 --- include/boost/redis/impl/request.ipp | 7 ++ include/boost/redis/request.hpp | 11 ++ test/test_request.cpp | 161 ++++++++++++++++++++++++--- 3 files changed, 165 insertions(+), 14 deletions(-) diff --git a/include/boost/redis/impl/request.ipp b/include/boost/redis/impl/request.ipp index f0a08e0c..741c3b5d 100644 --- a/include/boost/redis/impl/request.ipp +++ b/include/boost/redis/impl/request.ipp @@ -31,3 +31,10 @@ request make_hello_request() } } // namespace boost::redis::detail + +void boost::redis::request::append(const request& other) +{ + payload_ += other.payload_; + commands_ += other.commands_; + expected_responses_ += other.expected_responses_; +} diff --git a/include/boost/redis/request.hpp b/include/boost/redis/request.hpp index 534d92c6..ca6f9952 100644 --- a/include/boost/redis/request.hpp +++ b/include/boost/redis/request.hpp @@ -338,6 +338,17 @@ public: push_range(cmd, cbegin(range), cend(range)); } + /** @brief Appends the commands in another request to the end of the request. + * + * Appends all the commands contained in `other` to the end of + * this request. Configuration flags in `*this`, + * like @ref config::cancel_if_unresponded, are *not* modified, + * even if `other` has a different config than `*this`. + * + * @param other The request containing the commands to append. + */ + void append(const request& other); + private: void check_cmd(std::string_view cmd) { diff --git a/test/test_request.cpp b/test/test_request.cpp index 701ca5f9..4be2425e 100644 --- a/test/test_request.cpp +++ b/test/test_request.cpp @@ -4,55 +4,188 @@ * accompanying file LICENSE.txt) */ -#include - -#define BOOST_TEST_MODULE request #include -#include +#include + +#include +#include +#include using boost::redis::request; // TODO: Serialization. -BOOST_AUTO_TEST_CASE(single_arg_allocator) +namespace { + +void test_push_no_args() { request req1; req1.push("PING"); - BOOST_CHECK_EQUAL(req1.payload(), std::string{"*1\r\n$4\r\nPING\r\n"}); + BOOST_TEST_EQ(req1.payload(), "*1\r\n$4\r\nPING\r\n"); } -BOOST_AUTO_TEST_CASE(arg_int) +void test_push_int() { request req; req.push("PING", 42); - BOOST_CHECK_EQUAL(req.payload(), std::string{"*2\r\n$4\r\nPING\r\n$2\r\n42\r\n"}); + BOOST_TEST_EQ(req.payload(), "*2\r\n$4\r\nPING\r\n$2\r\n42\r\n"); } -BOOST_AUTO_TEST_CASE(multiple_args) +void test_push_multiple_args() { char const* res = "*5\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n$2\r\nEX\r\n$1\r\n2\r\n"; request req; req.push("SET", "key", "value", "EX", "2"); - BOOST_CHECK_EQUAL(req.payload(), std::string{res}); + BOOST_TEST_EQ(req.payload(), res); } -BOOST_AUTO_TEST_CASE(container_and_range) +void test_push_range() { std::map in{ {"key1", "value1"}, {"key2", "value2"} }; - char const* res = + constexpr std::string_view expected = "*6\r\n$4\r\nHSET\r\n$3\r\nkey\r\n$4\r\nkey1\r\n$6\r\nvalue1\r\n$4\r\nkey2\r\n$" "6\r\nvalue2\r\n"; request req1; req1.push_range("HSET", "key", in); - BOOST_CHECK_EQUAL(req1.payload(), std::string{res}); + BOOST_TEST_EQ(req1.payload(), expected); request req2; req2.push_range("HSET", "key", std::cbegin(in), std::cend(in)); - BOOST_CHECK_EQUAL(req2.payload(), std::string{res}); + BOOST_TEST_EQ(req2.payload(), expected); +} + +// Append +void test_append() +{ + request req1; + req1.push("PING", "req1"); + + request req2; + req2.push("GET", "mykey"); + req2.push("GET", "other"); + + req1.append(req2); + + constexpr std::string_view expected = + "*2\r\n$4\r\nPING\r\n$4\r\nreq1\r\n" + "*2\r\n$3\r\nGET\r\n$5\r\nmykey\r\n" + "*2\r\n$3\r\nGET\r\n$5\r\nother\r\n"; + BOOST_TEST_EQ(req1.payload(), expected); + BOOST_TEST_EQ(req1.get_commands(), 3u); + BOOST_TEST_EQ(req1.get_expected_responses(), 3u); +} + +// Commands without responses are handled correctly +void test_append_no_response() +{ + request req1; + req1.push("PING", "req1"); + + request req2; + req2.push("SUBSCRIBE", "mychannel"); + req2.push("GET", "other"); + + req1.append(req2); + + constexpr std::string_view expected = + "*2\r\n$4\r\nPING\r\n$4\r\nreq1\r\n" + "*2\r\n$9\r\nSUBSCRIBE\r\n$9\r\nmychannel\r\n" + "*2\r\n$3\r\nGET\r\n$5\r\nother\r\n"; + BOOST_TEST_EQ(req1.payload(), expected); + BOOST_TEST_EQ(req1.get_commands(), 3u); + BOOST_TEST_EQ(req1.get_expected_responses(), 2u); +} + +// Flags are not modified by append +void test_append_flags() +{ + request req1; + req1.get_config().cancel_if_not_connected = false; + req1.get_config().cancel_if_unresponded = false; + req1.get_config().cancel_on_connection_lost = false; + req1.push("PING", "req1"); + + request req2; + req2.get_config().cancel_if_not_connected = true; + req2.get_config().cancel_if_unresponded = true; + req2.get_config().cancel_on_connection_lost = true; + req2.push("GET", "other"); + + req1.append(req2); + + constexpr std::string_view expected = + "*2\r\n$4\r\nPING\r\n$4\r\nreq1\r\n" + "*2\r\n$3\r\nGET\r\n$5\r\nother\r\n"; + BOOST_TEST_EQ(req1.payload(), expected); + BOOST_TEST_NOT(req1.get_config().cancel_if_not_connected); + BOOST_TEST_NOT(req1.get_config().cancel_if_unresponded); + BOOST_TEST_NOT(req1.get_config().cancel_on_connection_lost); +} + +// Empty requests don't cause problems with append +void test_append_target_empty() +{ + request req1; + + request req2; + req2.push("GET", "other"); + + req1.append(req2); + + constexpr std::string_view expected = "*2\r\n$3\r\nGET\r\n$5\r\nother\r\n"; + BOOST_TEST_EQ(req1.payload(), expected); + BOOST_TEST_EQ(req1.get_commands(), 1u); + BOOST_TEST_EQ(req1.get_expected_responses(), 1u); +} + +void test_append_source_empty() +{ + request req1; + req1.push("GET", "other"); + + request req2; + + req1.append(req2); + + constexpr std::string_view expected = "*2\r\n$3\r\nGET\r\n$5\r\nother\r\n"; + BOOST_TEST_EQ(req1.payload(), expected); + BOOST_TEST_EQ(req1.get_commands(), 1u); + BOOST_TEST_EQ(req1.get_expected_responses(), 1u); +} + +void test_append_both_empty() +{ + request req1; + request req2; + + req1.append(req2); + + BOOST_TEST_EQ(req1.payload(), ""); + BOOST_TEST_EQ(req1.get_commands(), 0u); + BOOST_TEST_EQ(req1.get_expected_responses(), 0u); +} + +} // namespace + +int main() +{ + test_push_no_args(); + test_push_int(); + test_push_multiple_args(); + test_push_range(); + + test_append(); + test_append_no_response(); + test_append_flags(); + test_append_target_empty(); + test_append_source_empty(); + test_append_both_empty(); + + return boost::report_errors(); }