diff --git a/aedis/adapter/response_traits.hpp b/aedis/adapter/response_traits.hpp index 06a2da53..682f4597 100644 --- a/aedis/adapter/response_traits.hpp +++ b/aedis/adapter/response_traits.hpp @@ -112,6 +112,11 @@ struct assigner2<0> { } // detail +/** @brief Return a specific adapter from the tuple. + * + * \param t A tuple of response adapters. + * \return The adapter that corresponds to type T. + */ template auto& get(Tuple& t) { @@ -129,27 +134,34 @@ using adapters_array_t = std::tuple_size::value>; template -adapters_array_t -make_adapters_array(Tuple& t) +adapters_array_t make_adapters_array(Tuple& t) { adapters_array_t ret; detail::assigner::value - 1>::assign(ret, t); return ret; } +/** @brief Transaforms a tuple of responses. + * + * @return Transaforms a tuple of responses into a tuple of adapters. + */ template -using adapters_t = - boost::mp11::mp_unique< +using adapters_tuple_t = boost::mp11::mp_rename< boost::mp11::mp_transform< response_traits_t, Tuple>, - std::tuple>>; + std::tuple>; +/** @brief Make a tuple of adapters. + * + * \param t Tuple of responses. + * \return Tuple of adapters. + */ template auto make_adapters_tuple(Tuple& t) { - adapters_t ret; + adapters_tuple_t ret; detail::assigner2::value - 1>::assign(ret, t); return ret; } diff --git a/aedis/aedis.hpp b/aedis/aedis.hpp index 0ddd9b38..5ec62c42 100644 --- a/aedis/aedis.hpp +++ b/aedis/aedis.hpp @@ -459,40 +459,35 @@ The only thing users have to care about is with the implementation of the \c receiver class, everything else will be done - automatically by the client class. To simplify it even further - users can (but don't have to) use a base class that abstracts most - of the complexity away. The general form of a receiver looks like - this + automatically by the client class. The general form of a receiver + looks like this @code - class myreceiver : public receiver_base { + class receiver { public: - int to_index_impl(command cmd) override + void on_resp3(command cmd, node const& nd, boost::system::error_code& ec) { - // Directs responses to the desired types. - switch (cmd) { - case cmd1: return index_of(); - case cmd2: return index_of(); - case cmd3: return index_of(); - ... - default: return -1; // Ignore a command. - } + // Called when a new chunck of user data becomes available. } - void on_read_impl(command cmd) override + void on_read(command cmd) { - // Called when the response is received. Response available with get(). + // Called when done with a response. It is read for use. } - void on_push_impl() override + void on_write(std::size_t n) + { + // Called when a request has been writen to the socket. + } + + void on_push() { - // Called when a server push is received. Response available with get(). + // Called when a server push is received. } }; @endcode - Where \c T1, \c T2 etc. are any of the response types discussed in \ref low-level-api. Sending - commands is also similar to what has been discussed there. + Sending commands is also similar to what has been discussed there. @code void foo(client& db) @@ -511,50 +506,50 @@ request/response protocol, which means clients must wait for the response to a command before proceeding with the next one. - \subsection high-level-transaction Transactions + \subsection high-level-responses Responses - Aedis won't pass the responses of \c multi or any other - commands inside the transaction to the user, only the final result - i.e. the response to \c exec. The reason - for this behaviour is that unless there is a programming error, - the response to the individual commands that precede \c exec - can't fail, they will always be "QUEUED", or as quoted form the - Redis documentation https://redis.io/topics/transactions: - - > Redis commands can fail only if called with a wrong syntax (and - > the problem is not detectable during the command queueing), - > or against keys holding the wrong data type: this means that in - > practical terms a failing command is the result of a programming - > errors, and a kind of error that is very likely to be detected - > during development, and not in production. - - \subsection high-level-receiver Receiver - - Just as users can implement their own adapter for the low-level API, - they can also implement their own Receiver for the high-level API. - For example + Aedis also provides some facilities to use use custom responses with the + high-level API. Assume for example you have many different custom + response types \c T1, \c T2 etc, a receiver that makes use of this looks + like @code - struct receiver { - void on_resp3(command cmd, type t, std::size_t aggregate_size, std::size_t depth, char const* data, std::size_t size, boost::system::error_code&) - { - std::cout << "Resp3 data received" << std::endl; - } + using responses_tuple_type = std::tuple; + using adapters_tuple_type = adapters_t; - void on_push() + class myreceiver { + public: + myreceiver(...) : adapters_(make_adapters_tuple(resps_)) , {} + + void + on_resp3( command cmd, node const& nd, boost::system::error_code& ec) { - std::cout << "on_push: " << std::endl; + // Direct the responses to the desired adapter. + switch (cmd) { + case cmd1: adapter::get(adapters_)(nd, ec); + case cmd2: adapter::get(adapters_)(nd, ec); + case cmd3: adapter::get(adapters_)(nd, ec); + default:; + } } void on_read(command cmd) { - std::cout << "on_read: " << cmd << std::endl; + switch (cmd) { + case cmd1: // Data on std::get(resps_); break; + case cmd2: // Data on std::get(resps_); break; + case cmd3: // Data on std::get(resps_); break; + default:; + } } - void on_write(std::size_t n) - { - std::cout << "on_write: " << n << std::endl; - } + void on_write(std::size_t n) { ... } + + void on_push() { ... } + + private: + responses_tuple_type resps_; + adapters_tuple_type adapters_; }; @endcode @@ -576,9 +571,8 @@ @li high_level/stl_containers.cpp: Shows how to read responses in STL containers. @li high_level/serialization.cpp: Shows how to de/serialize your own non-aggregate data-structures. @li high_level/subscriber.cpp: Shows how channel subscription works at a high level. See also https://redis.io/topics/pubsub. - @li high_level/receiver.cpp: Customization point for users that want to de/serialize their own data-structures like containers for example. - \b Servers + \b Asynchronous \b Servers @li high_level/chat_room.cpp: Shows how to build a scalable chat room that scales to millions of users. @li high_level/echo_server.cpp: Shows the basic principles behind asynchronous communication with a database in an asynchronous server. In this case, the server is a proxy between the user and Redis. diff --git a/doc/Doxyfile.in b/doc/Doxyfile.in index 4c42994f..9d3928ad 100644 --- a/doc/Doxyfile.in +++ b/doc/Doxyfile.in @@ -823,7 +823,7 @@ WARN_LOGFILE = # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING # Note: If this tag is empty the current directory is searched. -INPUT = include/aedis examples +INPUT = aedis examples # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses diff --git a/examples/high_level/aggregates.cpp b/examples/high_level/aggregates.cpp index 0e8cb69d..c7854fb9 100644 --- a/examples/high_level/aggregates.cpp +++ b/examples/high_level/aggregates.cpp @@ -15,10 +15,9 @@ namespace net = boost::asio; using aedis::resp3::node; -using aedis::redis::command; -using aedis::generic::client; using aedis::adapter::adapt; -using client_type = client; +using aedis::redis::command; +using client_type = aedis::generic::client; using response_type = std::vector>; using adapter_type = aedis::adapter::response_traits_t; @@ -31,24 +30,12 @@ void print_aggregate(response_type const& v) std::cout << "\n"; } -struct myreceiver { -private: - response_type resp_; - adapter_type adapter_; - client_type* db_; - +struct receiver { public: - myreceiver(client_type& db) + receiver(client_type& db) : adapter_{adapt(resp_)} , db_{&db} {} - void on_write(std::size_t n) - { - std::cout << "Number of bytes written: " << n << std::endl; - } - - void on_push() { } - void on_resp3(command cmd, node const& nd, boost::system::error_code& ec) { adapter_(nd, ec); @@ -94,13 +81,25 @@ public: resp_.clear(); } + + void on_write(std::size_t n) + { + std::cout << "Number of bytes written: " << n << std::endl; + } + + void on_push() { } + +private: + response_type resp_; + adapter_type adapter_; + client_type* db_; }; int main() { net::io_context ioc; client_type db{ioc.get_executor()}; - myreceiver recv{db}; + receiver recv{db}; db.async_run( recv, diff --git a/examples/high_level/chat_room.cpp b/examples/high_level/chat_room.cpp index d4de0011..02c9af7a 100644 --- a/examples/high_level/chat_room.cpp +++ b/examples/high_level/chat_room.cpp @@ -5,8 +5,8 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -#include #include +#include #include @@ -17,36 +17,22 @@ namespace net = boost::asio; using aedis::resp3::node; +using aedis::adapter::adapt; using aedis::redis::command; using aedis::generic::client; -using aedis::adapter::adapt; using aedis::user_session; using aedis::user_session_base; using client_type = client; using response_type = std::vector>; using adapter_type = aedis::adapter::response_traits_t; -class myreceiver { -private: - response_type resp_; - adapter_type adapter_; - std::shared_ptr db_; - std::vector> sessions_; - +class receiver { public: - myreceiver(std::shared_ptr db) + receiver(std::shared_ptr db) : adapter_{adapt(resp_)} , db_{db} {} - void on_push() - { - for (auto& session: sessions_) - session->deliver(resp_.at(3).value); - - resp_.clear(); - } - void on_resp3(command cmd, node const& nd, boost::system::error_code& ec) { adapter_(nd, ec); @@ -74,15 +60,29 @@ public: std::cout << "Number of bytes written: " << n << std::endl; } + void on_push() + { + for (auto& session: sessions_) + session->deliver(resp_.at(3).value); + + resp_.clear(); + } + auto add(std::shared_ptr session) { sessions_.push_back(session); } + +private: + response_type resp_; + adapter_type adapter_; + std::shared_ptr db_; + std::vector> sessions_; }; net::awaitable listener( std::shared_ptr acc, std::shared_ptr db, - std::shared_ptr recv) + std::shared_ptr recv) { auto on_user_msg = [db](std::string const& msg) { @@ -104,7 +104,7 @@ int main() net::io_context ioc{1}; auto db = std::make_shared(ioc.get_executor()); - auto recv = std::make_shared(db); + auto recv = std::make_shared(db); db->async_run( *recv, diff --git a/examples/high_level/echo_server.cpp b/examples/high_level/echo_server.cpp index 56c9317d..21a86d29 100644 --- a/examples/high_level/echo_server.cpp +++ b/examples/high_level/echo_server.cpp @@ -18,10 +18,10 @@ #include "user_session.hpp" namespace net = boost::asio; -using aedis::redis::command; using aedis::resp3::node; -using aedis::generic::client; using aedis::adapter::adapt; +using aedis::redis::command; +using aedis::generic::client; using aedis::user_session; using aedis::user_session_base; using client_type = client; @@ -29,11 +29,6 @@ using response_type = std::vector>; using adapter_type = aedis::adapter::response_traits_t; class myreceiver { -private: - response_type resp_; - adapter_type adapter_; - std::queue> sessions_; - public: myreceiver() : adapter_{adapt(resp_)} @@ -71,6 +66,11 @@ public: void add_user_session(std::shared_ptr session) { sessions_.push(session); } + +private: + response_type resp_; + adapter_type adapter_; + std::queue> sessions_; }; net::awaitable diff --git a/examples/high_level/intro.cpp b/examples/high_level/intro.cpp index 4d321e33..82f7c959 100644 --- a/examples/high_level/intro.cpp +++ b/examples/high_level/intro.cpp @@ -26,13 +26,6 @@ public: : adapter_{adapt(resp_)} , db_{&db} {} - void on_write(std::size_t n) - { - std::cout << "Number of bytes written: " << n << std::endl; - } - - void on_push() { } - void on_resp3(command cmd, node const& nd, boost::system::error_code& ec) { adapter_(nd, ec); @@ -54,6 +47,13 @@ public: } } + void on_write(std::size_t n) + { + std::cout << "Number of bytes written: " << n << std::endl; + } + + void on_push() { } + private: response_type resp_; adapter_type adapter_; diff --git a/examples/high_level/serialization.cpp b/examples/high_level/serialization.cpp index 59465dae..07491b17 100644 --- a/examples/high_level/serialization.cpp +++ b/examples/high_level/serialization.cpp @@ -20,20 +20,20 @@ struct mystruct { namespace net = boost::asio; namespace adapter = aedis::adapter; using aedis::resp3::node; -using aedis::redis::command; -using aedis::adapter::adapters_t; +using aedis::adapter::adapters_tuple_t; using aedis::adapter::make_adapters_tuple; using aedis::adapter::get; +using aedis::redis::command; using aedis::generic::client; using client_type = client; -using responses_type = +using responses_tuple_type = std::tuple< - boost::optional, // get - std::list, // lrange - std::set, // smembers - std::map // hgetall + boost::optional, + std::list, + std::set, + std::map >; -using adapters_type = adapters_t; +using adapters_tuple_type = adapters_tuple_t; std::ostream& operator<<(std::ostream& os, mystruct const& obj) { @@ -59,14 +59,9 @@ void from_string(mystruct& obj, boost::string_view sv, boost::system::error_code obj.b = 2; } -class myreceiver { -private: - responses_type resps_; - adapters_type adapters_; - client_type* db_; - +class receiver { public: - myreceiver(client_type& db) + receiver(client_type& db) : adapters_(make_adapters_tuple(resps_)) , db_{&db} {} @@ -77,10 +72,10 @@ public: boost::system::error_code& ec) { switch (cmd) { - case command::get: adapter::get>(adapters_)(nd, ec); - case command::lrange: adapter::get>(adapters_)(nd, ec); - case command::smembers: adapter::get>(adapters_)(nd, ec); - case command::hgetall: adapter::get>(adapters_)(nd, ec); + case command::get: adapter::get>(adapters_)(nd, ec); break; + case command::lrange: adapter::get>(adapters_)(nd, ec); break; + case command::smembers: adapter::get>(adapters_)(nd, ec); break; + case command::hgetall: adapter::get>(adapters_)(nd, ec); break; default:; // Ignore } } @@ -153,15 +148,24 @@ public: } } - void on_write(std::size_t n) { } + void on_write(std::size_t n) + { + std::cout << "Number of bytes written: " << n << std::endl; + } + void on_push() { } + +private: + responses_tuple_type resps_; + adapters_tuple_type adapters_; + client_type* db_; }; int main() { net::io_context ioc; client_type db(ioc.get_executor()); - myreceiver recv{db}; + receiver recv{db}; db.async_run( recv, diff --git a/examples/high_level/stl_containers.cpp b/examples/high_level/stl_containers.cpp index 8970e9e6..6f85beae 100644 --- a/examples/high_level/stl_containers.cpp +++ b/examples/high_level/stl_containers.cpp @@ -18,16 +18,16 @@ using aedis::resp3::node; using aedis::redis::command; using aedis::generic::client; using aedis::adapter::adapt; -using aedis::adapter::adapters_t; +using aedis::adapter::adapters_tuple_t; using aedis::adapter::make_adapters_tuple; using client_type = client; -using responses_type = +using responses_tuple_type = std::tuple< std::list, boost::optional>, std::vector> >; -using adapters_type = adapters_t; +using adapters_tuple_type = adapters_tuple_t; template void print_and_clear(Container& cont) @@ -51,19 +51,12 @@ public: boost::system::error_code& ec) { switch (cmd) { - case command::lrange: adapter::get>(adapters_)(nd, ec); - case command::smembers: adapter::get>>(adapters_)(nd, ec); + case command::lrange: adapter::get>(adapters_)(nd, ec); break; + case command::smembers: adapter::get>>(adapters_)(nd, ec); break; default:; } } - void on_write(std::size_t n) - { - std::cout << "Number of bytes written: " << n << std::endl; - } - - void on_push() { } - void on_read(command cmd) { switch (cmd) { @@ -105,9 +98,16 @@ public: } } + void on_write(std::size_t n) + { + std::cout << "Number of bytes written: " << n << std::endl; + } + + void on_push() { } + private: - responses_type resps_; - adapters_type adapters_; + responses_tuple_type resps_; + adapters_tuple_type adapters_; client_type* db_; }; diff --git a/examples/high_level/subscriber.cpp b/examples/high_level/subscriber.cpp index c8cf0302..ad6dd621 100644 --- a/examples/high_level/subscriber.cpp +++ b/examples/high_level/subscriber.cpp @@ -41,27 +41,11 @@ public: : adapter_{adapt(resp_)} , db_{&db} {} - void on_write(std::size_t n) - { - std::cout << "Number of bytes written: " << n << std::endl; - } - void on_resp3(command cmd, node const& nd, boost::system::error_code& ec) { adapter_(nd, ec); } - void on_push() - { - std::cout - << "Event: " << resp_.at(1).value << "\n" - << "Channel: " << resp_.at(2).value << "\n" - << "Message: " << resp_.at(3).value << "\n" - << std::endl; - - resp_.clear(); - } - void on_read(command cmd) { switch (cmd) { @@ -74,6 +58,22 @@ public: resp_.clear(); } + void on_write(std::size_t n) + { + std::cout << "Number of bytes written: " << n << std::endl; + } + + void on_push() + { + std::cout + << "Event: " << resp_.at(1).value << "\n" + << "Channel: " << resp_.at(2).value << "\n" + << "Message: " << resp_.at(3).value << "\n" + << std::endl; + + resp_.clear(); + } + private: response_type resp_; adapter_type adapter_; @@ -93,4 +93,3 @@ int main() ioc.run(); } -