diff --git a/README.md b/README.md index bce9f5f7..bbe65494 100644 --- a/README.md +++ b/README.md @@ -5,58 +5,31 @@ providing an easy and intuitive interface. ## Example -The general form of the read and write operations of a redis client -that support push notifications and pipelines looks like the following +Lets us see some examples with increasing order of complexity. ```cpp -net::awaitable -example(net::ip::tcp::socket& socket, std::queue& pipelines) +net::awaitable ping() { - pipelines.push({}); - pipelines.back().hello("3"); + auto socket = co_await make_connection(); - std::string buffer; - response_buffers buffers; - response_adapters adapters{buffers}; - consumer_state cs; + std::queue requests; + requests.push({}); + requests.back().hello(); + requests.back().ping(); + requests.back().quit(); + resp3::consumer cs; for (;;) { - auto const type = - co_await async_consume( - socket, buffer, pipelines, adapters, cs, net::use_awaitable); - - if (type == resp3::type::push) { - // Push received. - continue; - } - - auto const cmd = pipelines.front().commands.front(); - - // Response to a specific command. + resp3::response resp; + co_await cs.async_consume(socket, requests, resp); + std::cout << requests.front().elements.front() << "\n" << resp << std::endl; } } ``` The example above will start writing the `hello` command and proceed reading its response. After that users can add further commands to the -queue. See the example directory for a complete example. The main -function looks like this - -```cpp -int main() -{ - net::io_context ioc; - net::ip::tcp::resolver resolver{ioc}; - auto const res = resolver.resolve("127.0.0.1", "6379"); - - net::ip::tcp::socket socket{ioc}; - net::connect(socket, res); - - std::queue pipelines; - co_spawn(ioc, example(socket, pipelines), net::detached); - ioc.run(); -} -``` +queue. See the example directory for a complete example. See the `examples` directory for more examples. diff --git a/examples/async_basic.cpp b/examples/async_basic.cpp index fb9014d9..bd4ea284 100644 --- a/examples/async_basic.cpp +++ b/examples/async_basic.cpp @@ -12,40 +12,34 @@ using namespace aedis; using tcp_socket = net::use_awaitable_t<>::as_default_on_t; +using tcp_resolver = net::use_awaitable_t<>::as_default_on_t; -void print_event(std::pair const& p) +net::awaitable example() { - std::cout << "Event: " << p.first << "."; + auto ex = co_await net::this_coro::executor; + tcp_resolver resolver{ex}; + auto const res = co_await resolver.async_resolve("127.0.0.1", "6379"); + tcp_socket socket{ex}; + co_await net::async_connect(socket, res); - if (!std::empty(p.second)) - std::cout << " Key: " << p.second << "."; - - std::cout << std::endl; -} - -net::awaitable -example( - tcp_socket& socket, - std::queue& requests) -{ + std::queue requests; requests.push({}); - requests.back().hello("3"); + requests.back().hello(); - resp3::response resp; resp3::consumer cs; for (;;) { - resp.clear(); + resp3::response resp; co_await cs.async_consume(socket, requests, resp); std::cout << resp << std::endl; if (resp.get_type() == resp3::type::push) continue; - auto const id = requests.front().ids.front(); + auto const& elem = requests.front().elements.front(); - print_event(id); - switch (id.first) { + std::cout << elem << std::endl; + switch (elem.cmd) { case command::hello: { prepare_next(requests); @@ -68,13 +62,6 @@ example( int main() { net::io_context ioc; - net::ip::tcp::resolver resolver{ioc}; - auto const res = resolver.resolve("127.0.0.1", "6379"); - - tcp_socket socket{ioc}; - net::connect(socket, res); - - std::queue requests; - co_spawn(ioc, example(socket, requests), net::detached); + co_spawn(ioc, example(), net::detached); ioc.run(); } diff --git a/examples/hello.cpp b/examples/hello.cpp index 79595c17..6276a9de 100644 --- a/examples/hello.cpp +++ b/examples/hello.cpp @@ -9,60 +9,52 @@ #include -/* A very simple example where we connect to redis and quit. +/** A very simple example showing how to + * + * 1. Connect to the redis server. + * 2. Send a ping + * 3. and quit. + * + * Notice that in this example we are sending all commands in the same request + * instead of waiting the response of each command. */ using namespace aedis; using tcp_socket = net::use_awaitable_t<>::as_default_on_t; +using tcp_resolver = net::use_awaitable_t<>::as_default_on_t; -void print_event(std::pair const& p) +net::awaitable make_connection() { - std::cout << "Event: " << p.first << "."; - - if (!std::empty(p.second)) - std::cout << " Key: " << p.second << "."; - - std::cout << std::endl; + auto ex = co_await net::this_coro::executor; + tcp_resolver resolver{ex}; + auto const res = co_await resolver.async_resolve("127.0.0.1", "6379"); + tcp_socket socket{ex}; + co_await net::async_connect(socket, res); + co_return std::move(socket); } -net::awaitable -example( - tcp_socket& socket, - std::queue& requests) +net::awaitable ping() { + auto socket = co_await make_connection(); + + std::queue requests; requests.push({}); - requests.back().hello("3"); + requests.back().hello(); + requests.back().ping(); + requests.back().quit(); - resp3::response resp; resp3::consumer cs; - for (;;) { + resp3::response resp; co_await cs.async_consume(socket, requests, resp); - auto const cmd = requests.front().ids.front().first; - auto const type = requests.front().ids.front().first; - - std::cout << cmd << "\n" << resp << std::endl; - - if (cmd == command::hello) { - prepare_next(requests); - requests.back().quit(); - } - - resp.clear(); + std::cout << requests.front().elements.front() << "\n" << resp << std::endl; } } int main() { net::io_context ioc; - net::ip::tcp::resolver resolver{ioc}; - auto const res = resolver.resolve("127.0.0.1", "6379"); - - tcp_socket socket{ioc}; - net::connect(socket, res); - - std::queue requests; - co_spawn(ioc, example(socket, requests), net::detached); + co_spawn(ioc, ping(), net::detached); ioc.run(); } diff --git a/include/aedis/resp3/detail/read.hpp b/include/aedis/resp3/detail/read.hpp index 35b8b943..0ef01451 100644 --- a/include/aedis/resp3/detail/read.hpp +++ b/include/aedis/resp3/detail/read.hpp @@ -189,11 +189,11 @@ struct consumer_op { yield { if (m_type == type::push) { - auto* adapter = resp.select_adapter(m_type, command::unknown, {}); + auto* adapter = resp.select_adapter(m_type); async_read_one(stream, buffer, *adapter, std::move(self)); } else { - auto const& pair = requests.front().ids.front(); - auto* adapter = resp.select_adapter(m_type, pair.first, pair.second); + auto const& elem = requests.front().elements.front(); + auto* adapter = resp.select_adapter(m_type, elem); async_read_one(stream, buffer, *adapter, std::move(self)); } } @@ -206,9 +206,9 @@ struct consumer_op { yield self.complete(ec, m_type); if (m_type != type::push) - requests.front().ids.pop(); + requests.front().elements.pop(); - } while (!std::empty(requests.front().ids)); + } while (!std::empty(requests.front().elements)); requests.pop(); } while (std::empty(requests)); } diff --git a/include/aedis/resp3/detail/write.hpp b/include/aedis/resp3/detail/write.hpp index eacf63e9..da43c2b7 100644 --- a/include/aedis/resp3/detail/write.hpp +++ b/include/aedis/resp3/detail/write.hpp @@ -78,13 +78,13 @@ struct write_some_op { requests.front().sent = true; - if (std::empty(requests.front().ids)) { + if (std::empty(requests.front().elements)) { // We only pop when all commands in the pipeline has push // responses like subscribe, otherwise, pop is done when the // response arrives. requests.pop(); } - } while (!std::empty(requests) && std::empty(requests.front().ids)); + } while (!std::empty(requests) && std::empty(requests.front().elements)); self.complete(ec); } diff --git a/include/aedis/resp3/impl/request.ipp b/include/aedis/resp3/impl/request.ipp index c2be831a..9de38bf9 100644 --- a/include/aedis/resp3/impl/request.ipp +++ b/include/aedis/resp3/impl/request.ipp @@ -25,5 +25,13 @@ bool prepare_next(std::queue& reqs) return false; } +std::ostream& operator<<(std::ostream& os, request::element const& r) +{ + os << r.cmd; + if (!std::empty(r.key)) + os << "(" << r.key << ")"; + return os; +} + } // resp3 } // aedis diff --git a/include/aedis/resp3/request.hpp b/include/aedis/resp3/request.hpp index 1083da00..a39c3574 100644 --- a/include/aedis/resp3/request.hpp +++ b/include/aedis/resp3/request.hpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -19,7 +20,7 @@ namespace aedis { namespace resp3 { -/** A class to compose redis requests +/** A Redis request also referred to as a pipeline. * * A request is composed of one or more redis commands and is * refered to in the redis documentation as a pipeline, see @@ -30,15 +31,19 @@ namespace resp3 { */ class request { public: + struct element { + command cmd; + std::string key; + }; std::string payload; - std::queue> ids; + std::queue elements; bool sent = false; public: /// Return the size of the pipeline. i.e. how many commands it /// contains. auto size() const noexcept - { return std::size(ids); } + { return std::size(elements); } bool empty() const noexcept { return std::empty(payload); }; @@ -47,72 +52,72 @@ public: void clear() { payload.clear(); - ids = {}; + elements = {}; } void ping() { detail::assemble(payload, "PING"); - ids.push(std::make_pair(command::ping, std::string{})); + elements.emplace(command::ping, std::string{}); } void quit() { detail::assemble(payload, "QUIT"); - ids.push(std::make_pair(command::quit, std::string{})); + elements.emplace(command::quit, std::string{}); } void multi() { detail::assemble(payload, "MULTI"); - ids.push(std::make_pair(command::multi, std::string{})); + elements.emplace(command::multi, std::string{}); } void exec() { detail::assemble(payload, "EXEC"); - ids.push(std::make_pair(command::exec, std::string{})); + elements.emplace(command::exec, std::string{}); } void incr(std::string_view key) { detail::assemble(payload, "INCR", key); - ids.push(std::make_pair(command::incr, std::string{key})); + elements.emplace(command::incr, std::string{key}); } /// Adds auth to the request, see https://redis.io/commands/bgrewriteaof void auth(std::string_view pwd) { detail::assemble(payload, "AUTH", pwd); - ids.push(std::make_pair(command::auth, std::string{})); + elements.emplace(command::auth, std::string{}); } /// Adds bgrewriteaof to the request, see https://redis.io/commands/bgrewriteaof void bgrewriteaof() { detail::assemble(payload, "BGREWRITEAOF"); - ids.push(std::make_pair(command::bgrewriteaof, std::string{})); + elements.emplace(command::bgrewriteaof, std::string{}); } /// Adds role to the request, see https://redis.io/commands/role void role() { detail::assemble(payload, "ROLE"); - ids.push(std::make_pair(command::role, std::string{})); + elements.emplace(command::role, std::string{}); } /// Adds bgsave to the request, see //https://redis.io/commands/bgsave void bgsave() { detail::assemble(payload, "BGSAVE"); - ids.push(std::make_pair(command::bgsave, std::string{})); + elements.emplace(command::bgsave, std::string{}); } /// Adds ping to the request, see https://redis.io/commands/flushall void flushall() { detail::assemble(payload, "FLUSHALL"); - ids.push(std::make_pair(command::flushall, std::string{})); + elements.emplace(command::flushall, std::string{}); } /// Adds ping to the request, see https://redis.io/commands/lpop @@ -130,20 +135,20 @@ public: // std::cend(par)); //} - ids.push(std::make_pair(command::lpop, std::string{key})); + elements.emplace(command::lpop, std::string{key}); } /// Adds ping to the request, see https://redis.io/commands/subscribe void subscribe(std::string_view key) { - // The response to this command is a push. + // The response to this command is a push type. detail::assemble(payload, "SUBSCRIBE", key); } /// Adds ping to the request, see https://redis.io/commands/unsubscribe void unsubscribe(std::string_view key) { - // The response to this command is a push. + // The response to this command is a push type. detail::assemble(payload, "UNSUBSCRIBE", key); } @@ -151,21 +156,21 @@ public: void get(std::string_view key) { detail::assemble(payload, "GET", key); - ids.push(std::make_pair(command::get, std::string{key})); + elements.emplace(command::get, std::string{key}); } /// Adds ping to the request, see https://redis.io/commands/keys void keys(std::string_view pattern) { detail::assemble(payload, "KEYS", pattern); - ids.push(std::make_pair(command::keys, std::string{})); + elements.emplace(command::keys, std::string{}); } /// Adds ping to the request, see https://redis.io/commands/hello void hello(std::string_view version = "3") { detail::assemble(payload, "HELLO", version); - ids.push(std::make_pair(command::hello, std::string{})); + elements.emplace(command::hello, std::string{}); } /// Adds ping to the request, see https://redis.io/commands/sentinel @@ -173,7 +178,7 @@ public: { auto par = {name}; detail::assemble(payload, "SENTINEL", {arg}, std::cbegin(par), std::cend(par)); - ids.push(std::make_pair(command::sentinel, std::string{})); + elements.emplace(command::sentinel, std::string{}); } /// Adds ping to the request, see https://redis.io/commands/append @@ -181,7 +186,7 @@ public: { auto par = {msg}; detail::assemble(payload, "APPEND", {key}, std::cbegin(par), std::cend(par)); - ids.push(std::make_pair(command::append, std::string{key})); + elements.emplace(command::append, std::string{key}); } /// Adds ping to the request, see https://redis.io/commands/bitcount @@ -196,7 +201,7 @@ public: , {key} , std::cbegin(par) , std::cend(par)); - ids.push(std::make_pair(command::bitcount, std::string{key})); + elements.emplace(command::bitcount, std::string{key}); } /// Adds ping to the request, see https://redis.io/commands/rpush @@ -204,7 +209,7 @@ public: void rpush(std::string_view key, Iter begin, Iter end) { detail::assemble(payload, "RPUSH", {key}, begin, end); - ids.push(std::make_pair(command::rpush, std::string{key})); + elements.emplace(command::rpush, std::string{key}); } /// Adds ping to the request, see https://redis.io/commands/rpush @@ -228,7 +233,7 @@ public: void lpush(std::string_view key, Iter begin, Iter end) { detail::assemble(payload, "LPUSH", {key}, begin, end); - ids.push(std::make_pair(command::lpush, std::string{key})); + elements.emplace(command::lpush, std::string{key}); } void psubscribe( std::initializer_list l) @@ -242,7 +247,7 @@ public: { auto par = {msg}; detail::assemble(payload, "PUBLISH", {key}, std::cbegin(par), std::cend(par)); - ids.push(std::make_pair(command::publish, std::string{key})); + elements.emplace(command::publish, std::string{key}); } /// Adds ping to the request, see https://redis.io/commands/set @@ -250,7 +255,7 @@ public: std::initializer_list args) { detail::assemble(payload, "SET", {key}, std::cbegin(args), std::cend(args)); - ids.push(std::make_pair(command::set, std::string{key})); + elements.emplace(command::set, std::string{key}); } // TODO: Find a way to assert the value type is a pair. @@ -263,7 +268,7 @@ public: using std::cbegin; using std::cend; detail::assemble(payload, "HSET", {key}, std::cbegin(r), std::cend(r), 2); - ids.push(std::make_pair(command::hset, std::string{key})); + elements.emplace(command::hset, std::string{key}); } /// Adds ping to the request, see https://redis.io/commands/hincrby @@ -272,7 +277,7 @@ public: auto by_str = std::to_string(by); std::initializer_list par {field, by_str}; detail::assemble(payload, "HINCRBY", {key}, std::cbegin(par), std::cend(par)); - ids.push(std::make_pair(command::hincrby, std::string{key})); + elements.emplace(command::hincrby, std::string{key}); } /// Adds ping to the request, see https://redis.io/commands/hkeys @@ -280,28 +285,28 @@ public: { auto par = {""}; detail::assemble(payload, "HKEYS", {key}, std::cbegin(par), std::cend(par)); - ids.push(std::make_pair(command::hkeys, std::string{key})); + elements.emplace(command::hkeys, std::string{key}); } /// Adds ping to the request, see https://redis.io/commands/hlen void hlen(std::string_view key) { detail::assemble(payload, "HLEN", {key}); - ids.push(std::make_pair(command::hlen, std::string{key})); + elements.emplace(command::hlen, std::string{key}); } /// Adds ping to the request, see https://redis.io/commands/hgetall void hgetall(std::string_view key) { detail::assemble(payload, "HGETALL", {key}); - ids.push(std::make_pair(command::hgetall, std::string{key})); + elements.emplace(command::hgetall, std::string{key}); } /// Adds ping to the request, see https://redis.io/commands/hvals void hvals( std::string_view key) { detail::assemble(payload, "HVALS", {key}); - ids.push(std::make_pair(command::hvals, std::string{key})); + elements.emplace(command::hvals, std::string{key}); } /// Adds ping to the request, see https://redis.io/commands/hget @@ -309,7 +314,7 @@ public: { auto par = {field}; detail::assemble(payload, "HGET", {key}, std::cbegin(par), std::cend(par)); - ids.push(std::make_pair(command::hget, std::string{key})); + elements.emplace(command::hget, std::string{key}); } /// Adds ping to the request, see https://redis.io/commands/hmget @@ -323,7 +328,7 @@ public: , std::cbegin(fields) , std::cend(fields)); - ids.push(std::make_pair(command::hmget, std::string{key})); + elements.emplace(command::hmget, std::string{key}); } /// Adds ping to the request, see https://redis.io/commands/hdel @@ -338,7 +343,7 @@ public: std::cbegin(fields), std::cend(fields)); - ids.push(std::make_pair(command::hdel, std::string{key})); + elements.emplace(command::hdel, std::string{key}); } /// Adds ping to the request, see https://redis.io/commands/expire @@ -347,7 +352,7 @@ public: auto const str = std::to_string(secs); std::initializer_list par {str}; detail::assemble(payload, "EXPIRE", {key}, std::cbegin(par), std::cend(par)); - ids.push(std::make_pair(command::expire, std::string{key})); + elements.emplace(command::expire, std::string{key}); } /// Adds ping to the request, see https://redis.io/commands/zadd @@ -356,7 +361,7 @@ public: auto const score_str = std::to_string(score); std::initializer_list par = {score_str, value}; detail::assemble(payload, "ZADD", {key}, std::cbegin(par), std::cend(par)); - ids.push(std::make_pair(command::zadd, std::string{key})); + elements.emplace(command::zadd, std::string{key}); } /// Adds zadd to the request, see https://redis.io/commands/zadd @@ -364,7 +369,7 @@ public: void zadd(std::initializer_list key, Range const& r) { detail::assemble(payload, "ZADD", key, std::cbegin(r), std::cend(r), 2); - ids.push(std::make_pair(command::zadd, std::string{key})); + elements.emplace(command::zadd, std::string{key}); } /// Adds ping to the request, see https://redis.io/commands/zrange @@ -375,7 +380,7 @@ public: std::initializer_list par {min_str, max_str}; detail::assemble(payload, "ZRANGE", {key}, std::cbegin(par), std::cend(par)); - ids.push(std::make_pair(command::zrange, std::string{key})); + elements.emplace(command::zrange, std::string{key}); } /// Adds ping to the request, see https://redis.io/commands/zrangebyscore @@ -388,7 +393,7 @@ public: auto const min_str = std::to_string(min); auto par = {min_str , max_str}; detail::assemble(payload, "ZRANGEBYSCORE", {key}, std::cbegin(par), std::cend(par)); - ids.push(std::make_pair(command::zrangebyscore, std::string{key})); + elements.emplace(command::zrangebyscore, std::string{key}); } /// Adds ping to the request, see https://redis.io/commands/zremrangebyscore @@ -400,7 +405,7 @@ public: { auto par = {min, max}; detail::assemble(payload, "ZREMRANGEBYSCORE", {key}, std::cbegin(par), std::cend(par)); - ids.push(std::make_pair(command::zremrangebyscore, std::string{key})); + elements.emplace(command::zremrangebyscore, std::string{key}); } /// Adds ping to the request, see https://redis.io/commands/lrange @@ -410,7 +415,7 @@ public: auto const max_str = std::to_string(max); std::initializer_list par {min_str, max_str}; detail::assemble(payload, "LRANGE", {key}, std::cbegin(par), std::cend(par)); - ids.push(std::make_pair(command::lrange, std::string{key})); + elements.emplace(command::lrange, std::string{key}); } /// Adds ping to the request, see https://redis.io/commands/ltrim @@ -420,7 +425,7 @@ public: auto const max_str = std::to_string(max); std::initializer_list par {min_str, max_str}; detail::assemble(payload, "LTRIM", {key}, std::cbegin(par), std::cend(par)); - ids.push(std::make_pair(command::ltrim, std::string{key})); + elements.emplace(command::ltrim, std::string{key}); } // TODO: Overload for vector del. @@ -428,14 +433,14 @@ public: void del(std::string_view key) { detail::assemble(payload, "DEL", key); - ids.push(std::make_pair(command::del, std::string{key})); + elements.emplace(command::del, std::string{key}); } /// Adds ping to the request, see https://redis.io/commands/llen void llen(std::string_view key) { detail::assemble(payload, "LLEN", key); - ids.push(std::make_pair(command::llen, std::string{key})); + elements.emplace(command::llen, std::string{key}); } /// Adds ping to the request, see https://redis.io/commands/sadd @@ -443,7 +448,7 @@ public: void sadd(std::string_view key, Iter begin, Iter end) { detail::assemble(payload, "SADD", {key}, begin, end); - ids.push(std::make_pair(command::sadd, std::string{key})); + elements.emplace(command::sadd, std::string{key}); } /// Adds ping to the request, see https://redis.io/commands/sadd @@ -459,28 +464,28 @@ public: void smembers(std::string_view key) { detail::assemble(payload, "SMEMBERS", key); - ids.push(std::make_pair(command::smembers, std::string{key})); + elements.emplace(command::smembers, std::string{key}); } /// Adds ping to the request, see https://redis.io/commands/scard void scard(std::string_view key) { detail::assemble(payload, "SCARD", key); - ids.push(std::make_pair(command::scard, std::string{key})); + elements.emplace(command::scard, std::string{key}); } /// Adds ping to the request, see https://redis.io/commands/scard void scard(std::string_view key, std::initializer_list l) { detail::assemble(payload, "SDIFF", {key}, std::cbegin(l), std::cend(l)); - ids.push(std::make_pair(command::sdiff, std::string{key})); + elements.emplace(command::sdiff, std::string{key}); } /// Adds ping to the request, see https://redis.io/commands/client_id void client_id(std::string_view parameters) { detail::assemble(payload, "CLIENT ID", {parameters}); - ids.push(std::make_pair(command::client_id, std::string{})); + elements.emplace(command::client_id, std::string{}); } }; @@ -489,5 +494,9 @@ public: */ bool prepare_next(std::queue& reqs); +/** Writes the request element as a string to the stream. + */ +std::ostream& operator<<(std::ostream& os, request::element const& r); + } // resp3 } // aedis diff --git a/include/aedis/resp3/response.hpp b/include/aedis/resp3/response.hpp index 0f92a2db..0c9dfeb7 100644 --- a/include/aedis/resp3/response.hpp +++ b/include/aedis/resp3/response.hpp @@ -9,6 +9,7 @@ #include #include +#include #include namespace aedis { @@ -81,11 +82,9 @@ public: virtual adapter* select_adapter( resp3::type t, - command cmd = command::unknown, - std::string const& key = "") + request::element const& e = {command::unknown, std::string{}}) { return &adapter_; } - auto const& raw() const noexcept {return data_;} auto& raw() noexcept {return data_;} diff --git a/tests/general.cpp b/tests/general.cpp index 592ce106..97bb2d20 100644 --- a/tests/general.cpp +++ b/tests/general.cpp @@ -167,9 +167,9 @@ test_general(net::ip::tcp::resolver::results_type const& res) continue; } - auto const id = requests.front().ids.front(); + auto const& elem = requests.front().elements.front(); - switch (id.first) { + switch (elem.cmd) { case command::hello: { prepare_next(requests); @@ -431,7 +431,7 @@ test_general(net::ip::tcp::resolver::results_type const& res) }; check_equal(resp.raw(), expected, "smembers (value)"); } break; - default: { std::cout << "Error: " << resp.get_type() << " " << id.first << std::endl; } + default: { std::cout << "Error: " << resp.get_type() << " " << elem.cmd << std::endl; } } resp.raw().clear(); @@ -1037,7 +1037,7 @@ net::awaitable test_streamed_string() std::string cmd {"$?\r\n;0\r\n"}; test_tcp_socket ts {cmd}; resp3::response resp; - auto* adapter = resp.select_adapter(resp3::type::streamed_string_part, command::unknown, {}); + auto* adapter = resp.select_adapter(resp3::type::streamed_string_part); co_await detail::async_read_one(ts, buf, *adapter); resp3::response::storage_type expected