2
0
mirror of https://github.com/boostorg/redis.git synced 2026-02-02 21:12:16 +00:00

Compare commits

...

55 Commits

Author SHA1 Message Date
Marcelo Zimbres
19f03e2f41 Changes version. 2022-04-08 12:45:35 +02:00
Marcelo Zimbres
0fff6496fb Improvements in the documentation. 2022-04-08 12:44:45 +02:00
Marcelo Zimbres
57ba376544 Improvements in the documentation. 2022-04-07 23:35:13 +02:00
Marcelo Zimbres
46b86c20e5 Fixes documentation and some examples. 2022-04-07 21:08:03 +02:00
Marcelo Zimbres
577c6d35fb Changes how receivers work. 2022-04-07 16:14:29 +02:00
Marcelo Zimbres
42ef8a3b06 Change signature of adapters. 2022-04-07 09:32:10 +02:00
Marcelo Zimbres
f9af8e585b Unifies the array adapters. 2022-04-07 08:45:24 +02:00
Marcelo Zimbres
350ae19936 Removes typedef. 2022-04-06 20:23:13 +02:00
Marcelo Zimbres
57f2644903 Fixes the build. 2022-04-06 17:25:24 +02:00
Marcelo Zimbres
0ac0c3cf23 Improvements in the docs. 2022-04-05 23:33:20 +02:00
Marcelo Zimbres
5c23299a8a Ports to C++14. 2022-04-05 19:47:20 +02:00
Marcelo Zimbres
379da7a340 Some code improvements. 2022-04-04 22:38:02 +02:00
Marcelo Zimbres
bcf13c03c0 Docs. 2022-04-03 21:42:01 +02:00
Marcelo Zimbres
be79f58808 Simplifies the serializer. 2022-04-03 20:54:39 +02:00
Marcelo Zimbres
2c96b07623 More fixes in the docs. 2022-04-03 12:26:28 +02:00
Marcelo Zimbres
577d32f0e2 Adds low-level adapter examplea and fixes example exe names. 2022-04-02 22:16:41 +02:00
Marcelo Zimbres
5f93257502 One more example. 2022-04-02 16:30:32 +02:00
Marcelo Zimbres
409ee61522 Make the low_level tests independent of coroutine support. 2022-04-02 16:17:17 +02:00
Marcelo Zimbres
5aa034d6a5 Some improvements. 2022-03-30 21:50:28 +02:00
Marcelo Zimbres
0ff0e537ce Improvements. 2022-03-28 23:03:22 +02:00
Marcelo Zimbres
320ee6b3cc Fixes adapter for general aggregates. 2022-03-27 21:13:03 +02:00
Marcelo Zimbres
5061e5a7a6 More improvements in the tests. 2022-03-27 11:28:33 +02:00
Marcelo Zimbres
0bda78dd9c Some refactoring and improvements in the docs. 2022-03-26 21:28:56 +01:00
Marcelo Zimbres
8704e7756f More improvements in the tests. 2022-03-20 21:18:59 +01:00
Marcelo Zimbres
8207ef7e8a Improvements in the tests. 2022-03-20 12:02:42 +01:00
Marcelo Zimbres
4712872daf Many improvements. 2022-03-19 20:55:58 +01:00
Marcelo Zimbres
06752ac664 Adds low level api example. 2022-03-13 20:56:56 +01:00
Marcelo Zimbres
b3be596f77 Moves files around. 2022-03-13 20:43:13 +01:00
Marcelo Zimbres
f92290be16 Improvements in the docs. 2022-03-13 11:59:47 +01:00
Marcelo Zimbres
331010c240 More documentation. 2022-03-12 21:58:28 +01:00
Marcelo Zimbres
11b697c572 Progresses with docs. 2022-03-12 11:08:23 +01:00
Marcelo Zimbres
1218b2ef01 Adds on_push to the receiver. 2022-03-06 21:40:57 +01:00
Marcelo Zimbres
8cc142d55b Many improvements in the docs. 2022-03-06 11:34:59 +01:00
Marcelo Zimbres
0723922ac1 Fixes many bugs in the adapters. 2022-03-04 23:21:02 +01:00
Marcelo Zimbres
54ba34c70c More progresses with examples. 2022-03-02 22:10:57 +01:00
Marcelo Zimbres
efe44fbd45 Simplifies the receiver. 2022-02-28 22:03:51 +01:00
Marcelo Zimbres
268b59afbc More progress. 2022-02-27 21:06:43 +01:00
Marcelo Zimbres
6b6272694c More improvements in the client. 2022-02-26 23:07:34 +01:00
Marcelo Zimbres
f36f3b7b5e Improvements in the code. 2022-02-26 11:26:55 +01:00
Marcelo Zimbres
b4fef73b87 More improvements in the examples. 2022-02-23 23:12:42 +01:00
Marcelo Zimbres
c27b3b6b85 Some improvements in the code. 2022-02-22 21:44:37 +01:00
Marcelo Zimbres
feb383d7e2 Fixes some examples. 2022-02-20 20:00:56 +01:00
Marcelo Zimbres
ee71ff885d Adds single async_run function. 2022-02-20 11:19:34 +01:00
Marcelo Zimbres
80a80f44ff Progresses with the high level api. 2022-02-19 23:05:49 +01:00
Marcelo Zimbres
053ce3aea9 Adds async_run function. 2022-02-19 19:48:17 +01:00
Marcelo Zimbres
db27e3cc60 Improves intro example. 2022-02-19 17:13:02 +01:00
Marcelo Zimbres
449ba0dd73 More improvements. 2022-02-19 13:35:49 +01:00
Marcelo Zimbres
8728f58981 Improvements in the client class. 2022-02-19 13:05:25 +01:00
Marcelo Zimbres
2573f39aa1 More improvements in the client. 2022-02-13 16:20:03 +01:00
Marcelo Zimbres
f460bf43a5 Using vector instead of queue. 2022-02-13 09:16:39 +01:00
Marcelo Zimbres
85d0a30dad Improvements in the client.
- Implements the writer with lightwait coroutines.
2022-02-12 19:58:31 +01:00
Marcelo Zimbres
b7f22d8c61 More improvements in the examples. 2022-02-12 16:31:36 +01:00
Marcelo Zimbres
b04cc12d64 Lots of improvements. 2022-02-10 22:05:40 +01:00
Marcelo Zimbres
317690ee91 Missing files. 2022-02-07 09:31:41 +01:00
Marcelo Zimbres
fe3cc2efb0 Fixes doc with release path. 2022-02-05 22:28:34 +01:00
67 changed files with 5475 additions and 3890 deletions

View File

@@ -6,30 +6,32 @@ DISTCHECK_CONFIGURE_FLAGS = CPPFLAGS="$(BOOST_CPPFLAGS) $(CPPFLAGS)" LDFLAGS="$(
AM_CPPFLAGS =
AM_CPPFLAGS += $(BOOST_CPPFLAGS)
#AM_CPPFLAGS += -I$(top_srcdir)/include
AM_LDFLAGS =
AM_LDFLAGS += -pthread
check_PROGRAMS =
check_PROGRAMS += intro
check_PROGRAMS += sets
check_PROGRAMS += hashes
check_PROGRAMS += serialization
check_PROGRAMS += multipurpose_response
check_PROGRAMS += lists
check_PROGRAMS += key_expiration
check_PROGRAMS += response_adapter
check_PROGRAMS += sync
check_PROGRAMS += multipurpose_client
check_PROGRAMS += test_offline
check_PROGRAMS += low_level_sync_intro
check_PROGRAMS += high_level_intro
check_PROGRAMS += high_level_aggregates
check_PROGRAMS += high_level_stl_containers
check_PROGRAMS += high_level_serialization
check_PROGRAMS += test_low_level
if HAVE_CXX20
check_PROGRAMS += low_level_async_intro
check_PROGRAMS += low_level_adapter
check_PROGRAMS += test_online
check_PROGRAMS += transaction
endif
EXTRA_PROGRAMS =
EXTRA_PROGRAMS += subscriber
EXTRA_PROGRAMS += echo_server
EXTRA_PROGRAMS += chat_room
EXTRA_PROGRAMS += high_level_subscriber
EXTRA_PROGRAMS += commands
if HAVE_CXX20
EXTRA_PROGRAMS += low_level_subscriber
EXTRA_PROGRAMS += high_level_echo_server
EXTRA_PROGRAMS += high_level_chat_room
endif
CLEANFILES =
CLEANFILES += $(EXTRA_PROGRAMS)
@@ -37,55 +39,52 @@ CLEANFILES += $(EXTRA_PROGRAMS)
.PHONY: all
all: $(check_PROGRAMS) $(EXTRA_PROGRAMS)
intro_SOURCES = $(top_srcdir)/examples/intro.cpp
sets_SOURCES = $(top_srcdir)/examples/sets.cpp
hashes_SOURCES = $(top_srcdir)/examples/hashes.cpp
serialization_SOURCES = $(top_srcdir)/examples/serialization.cpp
multipurpose_response_SOURCES = $(top_srcdir)/examples/multipurpose_response.cpp
lists_SOURCES = $(top_srcdir)/examples/lists.cpp
key_expiration_SOURCES = $(top_srcdir)/examples/key_expiration.cpp
response_adapter_SOURCES = $(top_srcdir)/examples/response_adapter.cpp
sync_SOURCES = $(top_srcdir)/examples/sync.cpp
multipurpose_client_SOURCES = $(top_srcdir)/examples/multipurpose_client.cpp
high_level_intro_SOURCES = $(top_srcdir)/examples/high_level/intro.cpp
high_level_aggregates_SOURCES = $(top_srcdir)/examples/high_level/aggregates.cpp
high_level_stl_containers_SOURCES = $(top_srcdir)/examples/high_level/stl_containers.cpp
high_level_serialization_SOURCES = $(top_srcdir)/examples/high_level/serialization.cpp
low_level_sync_intro_SOURCES = $(top_srcdir)/examples/low_level/sync_intro.cpp
commands_SOURCES = $(top_srcdir)/tools/commands.cpp
test_offline_SOURCES = $(top_srcdir)/tests/offline.cpp
high_level_subscriber_SOURCES = $(top_srcdir)/examples/high_level/subscriber.cpp
test_low_level_SOURCES = $(top_srcdir)/tests/low_level.cpp
if HAVE_CXX20
test_online_SOURCES = $(top_srcdir)/tests/online.cpp
subscriber_SOURCES = $(top_srcdir)/examples/subscriber.cpp
echo_server_SOURCES = $(top_srcdir)/examples/echo_server.cpp
chat_room_SOURCES = $(top_srcdir)/examples/chat_room.cpp
transaction_SOURCES = $(top_srcdir)/examples/transaction.cpp
low_level_async_intro_SOURCES = $(top_srcdir)/examples/low_level/async_intro.cpp
low_level_subscriber_SOURCES = $(top_srcdir)/examples/low_level/subscriber.cpp
low_level_adapter_SOURCES = $(top_srcdir)/examples/low_level/adapter.cpp
high_level_echo_server_SOURCES = $(top_srcdir)/examples/high_level/echo_server.cpp
high_level_chat_room_SOURCES = $(top_srcdir)/examples/high_level/chat_room.cpp
endif
nobase_include_HEADERS =\
$(top_srcdir)/aedis/config.hpp\
$(top_srcdir)/aedis/src.hpp\
$(top_srcdir)/aedis/redis/command.hpp\
$(top_srcdir)/aedis/generic/client.hpp\
$(top_srcdir)/aedis/generic/serializer.hpp\
$(top_srcdir)/aedis/generic/detail/client_ops.hpp\
$(top_srcdir)/aedis/sentinel/command.hpp\
$(top_srcdir)/aedis/aedis.hpp\
$(top_srcdir)/aedis/resp3/adapter/detail/adapters.hpp\
$(top_srcdir)/aedis/resp3/adapter/error.hpp\
$(top_srcdir)/aedis/resp3/adapt.hpp\
$(top_srcdir)/aedis/resp3/detail/composer.hpp\
$(top_srcdir)/aedis/adapter/detail/adapters.hpp\
$(top_srcdir)/aedis/adapter/error.hpp\
$(top_srcdir)/aedis/adapter/impl/error.ipp\
$(top_srcdir)/aedis/adapter/adapt.hpp\
$(top_srcdir)/aedis/adapter/response_traits.hpp\
$(top_srcdir)/aedis/resp3/node.hpp\
$(top_srcdir)/aedis/resp3/compose.hpp\
$(top_srcdir)/aedis/resp3/detail/read_ops.hpp\
$(top_srcdir)/aedis/resp3/detail/parser.hpp\
$(top_srcdir)/aedis/resp3/serializer.hpp\
$(top_srcdir)/aedis/resp3/response_traits.hpp\
$(top_srcdir)/aedis/resp3/node.hpp\
$(top_srcdir)/aedis/resp3/error.hpp\
$(top_srcdir)/aedis/resp3/impl/error.ipp\
$(top_srcdir)/aedis/resp3/type.hpp\
$(top_srcdir)/aedis/resp3/read.hpp\
$(top_srcdir)/aedis/redis/impl/command.ipp\
$(top_srcdir)/aedis/sentinel/impl/command.ipp\
$(top_srcdir)/aedis/resp3/detail/impl/parser.ipp\
$(top_srcdir)/aedis/resp3/impl/type.ipp\
$(top_srcdir)/aedis/resp3/impl/node.ipp\
$(top_srcdir)/aedis/redis/experimental/client.hpp\
$(top_srcdir)/aedis/redis/experimental/impl/client.ipp
$(top_srcdir)/aedis/resp3/impl/type.ipp
nobase_noinst_HEADERS =\
$(top_srcdir)/examples/lib/net_utils.hpp\
$(top_srcdir)/examples/lib/user_session.hpp\
$(top_srcdir)/tests/check.hpp\
$(top_srcdir)/tests/test_stream.hpp
$(top_srcdir)/examples/high_level/user_session.hpp\
$(top_srcdir)/tests/check.hpp
TESTS = $(check_PROGRAMS)

68
aedis/adapter/adapt.hpp Normal file
View File

@@ -0,0 +1,68 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <aedis/adapter/response_traits.hpp>
namespace aedis {
namespace adapter {
/** \brief Creates a void response adapter.
\ingroup any
The adapter returned by this function ignores responses and is
useful to avoid wasting time with responses on which the user is
not insterested in.
Example usage:
@code
co_await async_read(socket, buffer, adapt());
@endcode
*/
inline
auto adapt() noexcept
{ return response_traits<void>::adapt(); }
/** \brief Adapts user data to read operations.
* \ingroup any
*
* All STL containers, \c std::tuple and built-in types are supported and
* can be used in conjunction with \c boost::optional<T>.
*
* Example usage:
*
* @code
* std::unordered_map<std::string, std::string> cont;
* co_await async_read(socket, buffer, adapt(cont));
* @endcode
*
* For a transaction
*
* @code
* sr.push(command::multi);
* sr.push(command::ping, ...);
* sr.push(command::incr, ...);
* sr.push_range(command::rpush, ...);
* sr.push(command::lrange, ...);
* sr.push(command::incr, ...);
* sr.push(command::exec);
*
* co_await async_write(socket, buffer(request));
*
* // Reads the response to a transaction
* std::tuple<std::string, int, int, std::vector<std::string>, int> execs;
* co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(execs));
* @endcode
*/
template<class T>
auto adapt(T& t) noexcept
{ return response_traits<T>::adapt(t); }
} // adapter
} // aedis

View File

@@ -0,0 +1,401 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <set>
#include <unordered_set>
#include <forward_list>
#include <system_error>
#include <map>
#include <unordered_map>
#include <list>
#include <deque>
#include <vector>
#include <array>
#include <boost/optional.hpp>
#include <boost/utility/string_view.hpp>
#include <aedis/resp3/type.hpp>
#include <aedis/resp3/detail/parser.hpp>
#include <aedis/generic/serializer.hpp>
#include <aedis/resp3/node.hpp>
#include <aedis/adapter/error.hpp>
namespace aedis {
namespace adapter {
namespace detail {
// Serialization.
template <class T>
typename std::enable_if<std::is_integral<T>::value, void>::type
from_string(
T& i,
boost::string_view sv,
boost::system::error_code& ec)
{
i = resp3::detail::parse_uint(sv.data(), sv.size(), ec);
}
void from_string(
bool& t,
boost::string_view sv,
boost::system::error_code& ec)
{
t = *sv.data() == 't';
}
template <class CharT, class Traits, class Allocator>
void
from_string(
std::basic_string<CharT, Traits, Allocator>& s,
boost::string_view sv,
boost::system::error_code&)
{
s.append(sv.data(), sv.size());
}
//================================================
void set_on_resp3_error(resp3::type t, boost::system::error_code& ec)
{
switch (t) {
case resp3::type::simple_error: ec = adapter::error::simple_error; return;
case resp3::type::blob_error: ec = adapter::error::blob_error; return;
case resp3::type::null: ec = adapter::error::null; return;
default: return;
}
}
template <class Result>
class general_aggregate {
private:
Result* result_;
public:
general_aggregate(Result* c = nullptr): result_(c) {}
void operator()(resp3::node<boost::string_view> const& n, boost::system::error_code&)
{
result_->push_back({n.data_type, n.aggregate_size, n.depth, std::string{std::cbegin(n.value), std::cend(n.value)}});
}
};
template <class Node>
class general_simple {
private:
Node* result_;
public:
general_simple(Node* t = nullptr) : result_(t) {}
void operator()(resp3::node<boost::string_view> const& n, boost::system::error_code&)
{
result_->data_type = n.data_type;
result_->aggregate_size = n.aggregate_size;
result_->depth = n.depth;
result_->value.assign(n.value.data(), n.value.size());
}
};
template <class Result>
class simple_impl {
public:
void on_value_available(Result&) {}
void
operator()(
Result& result,
resp3::node<boost::string_view> const& n,
boost::system::error_code& ec)
{
set_on_resp3_error(n.data_type, ec);
if (ec)
return;
if (is_aggregate(n.data_type)) {
ec = adapter::error::expects_simple_type;
return;
}
from_string(result, n.value, ec);
}
};
template <class Result>
class set_impl {
private:
typename Result::iterator hint_;
public:
void on_value_available(Result& result)
{ hint_ = std::end(result); }
void
operator()(
Result& result,
resp3::node<boost::string_view> const& nd,
boost::system::error_code& ec)
{
set_on_resp3_error(nd.data_type, ec);
if (ec)
return;
if (is_aggregate(nd.data_type)) {
if (nd.data_type != resp3::type::set)
ec = error::expects_set_aggregate;
return;
}
assert(nd.aggregate_size == 1);
if (nd.depth < 1) {
ec = adapter::error::expects_set_aggregate;
return;
}
typename Result::key_type obj;
from_string(obj, nd.value, ec);
hint_ = result.insert(hint_, std::move(obj));
}
};
template <class Result>
class map_impl {
private:
typename Result::iterator current_;
bool on_key_ = true;
public:
void on_value_available(Result& result)
{ current_ = std::end(result); }
void
operator()(
Result& result,
resp3::node<boost::string_view> const& nd,
boost::system::error_code& ec)
{
set_on_resp3_error(nd.data_type, ec);
if (ec)
return;
if (is_aggregate(nd.data_type)) {
if (element_multiplicity(nd.data_type) != 2)
ec = error::expects_map_like_aggregate;
return;
}
assert(nd.aggregate_size == 1);
if (nd.depth < 1) {
ec = adapter::error::expects_map_like_aggregate;
return;
}
if (on_key_) {
typename Result::key_type obj;
from_string(obj, nd.value, ec);
current_ = result.insert(current_, {std::move(obj), {}});
} else {
typename Result::mapped_type obj;
from_string(obj, nd.value, ec);
current_->second = std::move(obj);
}
on_key_ = !on_key_;
}
};
template <class Result>
class vector_impl {
public:
void on_value_available(Result& ) { }
void
operator()(
Result& result,
resp3::node<boost::string_view> const& nd,
boost::system::error_code& ec)
{
set_on_resp3_error(nd.data_type, ec);
if (ec)
return;
if (is_aggregate(nd.data_type)) {
auto const m = element_multiplicity(nd.data_type);
result.reserve(result.size() + m * nd.aggregate_size);
} else {
result.push_back({});
from_string(result.back(), nd.value, ec);
}
}
};
template <class Result>
class array_impl {
private:
int i_ = -1;
public:
void on_value_available(Result& ) { }
void
operator()(
Result& result,
resp3::node<boost::string_view> const& nd,
boost::system::error_code& ec)
{
set_on_resp3_error(nd.data_type, ec);
if (ec)
return;
if (is_aggregate(nd.data_type)) {
if (i_ != -1) {
ec = adapter::error::nested_aggregate_unsupported;
return;
}
if (result.size() != nd.aggregate_size * element_multiplicity(nd.data_type)) {
ec = error::incompatible_size;
return;
}
} else {
if (i_ == -1) {
ec = adapter::error::expects_aggregate;
return;
}
assert(nd.aggregate_size == 1);
from_string(result.at(i_), nd.value, ec);
}
++i_;
}
};
template <class Result>
struct list_impl {
void on_value_available(Result& ) { }
void
operator()(
Result& result,
resp3::node<boost::string_view> const& nd,
boost::system::error_code& ec)
{
set_on_resp3_error(nd.data_type, ec);
if (ec)
return;
if (!is_aggregate(nd.data_type)) {
assert(nd.aggregate_size == 1);
if (nd.depth < 1) {
ec = adapter::error::expects_aggregate;
return;
}
result.push_back({});
from_string(result.back(), nd.value, ec);
}
}
};
//---------------------------------------------------
template <class T>
struct impl_map { using type = simple_impl<T>; };
template <class Key, class Compare, class Allocator>
struct impl_map<std::set<Key, Compare, Allocator>> { using type = set_impl<std::set<Key, Compare, Allocator>>; };
template <class Key, class Compare, class Allocator>
struct impl_map<std::multiset<Key, Compare, Allocator>> { using type = set_impl<std::multiset<Key, Compare, Allocator>>; };
template <class Key, class Hash, class KeyEqual, class Allocator>
struct impl_map<std::unordered_set<Key, Hash, KeyEqual, Allocator>> { using type = set_impl<std::unordered_set<Key, Hash, KeyEqual, Allocator>>; };
template <class Key, class Hash, class KeyEqual, class Allocator>
struct impl_map<std::unordered_multiset<Key, Hash, KeyEqual, Allocator>> { using type = set_impl<std::unordered_multiset<Key, Hash, KeyEqual, Allocator>>; };
template <class Key, class T, class Compare, class Allocator>
struct impl_map<std::map<Key, T, Compare, Allocator>> { using type = map_impl<std::map<Key, T, Compare, Allocator>>; };
template <class Key, class T, class Compare, class Allocator>
struct impl_map<std::multimap<Key, T, Compare, Allocator>> { using type = map_impl<std::multimap<Key, T, Compare, Allocator>>; };
template <class Key, class Hash, class KeyEqual, class Allocator>
struct impl_map<std::unordered_map<Key, Hash, KeyEqual, Allocator>> { using type = map_impl<std::unordered_map<Key, Hash, KeyEqual, Allocator>>; };
template <class Key, class Hash, class KeyEqual, class Allocator>
struct impl_map<std::unordered_multimap<Key, Hash, KeyEqual, Allocator>> { using type = map_impl<std::unordered_multimap<Key, Hash, KeyEqual, Allocator>>; };
template <class T, class Allocator>
struct impl_map<std::vector<T, Allocator>> { using type = vector_impl<std::vector<T, Allocator>>; };
template <class T, std::size_t N>
struct impl_map<std::array<T, N>> { using type = array_impl<std::array<T, N>>; };
template <class T, class Allocator>
struct impl_map<std::list<T, Allocator>> { using type = list_impl<std::list<T, Allocator>>; };
template <class T, class Allocator>
struct impl_map<std::deque<T, Allocator>> { using type = list_impl<std::deque<T, Allocator>>; };
//---------------------------------------------------
template <class Result>
class wrapper {
private:
Result* result_;
typename impl_map<Result>::type impl_;
public:
wrapper(Result* t = nullptr) : result_(t)
{ impl_.on_value_available(*result_); }
void
operator()(
resp3::node<boost::string_view> const& nd,
boost::system::error_code& ec)
{
assert(result_);
impl_(*result_, nd, ec);
}
};
template <class T>
class wrapper<boost::optional<T>> {
private:
boost::optional<T>* result_;
typename impl_map<T>::type impl_;
public:
wrapper(boost::optional<T>* o = nullptr) : result_(o), impl_{} {}
void
operator()(
resp3::node<boost::string_view> const& nd,
boost::system::error_code& ec)
{
if (nd.data_type == resp3::type::null)
return;
if (!result_->has_value()) {
*result_ = T{};
impl_.on_value_available(result_->value());
}
impl_(result_->value(), nd, ec);
}
};
} // detail
} // adapter
} // aedis

66
aedis/adapter/error.hpp Normal file
View File

@@ -0,0 +1,66 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <system_error>
namespace aedis {
namespace adapter {
/** \brief Errors that may occurr when reading a response.
* \ingroup any
*/
enum class error
{
/// Expects a simple RESP3 type but got an aggregate.
expects_simple_type = 1,
/// Expects aggregate type.
expects_aggregate,
/// Expects a map but got other aggregate.
expects_map_like_aggregate,
/// Expects a set aggregate but got something else.
expects_set_aggregate,
/// Nested response not supported.
nested_aggregate_unsupported,
/// Got RESP3 simple error.
simple_error,
/// Got RESP3 blob_error.
blob_error,
/// Aggregate container has incompatible size.
incompatible_size,
/// Got RESP3 null type.
null
};
/** \brief todo
* \ingroup any
*/
boost::system::error_code make_error_code(error e);
/** \brief todo
* \ingroup any
*/
boost::system::error_condition make_error_condition(error e);
} // adapter
} // aedis
namespace std {
template<>
struct is_error_code_enum<::aedis::adapter::error> : std::true_type {};
} // std

View File

@@ -0,0 +1,59 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <system_error>
namespace aedis {
namespace adapter {
namespace detail {
struct error_category_impl : boost::system::error_category {
char const* name() const noexcept override
{
return "aedis.adapter";
}
std::string message(int ev) const override
{
switch(static_cast<error>(ev)) {
case error::expects_simple_type: return "Expects a simple RESP3 type";
case error::expects_aggregate: return "Expects aggregate type";
case error::expects_map_like_aggregate: return "Expects map aggregate";
case error::expects_set_aggregate: return "Expects set aggregate";
case error::nested_aggregate_unsupported: return "Nested aggregate unsupported.";
case error::simple_error: return "Got RESP3 simple-error.";
case error::blob_error: return "Got RESP3 blob-error.";
case error::incompatible_size: return "Aggregate container has incompatible size.";
case error::null: return "Got RESP3 null.";
default: assert(false);
}
}
};
boost::system::error_category const& category()
{
static error_category_impl instance;
return instance;
}
} // detail
boost::system::error_code make_error_code(error e)
{
return boost::system::error_code{static_cast<int>(e), detail::category()};
}
boost::system::error_condition make_error_condition(error e)
{
return boost::system::error_condition(static_cast<int>(e), detail::category());
}
} // adapter
} // aedis

View File

@@ -0,0 +1,234 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <vector>
#include <tuple>
#include <boost/mp11.hpp>
#include <boost/variant2.hpp>
#include <aedis/resp3/type.hpp>
#include <aedis/resp3/read.hpp>
#include <aedis/adapter/detail/adapters.hpp>
#include <aedis/adapter/error.hpp>
namespace aedis {
namespace adapter {
/** @brief Traits class for response objects.
* @ingroup any
*
* Provides traits for all supported response types i.e. all STL containers
* and C++ buil-in types.
*/
template <class ResponseType>
struct response_traits
{
/// The adapter type.
using adapter_type = adapter::detail::wrapper<ResponseType>;
/** @brief Returns an adapter for the reponse r
*
* @param r The response object e.g a C++ container.
* @return An adapter suitable for use in resp3::read or resp3::async_read.
* @remark Users can also use the free adapt function for type deduction.
*/
static auto adapt(ResponseType& r) noexcept { return adapter_type{&r}; }
};
/// Template typedef for response_traits.
template <class T>
using adapter_t = typename response_traits<T>::adapter_type;
template <class T>
struct response_traits<resp3::node<T>>
{
using response_type = resp3::node<T>;
using adapter_type = adapter::detail::general_simple<response_type>;
static auto adapt(response_type& v) noexcept { return adapter_type{&v}; }
};
template <class String, class Allocator>
struct response_traits<std::vector<resp3::node<String>, Allocator>>
{
using response_type = std::vector<resp3::node<String>, Allocator>;
using adapter_type = adapter::detail::general_aggregate<response_type>;
static auto adapt(response_type& v) noexcept { return adapter_type{&v}; }
};
template <>
struct response_traits<void>
{
using response_type = void;
using adapter_type = resp3::detail::ignore_response;
static auto adapt() noexcept { return adapter_type{}; }
};
namespace detail {
// Duplicated here to avoid circular include dependency.
template<class T>
auto internal_adapt(T& t) noexcept
{ return response_traits<T>::adapt(t); }
template <std::size_t N>
struct assigner {
template <class T1, class T2>
static void assign(T1& dest, T2& from)
{
dest[N] = internal_adapt(std::get<N>(from));
assigner<N - 1>::assign(dest, from);
}
};
template <>
struct assigner<0> {
template <class T1, class T2>
static void assign(T1& dest, T2& from)
{
dest[0] = internal_adapt(std::get<0>(from));
}
};
template <std::size_t N>
struct assigner2 {
template <class T1, class T2>
static void assign(T1& dest, T2& from)
{
std::get<N>(dest) = internal_adapt(std::get<N>(from));
assigner2<N - 1>::assign(dest, from);
}
};
template <>
struct assigner2<0> {
template <class T1, class T2>
static void assign(T1& dest, T2& from)
{
std::get<0>(dest) = internal_adapt(std::get<0>(from));
}
};
} // 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 <class T, class Tuple>
auto& get(Tuple& t)
{
return std::get<typename response_traits<T>::adapter_type>(t);
}
template <class Tuple>
using adapters_array_t =
std::array<
boost::mp11::mp_unique<
boost::mp11::mp_rename<
boost::mp11::mp_transform<
adapter_t, Tuple>,
boost::variant2::variant>>,
std::tuple_size<Tuple>::value>;
template <class Tuple>
adapters_array_t<Tuple> make_adapters_array(Tuple& t)
{
adapters_array_t<Tuple> ret;
detail::assigner<std::tuple_size<Tuple>::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 <class Tuple>
using adapters_tuple_t =
boost::mp11::mp_rename<
boost::mp11::mp_transform<
adapter_t, Tuple>,
std::tuple>;
/** @brief Make a tuple of adapters.
*
* \param t Tuple of responses.
* \return Tuple of adapters.
*/
template <class Tuple>
auto
make_adapters_tuple(Tuple& t)
{
adapters_tuple_t<Tuple> ret;
detail::assigner2<std::tuple_size<Tuple>::value - 1>::assign(ret, t);
return ret;
}
namespace detail {
template <class Tuple>
class static_aggregate_adapter {
private:
std::size_t i_ = 0;
std::size_t aggregate_size_ = 0;
adapters_array_t<Tuple> adapters_;
public:
static_aggregate_adapter(Tuple* r = nullptr)
: adapters_(make_adapters_array(*r))
{}
void count(resp3::node<boost::string_view> const& nd)
{
if (nd.depth == 1) {
if (is_aggregate(nd.data_type))
aggregate_size_ = element_multiplicity(nd.data_type) * nd.aggregate_size;
else
++i_;
return;
}
if (--aggregate_size_ == 0)
++i_;
}
void
operator()(
resp3::node<boost::string_view> const& nd,
boost::system::error_code& ec)
{
using boost::variant2::visit;
if (nd.depth == 0) {
auto const real_aggr_size = nd.aggregate_size * element_multiplicity(nd.data_type);
if (real_aggr_size != std::tuple_size<Tuple>::value)
ec = error::incompatible_size;
return;
}
visit([&](auto& arg){arg(nd, ec);}, adapters_[i_]);
count(nd);
}
};
} // detail
template <class... Ts>
struct response_traits<std::tuple<Ts...>>
{
using response_type = std::tuple<Ts...>;
using adapter_type = detail::static_aggregate_adapter<response_type>;
static auto adapt(response_type& r) noexcept { return adapter_type{&r}; }
};
} // adapter
} // aedis

View File

@@ -7,91 +7,569 @@
#pragma once
#include <aedis/config.hpp>
#include <aedis/resp3/read.hpp>
#include <aedis/adapter/adapt.hpp>
#include <aedis/adapter/error.hpp>
#include <aedis/redis/command.hpp>
#include <aedis/sentinel/command.hpp>
#include <aedis/resp3/type.hpp>
#include <aedis/resp3/read.hpp>
#include <aedis/resp3/adapt.hpp>
#include <aedis/resp3/error.hpp>
#include <aedis/resp3/serializer.hpp>
#include <aedis/resp3/response_traits.hpp>
#include <aedis/redis/experimental/client.hpp>
#include <aedis/generic/client.hpp>
#include <aedis/generic/serializer.hpp>
/** \mainpage Documentation
\tableofcontents
\section Overview
Aedis is low-level redis client library built on top of Boost.Asio
that implements communication with a Redis server over its native
protocol RESP3. It has first-class support for STL containers and
C++ built in types among other things. You will be able to
implement your own redis client or use a general purpose provided
by the library. For more information about Redis see
https://redis.io/
Aedis is a [Redis](https://redis.io/) client library built on top
of [Asio](https://www.boost.org/doc/libs/release/doc/html/boost_asio.html)
that provides simple and efficient communication with a Redis
server. Some of its distinctive features are
@li Support for the latest version of the Redis communication protocol [RESP3](https://github.com/antirez/RESP3/blob/master/spec.md).
@li First class support for STL containers and C++ built-in types.
@li Serialization and deserialization of your own data types that avoid unnecessary copies.
@li Support for Redis [sentinel](https://redis.io/docs/manual/sentinel).
@li Sync and async API.
In addition to that, Aedis provides a high level client that offers the following functionality
@li Management of message queues.
@li Simplified handling of server pushes.
@li Zero asymptotic allocations by means of memory reuse.
If you never heard about Redis the best place to start is on
https://redis.io. Now let us have a look at the low-level API.
\section low-level-api Low-level API
The low-level API is very useful for simple tasks, for example,
assume we want to perform the following steps
@li Set the value of a Redis key.
@li Set the expiration of that key to two seconds.
@li Get and return its old value.
@li Quit
The async coroutine-based implementation of the steps above look like
@code
net::awaitable<std::string> set(net::ip::tcp::endpoint ep)
{
// To make code less verbose
using tcp_socket = net::use_awaitable_t<>::as_default_on_t<net::ip::tcp::socket>;
tcp_socket socket{co_await net::this_coro::executor};
co_await socket.async_connect(ep);
std::string request, read_buffer, response;
auto sr = make_serializer(request);
sr.push(command::hello, 3);
sr.push(command::set, "key", "Value", "EX", "2", "get");
sr.push(command::quit);
co_await net::async_write(socket, net::buffer(request));
co_await resp3::async_read(socket, dynamic_buffer(read_buffer)); // Hello (ignored).
co_await resp3::async_read(socket, dynamic_buffer(read_buffer), adapt(response)); // Set
co_await resp3::async_read(socket, dynamic_buffer(read_buffer)); // Quit (ignored)
co_return response;
}
@endcode
The simplicity of the code above makes it self explanatory
@li Connect to the Redis server.
@li Declare a \c std::string to hold the request and add some commands in it with a serializer.
@li Write the payload to the socket and read the responses in the same order they were sent.
@li Return the response to the user.
The @c hello command above is always required and must be sent
first as it informs we want to communicate over RESP3.
\subsection requests Requests
As stated above, request are created by defining a storage object
and a serializer that knowns how to convert user data into valid
RESP3 wire-format. Redis request are composed of one or more
commands (in Redis documentation they are called [pipelines](https://redis.io/topics/pipelining)),
which means users can add
as many commands to the request as they like, a feature that aids
performance.
The individual commands in a request assume many
different forms: with and without keys, variable length arguments,
ranges etc. To account for all these variations, the \c
serializer class offers some member functions, each of
them with a couple of overloads, for example
@code
// Some data to send to Redis.
std::string value = "some value";
std::list<std::string> list {"channel1", "channel2", "channel3"};
std::map<std::string, mystruct> map
{ {"key1", "value1"}
, {"key2", "value2"}
, {"key3", "value3"}};
// Command with no arguments
sr.push(command::quit);
// Command with variable lenght arguments.
sr.push(command::set, "key", value, "EX", "2");
// Sends a container, no key.
sr.push_range(command::subscribe, list);
// Same as above but an iterator range.
sr.push_range2(command::subscribe, std::cbegin(list), std::cend(list));
// Sends a container, with key.
sr.push_range(command::hset, "key", map);
// Same as above but as iterator range.
sr.push_range2(command::hset, "key", std::cbegin(map), std::cend(map));
@endcode
Once all commands we want to send have been added to the
request, we can write it as usual to the socket.
@code
co_await net::async_write(socket, buffer(request));
@endcode
\subsubsection requests-serialization Serialization
The \c send and \c send_range functions above work with integers
e.g. \c int and \c std::string out of the box. To send your own
data type defined the \c to_bulk function like this
@code
// Example struct.
struct mystruct {
// ...
};
void to_bulk(std::string& to, mystruct const& obj)
{
// Convert to obj string and call
aedis::resp3::to_bulk(to, "Dummy serializaiton string.");
}
std::map<std::string, mystruct> map
{ {"key1", {...}}
, {"key2", {...}}
, {"key3", {...}}};
db.send_range(command::hset, "key", map);
@endcode
It is quite common to store json string in Redis for example.
\subsection responses Responses
To read responses effectively, users must know their RESP3 type,
this can be found in the Redis documentation of each command
(https://redis.io/commands). For example
Command | RESP3 type | Documentation
---------|-------------------------------------|--------------
lpush | Number | https://redis.io/commands/lpush
lrange | Array | https://redis.io/commands/lrange
set | Simple string, null or blob string | https://redis.io/commands/set
get | Blob string | https://redis.io/commands/get
smembers | Set | https://redis.io/commands/smembers
hgetall | Map | https://redis.io/commands/hgetall
Once the RESP3 type of a given response is known we can choose a
proper C++ data structure to receive it in. Fortunately,
this is a simple task for most types, for example
RESP3 type | C++ | Type
---------------|--------------------------------------------------------------|------------------
Simple string | \c std::string | Simple
Simple error | \c std::string | Simple
Blob string | \c std::string, \c std::vector | Simple
Blob error | \c std::string, \c std::vector | Simple
Number | `long long`, `int`, \c std::string | Simple
Null | `boost::optional<T>` | Simple
Array | \c std::vector, \c std::list, \c std::array, \c std::deque | Aggregate
Map | \c std::vector, \c std::map, \c std::unordered_map | Aggregate
Set | \c std::vector, \c std::set, \c std::unordered_set | Aggregate
Push | \c std::vector, \c std::map, \c std::unordered_map | Aggregate
Exceptions to this rule are responses that contain nested
aggregates or heterogeneous data types, those will be treated
later. As of this writing, not all RESP3 types are used by the
Redis server, which means in practice users will be concerned with
a reduced subset of the RESP3 specification. Now let us see some
examples
@code
// To ignore the response.
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt());
// Read in a std::string e.g. get.
std::string str;
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(str));
// Read in a long long e.g. rpush.
long long number;
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(number));
// Read in a std::set e.g. smembers.
std::set<T, U> set;
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(set));
// Read in a std::map e.g. hgetall.
std::map<T, U> set;
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(map));
// Read in a std::unordered_map e.g. hgetall.
std::unordered_map<T, U> umap;
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(umap));
// Read in a std::vector e.g. lrange.
std::vector<T> vec;
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(vec));
@endcode
In other words, it is pretty straightforward, just pass the result
of \c adapt to the read function and make sure the response RESP3
type fits in the type you are calling @c adapter(...) with. All
standard C++ containers are supported by aedis.
\subsubsection Optional
It is not uncommon for apps to access keys that do not exist or
that have already expired in the Redis server, to deal with these
cases Aedis provides support for \c boost::optional. To use it,
wrap your type around \c boost::optional like this
@code
boost::optional<std::unordered_map<T, U>> umap;
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(umap));
@endcode
Everything else stays pretty much the same, before accessing data
users will have to check (or assert) the optional contains a
value.
\subsubsection heterogeneous_aggregates Heterogeneous aggregates
There are cases where Redis returns aggregates that
contain heterogeneous data, for example, an array that contains
integers, strings nested sets etc. Aedis supports reading such
aggregates in a \c std::tuple efficiently as long as the they
don't contain 2-order nested aggregates e.g. an array that
contains an array of arrays. For example, to read the response to
a \c hello command we can use the following response type.
@code
using hello_type = std::tuple<
std::string, std::string,
std::string, std::string,
std::string, int,
std::string, int,
std::string, std::string,
std::string, std::string,
std::string, std::vector<std::string>>;
@endcode
Transactions are another example where this feature is useful, for
example, the response to the transaction below
@code
db.send(command::multi);
db.send(command::get, "key1");
db.send(command::lrange, "key2", 0, -1);
db.send(command::hgetall, "key3");
db.send(command::exec);
@endcode
can be read in the following way
@code
std::tuple<
boost::optional<std::string>, // Response to get
boost::optional<std::vector<std::string>>, // Response to lrange
boost::optional<std::map<std::string, std::string>> // Response to hgetall
> trans;
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // Ignore multi
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // Ignore get
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // Ignore lrange
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // Ignore hgetall
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(trans));
@endcode
Note that we are not ignoring the response to the commands
themselves above but whether they have been successfully queued.
Only after @c exec is received Redis will execute them in
sequence. The response will then be sent in a single chunk to the
client.
\subsubsection Serialization
As mentioned in \ref requests-serialization, it is common for
users to serialized data before sending it to Redis e.g. json
strings, for example
@code
sr.push(command::set, "key", "json-string")
sr.push(command::get, "key")
@endcode
For performance and convenience reasons, we may want to avoid
receiving the response to the \c get command above as a string
just to convert it later to a e.g. deserialized json. To support
this, Aedis calls a user defined \c from_string function while
parsing the response. In simple terms, define your type
@code
struct mystruct {
// struct fields.
};
@endcode
and deserialize it from a string in a function \c from_string with
the following signature
@code
void from_string(mystruct& obj, char const* p, std::size_t size, boost::system::error_code& ec)
{
// Deserializes p into obj.
}
@endcode
After that, you can start receiving data efficiently in the desired
types e.g. \c mystruct, \c std::map<std::string, mystruct> etc.
\subsubsection gen-case The general case
As already mentioned, there are cases where the response to Redis
commands won't fit in the model presented above, some examples are
@li Commands (like \c set) whose response don't have a fixed
RESP3 type. Expecting an \c int and receiving a blob string
will result in error.
@li RESP3 responses that contain three levels of (nested) aggregates can't be
read in STL containers.
@li Transactions with a dynamic number of commands can't be read in a \c std::tuple.
To deal with these cases Aedis provides the \c resp3::node
type, that is the most general form of an element in a response,
be it a simple RESP3 type or an aggregate. It is defined like this
@code
template <class String>
struct node {
// The RESP3 type of the data in this node.
type data_type;
// The number of elements of an aggregate (or 1 for simple data).
std::size_t aggregate_size;
// The depth of this node in the response tree.
std::size_t depth;
// The actual data. For aggregate types this is always empty.
String value;
};
@endcode
Any response to a Redis command can be received in a \c
std::vector<node<std::string>>. The vector can be seen as a
pre-order view of the response tree
(https://en.wikipedia.org/wiki/Tree_traversal#Pre-order,_NLR).
Using it is no different that using other types
@code
// Receives any RESP3 simple data type.
node<std::string> resp;
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(resp));
// Receives any RESP3 simple or aggregate data type.
std::vector<node<std::string>> resp;
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(resp));
@endcode
For example, suppose we want to retrieve a hash data structure
from Redis with \c hgetall, some of the options are
@li \c std::vector<node<std::string>: Works always.
@li \c std::vector<std::string>: Efficient and flat, all elements as string.
@li \c std::map<std::string, std::string>: Efficient if you need the data as a \c std::map
@li \c std::map<U, V>: Efficient if you are storing serialized data. Avoids temporaries and requires \c from_string for \c U and \c V.
In addition to the above users can also use unordered versions of the containers. The same reasoning also applies to sets e.g. \c smembers.
\subsubsection low-level-adapters Adapters
Users that are not satisfied with any of the options above can
write their own adapters very easily. For example, the adapter below
can be used to print incoming data to the screen.
@code
auto adapter = [](resp3::node<boost::string_view> const& nd, boost::system::error_code&)
{
std::cout << nd << std::endl;
};
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapter);
@endcode
See more in the \ref examples section.
\section high-level-api High-level API
It requires a lot of further work to make use of many important features
of the Redis server while using the low-level API, for example
@li \b Server \b pushes: Short lived connections can't handle server pushes (e.g. https://redis.io/topics/client-side-caching and https://redis.io/topics/notifications).
@li \b Pubsub: Just like server pushes, to use Redis pubsub users need long lasting connections (https://redis.io/topics/pubsub).
@li \b Performance: Keep opening and closing connections impact performance.
@li \b Pipeline: Code such as shown in \ref low-level-api don't
support pipelines well since it can only send a fixed number of
commands at time. It misses important optimization opportunities
(https://redis.io/topics/pipelining).
To avoid these drawbacks users will address the points above
reinventing the high-level API here and there over and over again,
to prevent that from happening Aedis provides its own. The general
form of a program that uses the high-level api looks like this
@code
int main()
{
net::io_context ioc;
client<net::ip::tcp::socket> db{ioc.get_executor()};
receiver recv;
db.async_run(
recv,
{net::ip::make_address("127.0.0.1"), 6379},
[](auto ec){ ... });
// Pass db around to other objects so we can send commands.
ioc.run();
}
@endcode
The only thing users have to care about is with the implementation
of the \c receiver class, everything else will be performed
automatically by the client class. The general form of a receiver
looks like this
@code
class receiver {
public:
// Called when a new chunck of user data becomes available.
void on_resp3(command cmd, node<boost::string_view> const& nd, boost::system::error_code& ec);
// Called when a response becomes available.
void on_read(command cmd);
// Called when a request has been writen to the socket.
void on_write(std::size_t n);
// Called when a server push is received.
void on_push();
};
@endcode
Sending commands is also similar to what has been discussed before.
@code
void foo(client<net::ip::tcp::socket>& db)
{
db.send(command::ping, "O rato roeu a roupa do rei de Roma");
db.send(command::incr, "counter");
db.send(command::set, "key", "Três pratos de trigo para três tigres");
db.send(command::get, "key");
...
}
@endcode
The \c send functions in this case will add commands to the output
queue and send them only if there is no pending response of a
previously sent command. This is so because RESP3 is a
request/response protocol, which means clients must wait for the
response to a command before proceeding with the next one.
\subsection high-level-responses Responses
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
using responses_tuple_type = std::tuple<T1, T2, T3>;
using adapters_tuple_type = adapters_t<responses_tuple_type>;
class myreceiver {
public:
myreceiver(...) : adapters_(make_adapters_tuple(resps_)) , {}
void
on_resp3( command cmd, node<boost::string_view> const& nd, boost::system::error_code& ec)
{
// Direct the responses to the desired adapter.
switch (cmd) {
case cmd1: adapter::get<T1>(adapters_)(nd, ec);
case cmd2: adapter::get<T2>(adapters_)(nd, ec);
case cmd3: adapter::get<T2>(adapters_)(nd, ec);
default:;
}
}
void on_read(command cmd)
{
switch (cmd) {
case cmd1: // Data on std::get<T1>(resps_); break;
case cmd2: // Data on std::get<T2>(resps_); break;
case cmd3: // Data on std::get<T3>(resps_); break;
default:;
}
}
void on_write(std::size_t n) { ... }
void on_push() { ... }
private:
responses_tuple_type resps_;
adapters_tuple_type adapters_;
};
@endcode
\section examples Examples
\b Basics: Focuses on small examples that show basic usage of
the library.
To better fix what has been said above, users should have a look at some simple examples.
- intro.cpp: A good starting point. Some commands are sent to the
Redis server and the responses are printed to screen.
\b Low \b level \b API
- transaction.cpp: Shows how to read the responses to a trasaction
efficiently. See also https://redis.io/topics/transactions.
@li low_level/sync_intro.cpp: Shows how to use the Aedis synchronous api.
@li low_level/async_intro.cpp: Show how to use the low level async api.
@li low_level/subscriber.cpp: Shows how channel subscription works at the low level.
@li low_level/adapter.cpp: Shows how to write a response adapter that prints to the screen, see \ref low-level-adapters.
- multipurpose_response.cpp: Shows how to read any responses to
Redis commands, including nested aggegagtes.
\b High \b level \b API
- subscriber.cpp: Shows how channel subscription works at a low
level. See also https://redis.io/topics/pubsub.
@li high_level/intro.cpp: Some commands are sent to the Redis server and the responses are printed to screen.
@li high_level/aggregates.cpp: Shows how receive RESP3 aggregate data types in a general way.
@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 data types.
@li high_level/subscriber.cpp: Shows how channel subscription works at a high level. See also https://redis.io/topics/pubsub.
- sync.cpp: Shows hot to use the Aedis synchronous api.
\b Asynchronous \b Servers
- key_expiration.cpp: Shows how to use \c std::optional to deal
with keys that may have expired or do not exist.
\b STL \b Containers: Many of the Redis data structures can be
directly translated in to STL containers, below you will find some
example code. For a list of Redis data types see
https://redis.io/topics/data-types.
- hashes.cpp: Shows how to read Redis hashes in a \c std::map, \c
std::unordered_map and \c std::vector.
- lists.cpp: Shows how to read Redis lists in \c std::list,
\c std::deque, \c std::vector. It also illustrates basic serialization.
- sets.cpp: Shows how to read Redis sets in a \c std::set, \c
std::unordered_set and \c std::vector.
\b Customization \b points: Shows how de/serialize user types
avoiding copies. This is particularly useful for low latency
applications that want to avoid unneeded copies, for examples when
storing json strings in Redis keys.
- serialization.cpp: Shows how to de/serialize your own
non-aggregate data-structures.
- response_adapter.cpp: Customization point for users that want to
de/serialize their own data-structures like containers for example.
\b Asynchronous \b servers: Contains some non-trivial examples
servers that interact with users and Redis asynchronously over
long lasting connections using a higher level API.
- multipurpose_client.cpp: Shows how to use and experimental high
level redis client that keeps a long lasting connections to a
redis server. This is the starting point for the next examples.
- 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.
- 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.
@li high_level/chat_room.cpp: Shows how to build a scalable chat room that scales to millions of users.
\section using-aedis Using Aedis
@@ -99,43 +577,53 @@
- Boost 1.78 or greater.
- Unix Shell and Make.
- Compiler with C++20 coroutine support e.g. GCC 10 or greater.
- C++14. Some examples require C++20 with coroutine support.
- Redis server.
Some examples will also require interaction with
- redis-cli: used in one example.
- redis-cli: Used in one example.
- Redis Sentinel Server: used in some examples.
Aedis has been tested with the following compilers
- Tested with gcc: 7.5.0, 8.4.0, 9.3.0, 10.3.0.
- Tested with clang: 11.0.0, 10.0.0, 9.0.1, 8.0.1, 7.0.1.
\subsection Installation
Start by downloading and configuring the library
The first thing to do is to download and unpack Aedis
```
# Download the libray on github.
$ wget https://github.com/mzimbres/aedis/release-path # TODO
# Download the latest release on github
$ wget https://github.com/mzimbres/aedis/releases
# Uncompress the tarball and cd into the dir
$ tar -xzvf aedis-1.0.0.tar.gz && cd aedis-1.0.0
# Run configure with appropriate C++ flags and your boost
# installation, for example # You may also have to use
# -Wno-subobject-linkage on gcc.
$ CXXFLAGS="-std=c++20 -fcoroutines -g -Wall"\
./configure --prefix=/opt/aedis-1.0.0 --with-boost=/opt/boost_1_78_0 --with-boost-libdir=/opt/boost_1_78_0/lib
```
To install the library run
$ tar -xzvf aedis-version.tar.gz
```
If you can't use \c configure and \c make (e.g. Windows users)
you can already add the directory where you unpacked aedis to the
include directories in your project, otherwise run
```
# See configure --help for all options.
$ ./configure --prefix=/opt/aedis-version --with-boost=/opt/boost_1_78_0
# Install Aedis in the path specified in --prefix
$ sudo make install
```
At this point you can start using Aedis. To build the examples and
test you can also run
and include the following header
```cpp
#include <aedis/src.hpp>
```
in exactly one source file in your applications. At this point you
can start using Aedis. To build the examples and run the tests run
```
# Build aedis examples.
@@ -144,18 +632,7 @@
# Test aedis in your machine.
$ make check
```
Finally you will have to include the following header
```cpp
#include <aedis/src.hpp>
```
in exactly one source file in your applications.
Windows users can use aedis by either adding the project root
directory to their include path or manually copying to another
location.
\subsection Developers
To generate the build system run
@@ -164,15 +641,15 @@
$ autoreconf -i
```
After that you will have a config in the project dir that you can
run as explained above, for example, to use a compiler other that
the system compiler use
After that you will have a configure script
that you can run as explained above, for example, to use a
compiler other that the system compiler run
```
CC=/opt/gcc-10.2.0/bin/gcc-10.2.0\
CXX=/opt/gcc-10.2.0/bin/g++-10.2.0\
CXXFLAGS="-std=c++20 -fcoroutines -g -Wall -Wno-subobject-linkage -Werror" ./configure ...
$ CC=/opt/gcc-10.2.0/bin/gcc-10.2.0 CXX=/opt/gcc-10.2.0/bin/g++-10.2.0 CXXFLAGS="-g -Wall -Werror" ./configure ...
$ make distcheck
```
\section Referece
See \subpage any.

View File

@@ -1,16 +0,0 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
// TODO: Remove this.
#include <boost/asio.hpp>
namespace aedis {
namespace net = boost::asio;
}

374
aedis/generic/client.hpp Normal file
View File

@@ -0,0 +1,374 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <vector>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/steady_timer.hpp>
#include <aedis/resp3/type.hpp>
#include <aedis/generic/detail/client_ops.hpp>
#include <aedis/redis/command.hpp>
// TODO: What to do if users send a discard command not contained in a
// transaction. The client object will try to pop the queue until a
// multi is found.
namespace aedis {
namespace generic {
/** \brief A high level Redis client.
* \ingroup any
*
* This class represents a connection to the Redis server. Some of
* its most important features are
*
* 1. Automatic management of commands. The implementation will send
* commands and read responses automatically for the user.
* 2. Memory reuse. Dynamic memory allocations will decrease with time.
*
* For more details, please see the documentation of each individual
* function.
*/
template <class AsyncReadWriteStream, class Command>
class client {
public:
using stream_type = AsyncReadWriteStream;
using executor_type = typename stream_type::executor_type;
using default_completion_token_type = boost::asio::default_completion_token_t<executor_type>;
/** \brief Constructor.
*
* \param ex The executor.
*/
client(boost::asio::any_io_executor ex)
: socket_{ex}
, timer_{ex}
{
timer_.expires_at(std::chrono::steady_clock::time_point::max());
send(Command::hello, 3);
}
/// Returns the executor.
auto get_executor() {return socket_.get_executor();}
/** @brief Adds a command to the output command queue.
*
* Adds a command to the output command queue and signals the write
* operation there are new messages awaiting to be sent to Redis.
*
* @sa serializer.hpp
*
* @param cmd The command to send.
* @param args Arguments to commands.
*/
template <class... Ts>
void send(Command cmd, Ts const&... args)
{
auto const can_write = prepare_next();
serializer<std::string> sr(requests_);
auto const before = requests_.size();
sr.push(cmd, args...);
auto const after = requests_.size();
assert(after - before != 0);
req_info_.front().size += after - before;;
if (!has_push_response(cmd)) {
commands_.push_back(cmd);
++req_info_.front().cmds;
}
if (can_write)
timer_.cancel_one();
}
/** @brief Adds a command to the output command queue.
*
* Adds a command to the output command queue and signals the write
* operation there are new messages awaiting to be sent to Redis.
*
* @sa serializer.hpp
*
* @param cmd The command.
* @param key The key the commands refers to
* @param begin Begin of the range.
* @param end End of the range.
*/
template <class Key, class ForwardIterator>
void send_range2(Command cmd, Key const& key, ForwardIterator begin, ForwardIterator end)
{
if (begin == end)
return;
auto const can_write = prepare_next();
serializer<std::string> sr(requests_);
auto const before = requests_.size();
sr.push_range2(cmd, key, begin, end);
auto const after = requests_.size();
assert(after - before != 0);
req_info_.front().size += after - before;;
if (!has_push_response(cmd)) {
commands_.push_back(cmd);
++req_info_.front().cmds;
}
if (can_write)
timer_.cancel_one();
}
/** @brief Adds a command to the output command queue.
*
* Adds a command to the output command queue and signals the write
* operation there are new messages awaiting to be sent to Redis.
*
* @sa serializer.hpp
*
* @param cmd The command.
* @param begin Begin of the range.
* @param end End of the range.
*/
template <class ForwardIterator>
void send_range2(Command cmd, ForwardIterator begin, ForwardIterator end)
{
if (begin == end)
return;
auto const can_write = prepare_next();
serializer<std::string> sr(requests_);
auto const before = requests_.size();
sr.push_range2(cmd, begin, end);
auto const after = requests_.size();
assert(after - before != 0);
req_info_.front().size += after - before;;
if (!has_push_response(cmd)) {
commands_.push_back(cmd);
++req_info_.front().cmds;
}
if (can_write)
timer_.cancel_one();
}
/** @brief Adds a command to the output command queue.
*
* Adds a command to the output command queue and signals the write
* operation there are new messages awaiting to be sent to Redis.
*
* @sa serializer.hpp
*
* @param cmd The command.
* @param key The key the commands refers to.
* @param range Range of elements to send.
*/
template <class Key, class Range>
void send_range(Command cmd, Key const& key, Range const& range)
{
using std::begin;
using std::end;
send_range2(cmd, key, begin(range), end(range));
}
/** @brief Adds a command to the output command queue.
*
* Adds a command to the output command queue and signals the write
* operation there are new messages awaiting to be sent to Redis.
*
* @sa serializer.hpp
*
* @param cmd The command.
* @param range End of the range.
*/
template <class Range>
void send_range(Command cmd, Range const& range)
{
using std::begin;
using std::end;
send_range2(cmd, begin(range), end(range));
}
/** @brief Starts communication with the Redis server asynchronously.
*
* This class performs the following steps
*
* @li Connect to the endpoint passed in the function parameter.
* @li Start the async read operation that keeps reading responses to commands and server pushes.
* @li Start the async write operation that keeps sending commands to Redis.
*
* \param recv The receiver (see below)
* \param ep The address of the Redis server.
* \param token The completion token (ASIO jargon)
*
* The receiver is a class that privides the following member functions
*
* @code
* class receiver {
* public:
* // Called when a new chunck of user data becomes available.
* void on_resp3(command cmd, node<boost::string_view> const& nd, boost::system::error_code& ec);
*
* // Called when a response becomes available.
* void on_read(command cmd);
*
* // Called when a request has been writen to the socket.
* void on_write(std::size_t n);
*
* // Called when a server push is received.
* void on_push();
* };
* @endcode
*
*/
template <
class Receiver,
class CompletionToken = default_completion_token_type
>
auto
async_run(
Receiver& recv,
boost::asio::ip::tcp::endpoint ep = {boost::asio::ip::make_address("127.0.0.1"), 6379},
CompletionToken token = CompletionToken{})
{
endpoint_ = ep;
return boost::asio::async_compose
< CompletionToken
, void(boost::system::error_code)
>(run_op<client, Receiver>{this, &recv}, token, socket_, timer_);
}
private:
template <class T, class U, class V> friend struct read_op;
template <class T, class U> friend struct writer_op;
template <class T, class U> friend struct read_write_op;
template <class T, class U> friend struct run_op;
struct request_info {
// Request size in bytes.
std::size_t size = 0;
// The number of commands it contains excluding commands that
// have push types as responses, see has_push_response.
std::size_t cmds = 0;
};
// Buffer used in the read operations.
std::string read_buffer_;
// Requests payload.
std::string requests_;
// The commands contained in the requests.
std::vector<Command> commands_;
// Info about the requests.
std::vector<request_info> req_info_;
// The stream.
stream_type socket_;
// Timer used to inform the write coroutine that it can write the
// next message in the output queue.
boost::asio::steady_timer timer_;
// Redis endpoint.
boost::asio::ip::tcp::endpoint endpoint_;
bool stop_writer_ = false;
/* Prepares the back of the queue to receive further commands.
*
* If true is returned the request in the front of the queue can be
* sent to the server. See async_write_some.
*/
bool prepare_next()
{
if (req_info_.empty()) {
req_info_.push_back({});
return true;
}
if (req_info_.front().size == 0) {
// It has already been written and we are waiting for the
// responses.
req_info_.push_back({});
return false;
}
return false;
}
// Returns true when the next request can be writen.
bool on_cmd(Command)
{
// TODO: If the response to a discard is received we have to
// remove all commands up until multi.
assert(!req_info_.empty());
assert(!commands_.empty());
commands_.erase(std::begin(commands_));
if (--req_info_.front().cmds != 0)
return false;
req_info_.erase(std::begin(req_info_));
return !req_info_.empty();
}
// Reads messages asynchronously.
template <
class Receiver,
class CompletionToken = default_completion_token_type>
auto
async_reader(
Receiver* recv,
CompletionToken&& token = default_completion_token_type{})
{
return boost::asio::async_compose
< CompletionToken
, void(boost::system::error_code)
>(read_op<client, Receiver, Command>{this, recv}, token, socket_);
}
template <
class Receiver,
class CompletionToken = default_completion_token_type>
auto
async_writer(
Receiver* recv,
CompletionToken&& token = default_completion_token_type{})
{
return boost::asio::async_compose
< CompletionToken
, void(boost::system::error_code)
>(writer_op<client, Receiver>{this, recv}, token, socket_, timer_);
}
template <
class Receiver,
class CompletionToken = default_completion_token_type>
auto
async_read_write(
Receiver* recv,
CompletionToken&& token = default_completion_token_type{})
{
return boost::asio::async_compose
< CompletionToken
, void(boost::system::error_code)
>(read_write_op<client, Receiver>{this, recv}, token, socket_, timer_);
}
};
} // generic
} // aedis

View File

@@ -0,0 +1,205 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <array>
#include <boost/asio/experimental/parallel_group.hpp>
#include <boost/system.hpp>
#include <boost/asio/write.hpp>
#include <boost/core/ignore_unused.hpp>
#include <aedis/resp3/type.hpp>
#include <aedis/resp3/detail/parser.hpp>
#include <aedis/resp3/read.hpp>
namespace aedis {
namespace generic {
#include <boost/asio/yield.hpp>
template <class Client, class Receiver>
struct run_op {
Client* cli;
Receiver* recv_;
boost::asio::coroutine coro;
template <class Self>
void operator()(Self& self, boost::system::error_code ec = {})
{
reenter (coro) {
yield cli->socket_.async_connect(cli->endpoint_, std::move(self));
if (ec) {
self.complete(ec);
return;
}
yield cli->async_read_write(recv_, std::move(self));
self.complete(ec);
}
}
};
template <class Client, class Receiver>
struct read_write_op {
Client* cli;
Receiver* recv;
boost::asio::coroutine coro;
template <class Self>
void operator()( Self& self
, std::array<std::size_t, 2> order = {}
, boost::system::error_code ec1 = {}
, boost::system::error_code ec2 = {}
)
{
reenter (coro) {
yield
boost::asio::experimental::make_parallel_group(
[this](auto token) { return cli->async_writer(recv, token);},
[this](auto token) { return cli->async_reader(recv, token);}
).async_wait(
boost::asio::experimental::wait_for_one_error(),
std::move(self));
switch (order[0]) {
case 0: self.complete(ec1); break;
case 1: self.complete(ec2); break;
default: assert(false);
}
}
}
};
// Consider limiting the size of the pipelines by spliting that last
// one in two if needed.
template <class Client, class Receiver>
struct writer_op {
Client* cli;
Receiver* recv;
std::size_t size;
boost::asio::coroutine coro;
template <class Self>
void operator()( Self& self
, boost::system::error_code ec = {}
, std::size_t n = 0)
{
reenter (coro) for (;;) {
boost::ignore_unused(n);
assert(!cli->req_info_.empty());
assert(cli->req_info_.front().size != 0);
assert(!cli->requests_.empty());
yield
boost::asio::async_write(
cli->socket_,
boost::asio::buffer(cli->requests_.data(), cli->req_info_.front().size),
std::move(self));
if (ec) {
cli->socket_.close();
self.complete(ec);
return;
}
size = cli->req_info_.front().size;
cli->requests_.erase(0, cli->req_info_.front().size);
cli->req_info_.front().size = 0;
if (cli->req_info_.front().cmds == 0)
cli->req_info_.erase(std::begin(cli->req_info_));
recv->on_write(size);
yield cli->timer_.async_wait(std::move(self));
if (cli->stop_writer_) {
self.complete(ec);
return;
}
}
}
};
template <class Client, class Receiver, class Command>
struct read_op {
Client* cli;
Receiver* recv;
boost::asio::coroutine coro;
// Consider moving this variables to the client to spare some
// memory in the competion handler.
resp3::type t = resp3::type::invalid;
Command cmd = Command::invalid;
template <class Self>
void operator()( Self& self
, boost::system::error_code ec = {}
, std::size_t n = 0)
{
reenter (coro) for (;;) {
boost::ignore_unused(n);
if (cli->read_buffer_.empty()) {
yield
boost::asio::async_read_until(
cli->socket_,
boost::asio::dynamic_buffer(cli->read_buffer_),
"\r\n",
std::move(self));
if (ec) {
cli->stop_writer_ = true;
self.complete(ec);
return;
}
}
assert(!cli->read_buffer_.empty());
t = resp3::detail::to_type(cli->read_buffer_.front());
cmd = Command::invalid;
if (t != resp3::type::push) {
assert(!cli->commands_.empty());
cmd = cli->commands_.front();
}
yield
resp3::async_read(
cli->socket_,
boost::asio::dynamic_buffer(cli->read_buffer_),
[p = recv, c = cmd](resp3::node<boost::string_view> const& nd, boost::system::error_code& ec) mutable {p->on_resp3(c, nd, ec);},
std::move(self));
if (ec) {
cli->stop_writer_ = true;
self.complete(ec);
return;
}
if (t == resp3::type::push) {
recv->on_push();
} else {
if (cli->on_cmd(cmd))
cli->timer_.cancel_one();
recv->on_read(cmd);
}
}
}
};
#include <boost/asio/unyield.hpp>
} // generic
} // aedis

View File

@@ -7,10 +7,11 @@
#pragma once
#include <aedis/resp3/detail/composer.hpp>
#include <boost/hana.hpp>
#include <aedis/resp3/compose.hpp>
namespace aedis {
namespace resp3 {
namespace generic {
/** @brief Creates a Redis request from user data.
* \ingroup any
@@ -32,21 +33,19 @@ namespace resp3 {
* co_await async_write(socket, buffer(request));
* @endcode
*
* \tparam Storage The storage type. This is currently a \c std::string.
* \tparam Storage The storage type e.g \c std::string.
* \tparam Command The command to serialize.
*
* \remarks Non-string types will be converted to string by using \c
* to_string, which must be made available over ADL.
*/
// NOTE: Consider adding an overload for containers.
//
// TODO: Should we detect any std::pair or tuple in the type in the parameter
// pack to calculate the header size correctly?
// Consider detecting tuples in the type in the parameter pack to
// calculate the header size correctly.
//
// NOTE: For some commands like hset it would be a good idea to assert
// the value type is a pair.
template <class Storage, class Command>
template <class Storage>
class serializer {
private:
Storage* request_;
@@ -76,14 +75,17 @@ public:
* \param args The arguments of the Redis command.
*
*/
template <class... Ts>
template <class Command, class... Ts>
void push(Command cmd, Ts const&... args)
{
auto constexpr pack_size = sizeof...(Ts);
detail::add_header(*request_, 1 + pack_size);
using boost::hana::for_each;
using boost::hana::make_tuple;
detail::add_bulk(*request_, to_string(cmd));
(detail::add_bulk(*request_, args), ...);
auto constexpr pack_size = sizeof...(Ts);
resp3::add_header(*request_, 1 + pack_size);
resp3::add_bulk(*request_, to_string(cmd));
resp3::add_bulk(*request_, make_tuple(args...));
}
/** @brief Appends a new command to the end of the request.
@@ -98,7 +100,7 @@ public:
* };
*
* request req;
* req.push_range(command::hset, "key", std::cbegin(map), std::cend(map));
* req.push_range2(command::hset, "key", std::cbegin(map), std::cend(map));
* \endcode
*
* \param cmd The Redis command
@@ -106,19 +108,22 @@ public:
* \param begin Iterator to the begin of the range.
* \param end Iterator to the end of the range.
*/
template <class Key, class ForwardIterator>
void push_range(Command cmd, Key const& key, ForwardIterator begin, ForwardIterator end)
template <class Command, class Key, class ForwardIterator>
void push_range2(Command cmd, Key const& key, ForwardIterator begin, ForwardIterator end)
{
using value_type = typename std::iterator_traits<ForwardIterator>::value_type;
auto constexpr size = detail::value_type_size<value_type>::size;
if (begin == end)
return;
auto constexpr size = resp3::bulk_counter<value_type>::size;
auto const distance = std::distance(begin, end);
detail::add_header(*request_, 2 + size * distance);
detail::add_bulk(*request_, to_string(cmd));
detail::add_bulk(*request_, key);
resp3::add_header(*request_, 2 + size * distance);
resp3::add_bulk(*request_, to_string(cmd));
resp3::add_bulk(*request_, key);
for (; begin != end; ++begin)
detail::add_bulk(*request_, *begin);
resp3::add_bulk(*request_, *begin);
}
/** @brief Appends a new command to the end of the request.
@@ -138,20 +143,58 @@ public:
* \param begin Iterator to the begin of the range.
* \param end Iterator to the end of the range.
*/
template <class ForwardIterator>
void push_range(Command cmd, ForwardIterator begin, ForwardIterator end)
template <class Command, class ForwardIterator>
void push_range2(Command cmd, ForwardIterator begin, ForwardIterator end)
{
using value_type = typename std::iterator_traits<ForwardIterator>::value_type;
auto constexpr size = detail::value_type_size<value_type>::size;
if (begin == end)
return;
auto constexpr size = resp3::bulk_counter<value_type>::size;
auto const distance = std::distance(begin, end);
detail::add_header(*request_, 1 + size * distance);
detail::add_bulk(*request_, to_string(cmd));
resp3::add_header(*request_, 1 + size * distance);
resp3::add_bulk(*request_, to_string(cmd));
for (; begin != end; ++begin)
detail::add_bulk(*request_, *begin);
resp3::add_bulk(*request_, *begin);
}
/** @brief Appends a new command to the end of the request.
*
* Similar to the range version.
*/
template <class Command, class Key, class Range>
void push_range(Command cmd, Key const& key, Range const& range)
{
using std::begin;
using std::end;
push_range2(cmd, key, begin(range), end(range));
}
/** @brief Appends a new command to the end of the request.
*
* Similar to the range version.
*/
template <class Command, class Range>
void push_range(Command cmd, Range const& range)
{
using std::begin;
using std::end;
push_range2(cmd, begin(range), end(range));
}
};
} // resp3
/** \brief Creates a serializer.
* \ingroup any
* \param storage The string.
*/
template <class CharT, class Traits, class Allocator>
serializer<std::basic_string<CharT, Traits, Allocator>>
make_serializer(std::basic_string<CharT, Traits, Allocator>& storage)
{
return serializer<std::basic_string<CharT, Traits, Allocator>>(storage);
}
} // generic
} // aedis

View File

@@ -10,8 +10,6 @@
#include <ostream>
#include <string>
#include <aedis/resp3/serializer.hpp>
namespace aedis {
namespace redis {
@@ -74,7 +72,7 @@ enum class command {
decrby,
/// https://redis.io/commands/del
del,
/// https://redis.io/commands/discard
/// https://redis.io/commands/discard (not supported yet)
discard,
/// https://redis.io/commands/dump
dump,
@@ -432,8 +430,8 @@ enum class command {
zscore,
/// https://redis.io/commands/zunionstore
zunionstore,
/// Unknown/invalid command.
unknown
/// Invalid command.
invalid
};
/** \brief Converts a command to a string
@@ -456,16 +454,5 @@ std::ostream& operator<<(std::ostream& os, command c);
*/
bool has_push_response(command cmd);
/** \brief Creates a serializer for a \c std::string.
* \ingroup any
* \param storage The string.
*/
template <class CharT, class Traits, class Allocator>
resp3::serializer<std::string, command>
make_serializer(std::basic_string<CharT, Traits, Allocator>& storage)
{
return resp3::serializer<std::basic_string<CharT, Traits, Allocator>, command>(storage);
}
} // redis
} // aedis

View File

@@ -1,158 +0,0 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <queue>
#include <functional>
#include <aedis/aedis.hpp>
#include <aedis/redis/command.hpp>
namespace aedis {
namespace resp3 {
namespace experimental {
/** \brief A high level redis client.
* \ingroup any
*
* This Redis client keeps a connection to the database open and
* uses it for all communication with Redis. For examples on how to
* use see the examples chat_room.cpp, echo_server.cpp and redis_client.cpp.
*
* \remarks This class reuses its internal buffers for requests and
* for reading Redis responses. With time it will allocate less and
* less.
*/
class client : public std::enable_shared_from_this<client> {
public:
/** \brief The extended response adapter type.
*
* The difference between the adapter and extended_adapter
* concepts is that the extended get a command redis::parameter.
*/
using extented_adapter_type = std::function<void(redis::command, type, std::size_t, std::size_t, char const*, std::size_t, std::error_code&)>;
/// The type of the message callback.
using on_message_type = std::function<void(std::error_code ec, redis::command)>;
/// The type of the socket used by the client.
//using socket_type = net::use_awaitable_t<>::as_default_on_t<net::ip::tcp::socket>;
using socket_type = net::ip::tcp::socket;
private:
struct request_info {
// Request size in bytes.
std::size_t size = 0;
// The number of commands it contains excluding commands that
// have push types as responses, see has_push_response.
std::size_t cmds = 0;
};
// Requests payload.
std::string requests_;
// The commands contained in the requests.
std::queue<redis::command> commands_;
// Info about the requests.
std::queue<request_info> req_info_;
// The stream.
socket_type socket_;
// Timer used to inform the write coroutine that it can write the
// next message in the output queue.
net::steady_timer timer_;
// Response adapter.
extented_adapter_type extended_adapter_ = [](redis::command, type, std::size_t, std::size_t, char const*, std::size_t, std::error_code&) {};
// Message callback.
on_message_type on_msg_ = [](std::error_code ec, redis::command) {};
// Set when the writer coroutine should stop.
bool stop_writer_ = false;
// A coroutine that keeps reading the socket. When a message
// arrives it calls on_message.
net::awaitable<void> reader();
// Write coroutine. It is kept suspended until there are messages
// to be sent.
net::awaitable<void> writer();
/* Prepares the back of the queue to receive further commands.
*
* If true is returned the request in the front of the queue can be
* sent to the server. See async_write_some.
*/
bool prepare_next();
public:
/** \brief Client constructor.
*
* Constructos the client from an executor.
*
* \param ex The executor.
*/
client(net::any_io_executor ex);
/// Returns the executor used for I/O with Redis.
auto get_executor() {return socket_.get_executor();}
/** \brief Starts communication with Redis.
*
* This functions will send the hello command to Redis and spawn
* the read and write coroutines.
*
* \param socket A socket that is connected to redis.
*
* \returns This function returns an awaitable on which users should \c
* co_await. When the communication with Redis is lost the
* coroutine will finally co_return.
*/
net::awaitable<void> engage(socket_type socket);
/** \brief Adds a command to the command queue.
*
* \sa serializer.hpp
*/
template <class... Ts>
void send(redis::command cmd, Ts const&... args);
/// Sets an extended response adapter.
void set_extended_adapter(extented_adapter_type adapter);
/// Sets the message callback;
void set_msg_callback(on_message_type on_msg);
};
template <class... Ts>
void client::send(redis::command cmd, Ts const&... args)
{
auto const can_write = prepare_next();
auto sr = redis::make_serializer(requests_);
auto const before = std::size(requests_);
sr.push(cmd, args...);
auto const after = std::size(requests_);
req_info_.front().size += after - before;;
if (!has_push_response(cmd)) {
commands_.emplace(cmd);
++req_info_.front().cmds;
}
if (can_write)
timer_.cancel_one();
}
} // experimental
} // resp3
} // aedis

View File

@@ -1,200 +0,0 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <aedis/redis/experimental/client.hpp>
#include <boost/asio/experimental/awaitable_operators.hpp>
namespace aedis {
namespace resp3 {
namespace experimental {
client::client(net::any_io_executor ex)
: socket_{ex}
, timer_{ex}
{
timer_.expires_at(std::chrono::steady_clock::time_point::max());
}
net::awaitable<void> client::reader()
{
// Writes and reads continuosly from the socket.
for (std::string buffer;;) {
// Notice this coro can get scheduled while the write operation
// in the writer is ongoing. so we have to check.
while (!std::empty(req_info_) && req_info_.front().size != 0) {
assert(!std::empty(requests_));
boost::system::error_code ec;
co_await
net::async_write(
socket_,
net::buffer(requests_.data(), req_info_.front().size),
net::redirect_error(net::use_awaitable, ec));
requests_.erase(0, req_info_.front().size);
req_info_.front().size = 0;
if (req_info_.front().cmds != 0)
break; // We must await the responses.
req_info_.pop();
}
do { // Keeps reading while there are no messages queued waiting to be sent.
do { // Consumes the responses to all commands in the request.
boost::system::error_code ec;
auto const t =
co_await async_read_type(socket_, net::dynamic_buffer(buffer),
net::redirect_error(net::use_awaitable, ec));
if (ec) {
stop_writer_ = true;
timer_.cancel();
co_return;
}
if (t == type::push) {
auto adapter = [this](type t, std::size_t aggregate_size, std::size_t depth, char const* data, std::size_t size, std::error_code& ec)
{extended_adapter_(redis::command::unknown, t, aggregate_size, depth, data, size, ec);};
co_await resp3::async_read(socket_, net::dynamic_buffer(buffer), adapter, net::redirect_error(net::use_awaitable, ec));
on_msg_(ec, redis::command::unknown);
if (ec) { // TODO: Return only on non aedis errors.
stop_writer_ = true;
timer_.cancel();
co_return;
}
} else {
auto adapter = [this](type t, std::size_t aggregate_size, std::size_t depth, char const* data, std::size_t size, std::error_code& ec)
{extended_adapter_(commands_.front(), t, aggregate_size, depth, data, size, ec);};
boost::system::error_code ec;
co_await resp3::async_read(socket_, net::dynamic_buffer(buffer), adapter, net::redirect_error(net::use_awaitable, ec));
on_msg_(ec, commands_.front());
if (ec) { // TODO: Return only on non aedis errors.
stop_writer_ = true;
timer_.cancel();
co_return;
}
commands_.pop();
--req_info_.front().cmds;
}
} while (!std::empty(req_info_) && req_info_.front().cmds != 0);
// We may exit the loop above either because we are done
// with the response or because we received a server push
// while the queue was empty so we have to check before
// poping..
if (!std::empty(req_info_))
req_info_.pop();
} while (std::empty(req_info_));
}
}
net::awaitable<void> client::writer()
{
boost::system::error_code ec;
while (socket_.is_open()) {
ec = {};
co_await timer_.async_wait(net::redirect_error(net::use_awaitable, ec));
if (stop_writer_)
co_return;
// Notice this coro can get scheduled while the write operation
// in the reader is ongoing. so we have to check.
while (!std::empty(req_info_) && req_info_.front().size != 0) {
assert(!std::empty(requests_));
ec = {};
co_await net::async_write(
socket_, net::buffer(requests_.data(), req_info_.front().size),
net::redirect_error(net::use_awaitable, ec));
if (ec) {
// What should we do here exactly? Closing the socket will
// cause the reader coroutine to return so that the engage
// coroutine returns to the user.
socket_.close();
co_return;
}
requests_.erase(0, req_info_.front().size);
req_info_.front().size = 0;
if (req_info_.front().cmds != 0)
break;
req_info_.pop();
}
}
}
bool client::prepare_next()
{
if (std::empty(req_info_)) {
req_info_.push({});
return true;
}
if (req_info_.front().size == 0) {
// It has already been written and we are waiting for the
// responses.
req_info_.push({});
return false;
}
return false;
}
void client::set_extended_adapter(extented_adapter_type adapter)
{
extended_adapter_ = adapter;
}
void client::set_msg_callback(on_message_type on_msg)
{
on_msg_ = on_msg;
}
net::awaitable<void> client::engage(socket_type socket)
{
using namespace aedis::net::experimental::awaitable_operators;
socket_ = std::move(socket);
std::string request;
auto sr = redis::make_serializer(request);
sr.push(redis::command::hello, 3);
boost::system::error_code ec;
co_await net::async_write(socket_, net::buffer(request), net::redirect_error(net::use_awaitable, ec));
if (ec)
co_return;
std::string buffer;
auto adapter = [this](type t, std::size_t aggregate_size, std::size_t depth, char const* data, std::size_t size, std::error_code& ec)
{extended_adapter_(redis::command::hello, t, aggregate_size, depth, data, size, ec);};
co_await
resp3::async_read(
socket_, net::dynamic_buffer(buffer), adapter, net::redirect_error(net::use_awaitable, ec));
on_msg_(ec, redis::command::hello);
if (ec)
co_return;
co_await (reader() && writer());
}
} // experimental
} // resp3
} // aedis

View File

@@ -5,6 +5,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include <cassert>
#include <aedis/redis/command.hpp>
namespace aedis {
@@ -217,6 +218,7 @@ char const* to_string(command c)
"ZSCAN",
"ZSCORE",
"ZUNIONSTORE",
"INVALID",
};
return table[static_cast<int>(c)];

View File

@@ -1,94 +0,0 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <aedis/resp3/response_traits.hpp>
namespace aedis {
namespace resp3 {
/** \brief Creates a void response adapter.
\ingroup any
The adapter returned by this function ignores responses and is
useful to avoid wasting time with responses which the user is
insterested in.
Example usage:
@code
co_await async_read(socket, buffer, adapt());
@endcode
*/
inline
auto adapt() noexcept
{ return response_traits<void>::adapt(); }
/** \brief Adapts user data to read operations.
* \ingroup any
*
* For example
* The following types are supported.
*
* - Integer data types e.g. `int`, `unsigned`, etc.
*
* - `std::string`
*
* We also support the following C++ containers
*
* - `std::vector<T>`. Can be used with any RESP3 aggregate type.
*
* - `std::deque<T>`. Can be used with any RESP3 aggregate type.
*
* - `std::list<T>`. Can be used with any RESP3 aggregate type.
*
* - `std::set<T>`. Can be used with RESP3 set type.
*
* - `std::unordered_set<T>`. Can be used with RESP3 set type.
*
* - `std::map<T>`. Can be used with RESP3 hash type.
*
* - `std::unordered_map<T>`. Can be used with RESP3 hash type.
*
* All these types can be wrapped in an `std::optional<T>`. This
* function also support \c std::tuple to read the response to
* tuples. At the moment this feature supports only transactions that
* contain simple types or aggregates that don't contain aggregates
* themselves (as in most cases).
*
* Example usage:
*
* @code
* std::unordered_map<std::string, std::string> cont;
* co_await async_read(socket, buffer, adapt(cont));
* @endcode
*
* For a transaction
*
* @code
sr.push(command::multi);
sr.push(command::ping, ...);
sr.push(command::incr, ...);
sr.push_range(command::rpush, ...);
sr.push(command::lrange, ...);
sr.push(command::incr, ...);
sr.push(command::exec);
co_await async_write(socket, buffer(request));
// Reads the response to a transaction
std::tuple<std::string, int, int, std::vector<std::string>, int> execs;
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(execs));
* @endcode
*/
template<class T>
auto adapt(T& t) noexcept
{ return response_traits<T>::adapt(t); }
} // resp3
} // aedis

View File

@@ -1,382 +0,0 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <set>
#include <optional>
#include <system_error>
#include <map>
#include <list>
#include <deque>
#include <vector>
#include <charconv>
#include <aedis/resp3/type.hpp>
#include <aedis/resp3/node.hpp>
#include <aedis/resp3/serializer.hpp>
#include <aedis/resp3/adapter/error.hpp>
namespace aedis {
namespace resp3 {
namespace adapter {
namespace detail {
template <class T>
typename std::enable_if<std::is_integral<T>::value, void>::type
from_string(
T& i,
char const* data,
std::size_t data_size,
std::error_code& ec)
{
auto const res = std::from_chars(data, data + data_size, i);
if (res.ec != std::errc())
ec = std::make_error_code(res.ec);
}
template <class CharT, class Traits, class Allocator>
void
from_string(
std::basic_string<CharT, Traits, Allocator>& s,
char const* data,
std::size_t data_size,
std::error_code&)
{
s.assign(data, data_size);
}
void set_on_resp3_error(type t, std::error_code& ec)
{
switch (t) {
case type::simple_error: ec = adapter::error::simple_error; return;
case type::blob_error: ec = adapter::error::blob_error; return;
case type::null: ec = adapter::error::null; return;
default: return;
}
}
// For optional responses.
void set_on_resp3_error2(type t, std::error_code& ec)
{
switch (t) {
case type::simple_error: ec = adapter::error::simple_error; return;
case type::blob_error: ec = adapter::error::blob_error; return;
default: return;
}
}
// Adapter that ignores responses.
struct ignore {
void
operator()(
type, std::size_t, std::size_t, char const*, std::size_t,
std::error_code&) { }
};
template <class Container>
class general {
private:
Container* result_;
public:
general(Container* c = nullptr): result_(c) {}
/** @brief Function called by the parser when new data has been processed.
*
* Users who what to customize their response types are required to derive
* from this class and override this function, see examples.
*
* \param t The RESP3 type of the data.
*
* \param n When t is an aggregate data type this will contain its size
* (see also element_multiplicity) for simple data types this is always 1.
*
* \param depth The element depth in the tree.
*
* \param data A pointer to the data.
*
* \param size The size of data.
*/
void
operator()(
type t,
std::size_t aggregate_size,
std::size_t depth,
char const* data,
std::size_t size,
std::error_code&)
{
result_->emplace_back(t, aggregate_size, depth, std::string{data, size});
}
};
template <class Node>
class adapter_node {
private:
Node* result_;
public:
adapter_node(Node* t = nullptr) : result_(t) {}
void
operator()(
type t,
std::size_t aggregate_size,
std::size_t depth,
char const* data,
std::size_t data_size,
std::error_code&)
{
result_->data_type = t;
result_->aggregate_size = aggregate_size;
result_->depth = depth;
result_->data.assign(data, data_size);
}
};
// Adapter for RESP3 simple data types.
template <class T>
class simple {
private:
T* result_;
public:
simple(T* t = nullptr) : result_(t) {}
void
operator()(
type t,
std::size_t aggregate_size,
std::size_t depth,
char const* data,
std::size_t data_size,
std::error_code& ec)
{
set_on_resp3_error(t, ec);
if (is_aggregate(t)) {
ec = adapter::error::expects_simple_type;
return;
}
assert(aggregate_size == 1);
from_string(*result_, data, data_size, ec);
}
};
template <class T>
class simple_optional {
private:
std::optional<T>* result_;
public:
simple_optional(std::optional<T>* o = nullptr) : result_(o) {}
void
operator()(
type t,
std::size_t aggregate_size,
std::size_t depth,
char const* data,
std::size_t data_size,
std::error_code& ec)
{
set_on_resp3_error2(t, ec);
if (is_aggregate(t)) {
ec = adapter::error::expects_simple_type;
return;
}
assert(aggregate_size == 1);
if (depth != 0) {
ec = adapter::error::nested_unsupported;
return;
}
if (t == type::null)
return;
if (!result_->has_value())
*result_ = T{};
from_string(result_->value(), data, data_size, ec);
}
};
/* A std::vector adapter.
*/
template <class Container>
class vector {
private:
int i_ = -1;
Container* result_;
public:
vector(Container* v = nullptr) : result_{v} {}
void
operator()(type t,
std::size_t aggregate_size,
std::size_t depth,
char const* data,
std::size_t data_size,
std::error_code& ec)
{
set_on_resp3_error(t, ec);
if (is_aggregate(t)) {
if (i_ != -1) {
ec = adapter::error::nested_unsupported;
return;
}
auto const m = element_multiplicity(t);
result_->resize(m * aggregate_size);
++i_;
} else {
assert(aggregate_size == 1);
from_string(result_->at(i_), data, data_size, ec);
++i_;
}
}
};
template <class Container>
class list {
private:
Container* result_;
public:
list(Container* ref = nullptr): result_(ref) {}
void
operator()(type t,
std::size_t aggregate_size,
std::size_t depth,
char const* data,
std::size_t data_size,
std::error_code& ec)
{
set_on_resp3_error(t, ec);
if (is_aggregate(t)) {
if (depth != 0) {
ec = adapter::error::nested_unsupported;
return;
}
return;
}
assert(aggregate_size == 1);
if (depth != 1) {
ec = adapter::error::nested_unsupported;
return;
}
result_->push_back({});
from_string(result_->back(), data, data_size, ec);
}
};
template <class Container>
class set {
private:
Container* result_;
typename Container::iterator hint_;
public:
set(Container* c = nullptr)
: result_(c)
, hint_(std::end(*c))
{}
void
operator()(type t,
std::size_t aggregate_size,
std::size_t depth,
char const* data,
std::size_t data_size,
std::error_code& ec)
{
set_on_resp3_error(t, ec);
if (t == type::set) {
assert(depth == 0);
return;
}
assert(!is_aggregate(t));
assert(depth == 1);
assert(aggregate_size == 1);
typename Container::key_type obj;
from_string(obj, data, data_size, ec);
if (hint_ == std::end(*result_)) {
auto const ret = result_->insert(std::move(obj));
hint_ = ret.first;
} else {
hint_ = result_->insert(hint_, std::move(obj));
}
}
};
template <class Container>
class map {
private:
Container* result_;
typename Container::iterator current_;
bool on_key_ = true;
public:
map(Container* c = nullptr)
: result_(c)
, current_(std::end(*c))
{}
void
operator()(type t,
std::size_t aggregate_size,
std::size_t depth,
char const* data,
std::size_t data_size,
std::error_code& ec)
{
set_on_resp3_error(t, ec);
if (t == type::map) {
assert(depth == 0);
return;
}
assert(!is_aggregate(t));
assert(depth == 1);
assert(aggregate_size == 1);
if (on_key_) {
typename Container::key_type obj;
from_string(obj, data, data_size, ec);
current_ = result_->insert(current_, {std::move(obj), {}});
} else {
typename Container::mapped_type obj;
from_string(obj, data, data_size, ec);
current_->second = std::move(obj);
}
on_key_ = !on_key_;
}
};
} // detail
} // adapter
} // resp3
} // aedis

View File

@@ -1,87 +0,0 @@
#pragma once
# include <system_error>
namespace aedis {
namespace resp3 {
namespace adapter {
/** \brief Errors that may occurr when reading a response.
* \ingroup any
*/
enum class error
{
/// Expects a simple RESP3 type but got an aggregate.
expects_simple_type = 1,
/// Nested response not supported.
nested_unsupported,
/// Got RESP3 simple error.
simple_error,
/// Got RESP3 blob_error.
blob_error,
/// The tuple used as response has incompatible size.
incompatible_tuple_size,
/// Got RESP3 null type.
null
};
namespace detail {
struct error_category_impl : std::error_category {
/// \todo Fix string lifetime.
char const* name() const noexcept override
{ return "aedis.response_adapter"; }
// TODO: Move to .ipp
std::string message(int ev) const override
{
switch(static_cast<error>(ev)) {
case error::expects_simple_type: return "Expects a simple RESP3 type";
case error::nested_unsupported: return "Nested responses unsupported.";
case error::simple_error: return "Got RESP3 simple-error.";
case error::blob_error: return "Got RESP3 blob-error.";
case error::incompatible_tuple_size: return "The tuple used as response has incompatible size.";
case error::null: return "Got RESP3 null.";
default: assert(false);
}
}
};
inline
std::error_category const& adapter_category()
{
static error_category_impl instance;
return instance;
}
} // detail
inline
std::error_code make_error_code(error e)
{
static detail::error_category_impl const eci{};
return std::error_code{static_cast<int>(e), detail::adapter_category()};
}
inline
std::error_condition make_error_condition(error e)
{
return std::error_condition(static_cast<int>(e), detail::adapter_category());
}
} // adapter
} // resp3
} // aedis
namespace std {
template<>
struct is_error_code_enum<::aedis::resp3::adapter::error> : std::true_type {};
} // std

121
aedis/resp3/compose.hpp Normal file
View File

@@ -0,0 +1,121 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <string>
#include <tuple>
#include <boost/hana.hpp>
#include <boost/utility/string_view.hpp>
namespace aedis {
namespace resp3 {
/** @brief Adds data to the request.
* @ingroup any
*/
template <class Request>
void to_bulk(Request& to, boost::string_view data)
{
auto const str = std::to_string(data.size());
to += "$";
to.append(std::cbegin(str), std::cend(str));
to += "\r\n";
to.append(std::cbegin(data), std::cend(data));
to += "\r\n";
}
template <class Request, class T, typename = typename std::enable_if<std::is_integral<T>::value>::type>
void to_bulk(Request& to, T n)
{
auto const s = std::to_string(n);
to_bulk(to, boost::string_view{s});
}
namespace detail {
template <class T>
struct add_bulk_impl {
template <class Request>
static void add(Request& to, T const& from)
{
using namespace aedis::resp3;
to_bulk(to, from);
}
};
template <class U, class V>
struct add_bulk_impl<std::pair<U, V>> {
template <class Request>
static void add(Request& to, std::pair<U, V> const& from)
{
using namespace aedis::resp3;
to_bulk(to, from.first);
to_bulk(to, from.second);
}
};
template <class ...Ts>
struct add_bulk_impl<boost::hana::tuple<Ts...>> {
template <class Request>
static void add(Request& to, boost::hana::tuple<Ts...> const& from)
{
using boost::hana::for_each;
// Fold expressions is C++17 so we use hana.
//(resp3::add_bulk(*request_, args), ...);
for_each(from, [&](auto const& e) {
using namespace aedis::resp3;
to_bulk(to, e);
});
}
};
} // detail
/** @brief Adds a resp3 header to the store to.
* @ingroup any
*/
template <class Request>
void add_header(Request& to, std::size_t size)
{
auto const str = std::to_string(size);
to += "*";
to.append(std::cbegin(str), std::cend(str));
to += "\r\n";
}
/** @brief Adds a rep3 bulk to the request.
* @ingroup any
*
* This function adds \c data as a bulk string to the request \c to.
*/
template <class Request, class T>
void add_bulk(Request& to, T const& data)
{
detail::add_bulk_impl<T>::add(to, data);
}
template <class>
struct bulk_counter;
template <class>
struct bulk_counter {
static constexpr auto size = 1U;
};
template <class T, class U>
struct bulk_counter<std::pair<T, U>> {
static constexpr auto size = 2U;
};
} // resp3
} // aedis

View File

@@ -1,87 +0,0 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <string>
#include <string_view>
#include <utility>
namespace aedis {
namespace resp3 {
namespace detail {
template <class>
struct needs_to_string : std::true_type {};
template <> struct needs_to_string<std::string> : std::false_type {};
template <> struct needs_to_string<std::string_view> : std::false_type {};
template <> struct needs_to_string<char const*> : std::false_type {};
template <> struct needs_to_string<char*> : std::false_type {};
template <std::size_t N>
struct needs_to_string<char[N]> : std::false_type {};
template <std::size_t N>
struct needs_to_string<char const[N]> : std::false_type {};
template <class Storage>
void add_header(Storage& to, int size)
{
// std::string does not support allocators.
using std::to_string;
auto const str = to_string(size);
to += "*";
to.append(std::cbegin(str), std::cend(str));
to += "\r\n";
}
template <class Storage>
void add_bulk(Storage& to, std::string_view data)
{
// std::string does not support allocators.
using std::to_string;
auto const str = to_string(std::size(data));
to += "$";
to.append(std::cbegin(str), std::cend(str));
to += "\r\n";
to += data;
to += "\r\n";
}
template <class Storage, class T>
void add_bulk(Storage& to, T const& data, typename std::enable_if<needs_to_string<T>::value, bool>::type = false)
{
using std::to_string;
auto const s = to_string(data);
add_bulk(to, s);
}
// Overload for pairs.
// TODO: Overload for tuples.
template <class Storage, class T1, class T2>
void add_bulk(Storage& to, std::pair<T1, T2> const& pair)
{
add_bulk(to, pair.first);
add_bulk(to, pair.second);
}
template <class>
struct value_type_size {
static constexpr auto size = 1U;
};
template <class T, class U>
struct value_type_size<std::pair<T, U>> {
static constexpr auto size = 2U;
};
} // detail
} // resp3
} // aedis

View File

@@ -1,4 +1,4 @@
/* Copyright (c) 2019 - 2021 Marcelo Zimbres Silva (mzimbres at gmail dot com)
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres at gmail dot com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -12,6 +12,16 @@ namespace aedis {
namespace resp3 {
namespace detail {
std::size_t parse_uint(char const* data, std::size_t size, boost::system::error_code& ec)
{
static constexpr boost::spirit::x3::uint_parser<std::size_t, 10> p{};
std::size_t ret;
if (!parse(data, data + size, p, ret))
ec = error::not_a_number;
return ret;
}
type to_type(char c)
{
switch (c) {

View File

@@ -8,22 +8,29 @@
#pragma once
#include <string_view>
#include <charconv>
#include <system_error>
#include <limits>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/home/x3.hpp>
#include <boost/utility/string_view.hpp>
#include <aedis/resp3/error.hpp>
#include <aedis/resp3/node.hpp>
namespace aedis {
namespace resp3 {
namespace detail {
std::size_t parse_uint(char const* data, std::size_t size, boost::system::error_code& ec);
// Converts a wire-format RESP3 type (char) to a resp3 type.
type to_type(char c);
template <class ResponseAdapter>
class parser {
private:
using node_type = node<boost::string_view>;
static constexpr std::size_t max_embedded_depth = 5;
ResponseAdapter adapter_;
@@ -54,24 +61,21 @@ public:
// Returns the number of bytes that have been consumed.
std::size_t
advance(char const* data, std::size_t n, std::error_code& ec)
consume(char const* data, std::size_t n, boost::system::error_code& ec)
{
if (bulk_ != type::invalid) {
n = bulk_length_ + 2;
switch (bulk_) {
case type::streamed_string_part:
{
if (bulk_length_ == 0) {
sizes_[depth_] = 1;
} else {
adapter_(bulk_, 1, depth_, data, bulk_length_, ec);
if (ec)
return 0;
}
assert(bulk_length_ != 0);
adapter_({bulk_, 1, depth_, {data, bulk_length_}}, ec);
if (ec)
return 0;
} break;
default:
{
adapter_(bulk_, 1, depth_, data, bulk_length_, ec);
adapter_({bulk_, 1, depth_, {data, bulk_length_}}, ec);
if (ec)
return 0;
}
@@ -83,46 +87,75 @@ public:
} else if (sizes_[depth_] != 0) {
auto const t = to_type(*data);
switch (t) {
case type::blob_error:
case type::verbatim_string:
case type::streamed_string_part:
{
auto const r =
std::from_chars(data + 1, data + n - 2, bulk_length_);
if (r.ec != std::errc()) {
ec = error::not_a_number;
bulk_length_ = parse_uint(data + 1, n - 2, ec);
if (ec)
return 0;
}
bulk_ = t;
if (bulk_length_ == 0) {
adapter_({type::streamed_string_part, 1, depth_, {}}, ec);
sizes_[depth_] = 0; // We are done.
} else {
bulk_ = type::streamed_string_part;
}
} break;
case type::blob_error:
case type::verbatim_string:
case type::blob_string:
{
if (*(data + 1) == '?') {
// NOTE: This can only be triggered with blob_string.
// Trick: A streamed string is read as an aggregate
// of infinite lenght. When the streaming is done
// the server is supposed to send a part with lenght
// the server is supposed to send a part with length
// 0.
sizes_[++depth_] = (std::numeric_limits<std::size_t>::max)();
} else {
auto const r =
std::from_chars(data + 1, data + n - 2, bulk_length_);
if (r.ec != std::errc()) {
ec = error::not_a_number;
return 0;
}
bulk_length_ = parse_uint(data + 1, n - 2, ec);
if (ec)
return 0;
bulk_ = type::blob_string;
bulk_ = t;
}
} break;
case type::simple_error:
case type::number:
case type::doublean:
case type::boolean:
{
if (n == 3) {
ec = error::empty_field;
return 0;
}
if (*(data + 1) != 'f' && *(data + 1) != 't') {
ec = error::unexpected_bool_value;
return 0;
}
adapter_({t, 1, depth_, {data + 1, n - 3}}, ec);
if (ec)
return 0;
--sizes_[depth_];
} break;
case type::doublean:
case type::big_number:
case type::number:
{
if (n == 3) {
ec = error::empty_field;
return 0;
}
adapter_({t, 1, depth_, {data + 1, n - 3}}, ec);
if (ec)
return 0;
--sizes_[depth_];
} break;
case type::simple_error:
case type::simple_string:
{
adapter_(t, 1, depth_, data + 1, n - 3, ec);
adapter_({t, 1, depth_, {data + 1, n - 3}}, ec);
if (ec)
return 0;
@@ -130,7 +163,7 @@ public:
} break;
case type::null:
{
adapter_(type::null, 1, depth_, nullptr, 0, ec);
adapter_({type::null, 1, depth_, {}}, ec);
if (ec)
return 0;
@@ -142,14 +175,11 @@ public:
case type::attribute:
case type::map:
{
std::size_t l;
auto const r = std::from_chars(data + 1, data + n - 2, l);
if (r.ec != std::errc()) {
ec = error::not_a_number;
return 0;
}
auto const l = parse_uint(data + 1, n - 2, ec);
if (ec)
return 0;
adapter_(t, l, depth_, nullptr, 0, ec);
adapter_({t, l, depth_, {}}, ec);
if (ec)
return 0;

View File

@@ -1,4 +1,4 @@
/* Copyright (c) 2019 - 2021 Marcelo Zimbres Silva (mzimbres at gmail dot com)
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres at gmail dot com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -9,15 +9,24 @@
#include <string_view>
#include <aedis/config.hpp>
#include <aedis/resp3/detail/parser.hpp>
#include <boost/asio/read.hpp>
#include <boost/asio/read_until.hpp>
#include <boost/asio/coroutine.hpp>
#include <boost/core/ignore_unused.hpp>
#include <boost/utility/string_view.hpp>
#include <aedis/resp3/detail/parser.hpp>
namespace aedis {
namespace resp3 {
namespace detail {
// TODO: Use asio::coroutine.
#include <boost/asio/yield.hpp>
struct ignore_response {
void operator()(node<boost::string_view>, boost::system::error_code&) { }
};
template <
class AsyncReadStream,
class DynamicBuffer,
@@ -28,7 +37,8 @@ private:
DynamicBuffer buf_;
parser<ResponseAdapter> parser_;
std::size_t consumed_;
int start_;
std::size_t buffer_size_;
boost::asio::coroutine coro_;
public:
parse_op(AsyncReadStream& stream, DynamicBuffer buf, ResponseAdapter adapter)
@@ -36,7 +46,6 @@ public:
, buf_ {buf}
, parser_ {adapter}
, consumed_{0}
, start_{1}
{ }
template <class Self>
@@ -44,15 +53,16 @@ public:
, boost::system::error_code ec = {}
, std::size_t n = 0)
{
switch (start_) {
for (;;) {
if (parser_.bulk() == type::invalid) {
case 1:
start_ = 0;
net::async_read_until(stream_, buf_, "\r\n", std::move(self));
reenter (coro_) for (;;) {
if (parser_.bulk() == type::invalid) {
yield
boost::asio::async_read_until(stream_, buf_, "\r\n", std::move(self));
if (ec) {
self.complete(ec, 0);
return;
}
} else {
// On a bulk read we can't read until delimiter since the
// payload may contain the delimiter itself so we have to
// read the whole chunk. However if the bulk blob is small
@@ -60,47 +70,49 @@ public:
// read), in which case there is no need of initiating
// another async op, otherwise we have to read the missing
// bytes.
if (std::size(buf_) < (parser_.bulk_length() + 2)) {
start_ = 0;
auto const s = std::size(buf_);
auto const l = parser_.bulk_length();
auto const to_read = l + 2 - s;
buf_.grow(to_read);
net::async_read(stream_, buf_.data(s, to_read), net::transfer_all(), std::move(self));
return;
if (buf_.size() < (parser_.bulk_length() + 2)) {
buffer_size_ = buf_.size();
buf_.grow(parser_.bulk_length() + 2 - buffer_size_);
yield
boost::asio::async_read(
stream_,
buf_.data(buffer_size_, parser_.bulk_length() + 2 - buffer_size_),
boost::asio::transfer_all(),
std::move(self));
if (ec) {
self.complete(ec, 0);
return;
}
}
default:
{
if (ec) {
self.complete(ec, 0);
return;
}
n = parser_.bulk_length() + 2;
assert(buf_.size() >= n);
}
n = parser_.advance((char const*)buf_.data(0, n).data(), n, ec);
if (ec) {
self.complete(ec, 0);
return;
}
n = parser_.consume((char const*)buf_.data(0, n).data(), n, ec);
if (ec) {
self.complete(ec, 0);
return;
}
buf_.consume(n);
consumed_ += n;
if (parser_.done()) {
self.complete({}, consumed_);
return;
}
}
buf_.consume(n);
consumed_ += n;
if (parser_.done()) {
self.complete({}, consumed_);
return;
}
}
}
};
// TODO: Use asio::coroutine.
template <class AsyncReadStream, class DynamicBuffer>
class type_op {
private:
AsyncReadStream& stream_;
DynamicBuffer buf_;
boost::asio::coroutine coro_;
public:
type_op(AsyncReadStream& stream, DynamicBuffer buf)
@@ -113,25 +125,26 @@ public:
, boost::system::error_code ec = {}
, std::size_t n = 0)
{
boost::ignore_unused(n);
reenter (coro_) {
if (ec) {
self.complete(ec, type::invalid);
return;
boost::ignore_unused(n);
if (buf_.size() == 0) {
yield boost::asio::async_read_until(stream_, buf_, "\r\n", std::move(self));
if (ec) {
self.complete(ec, type::invalid);
return;
}
}
auto const* data = (char const*)buf_.data(0, n).data();
auto const type = to_type(*data);
self.complete(ec, type);
}
if (std::size(buf_) == 0) {
net::async_read_until(stream_, buf_, "\r\n", std::move(self));
return;
}
auto const* data = (char const*)buf_.data(0, n).data();
auto const type = to_type(*data);
self.complete(ec, type);
return;
}
};
#include <boost/asio/unyield.hpp>
} // detail
} // resp3
} // aedis

View File

@@ -1,8 +1,13 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
# include <system_error>
/// \file error.hpp
#include <boost/system/error_code.hpp>
namespace aedis {
namespace resp3 {
@@ -22,52 +27,24 @@ enum class error
unexpected_read_size,
/// The maximum depth of a nested response was exceeded.
exceeeds_max_nested_depth
exceeeds_max_nested_depth,
/// Unexpected bool value
unexpected_bool_value,
/// Expected field value is empty.
empty_field
};
namespace detail {
struct error_category_impl : std::error_category {
char const* name() const noexcept override
{ return "aedis.resp3"; }
std::string message(int ev) const override
{
switch(static_cast<error>(ev)) {
case error::invalid_type: return "Invalid resp3 type.";
case error::not_a_number: return "Can't convert string to number.";
case error::unexpected_read_size: return "Unexpected read size.";
case error::exceeeds_max_nested_depth: return "Exceeds the maximum number of nested responses.";
default: assert(false);
}
}
};
inline
std::error_category const& category()
{
static error_category_impl instance;
return instance;
}
} // detail
/** \brief Converts an error in an std::error_code object.
/** \brief Converts an error in an boost::system::error_code object.
* \ingroup any
*/
inline
std::error_code make_error_code(error e)
{
static detail::error_category_impl const eci{};
return std::error_code{static_cast<int>(e), detail::category()};
}
boost::system::error_code make_error_code(error e);
inline
std::error_condition make_error_condition(error e)
{
return std::error_condition(static_cast<int>(e), detail::category());
}
/** \brief todo
* \ingroup any
*/
boost::system::error_condition make_error_condition(error e);
} // resp3
} // aedis

View File

@@ -0,0 +1,54 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include <aedis/resp3/error.hpp>
namespace aedis {
namespace resp3 {
namespace detail {
struct error_category_impl : boost::system::error_category {
char const* name() const noexcept override
{
return "aedis.resp3";
}
std::string message(int ev) const override
{
switch(static_cast<error>(ev)) {
case error::invalid_type: return "Invalid resp3 type.";
case error::not_a_number: return "Can't convert string to number.";
case error::unexpected_read_size: return "Unexpected read size.";
case error::exceeeds_max_nested_depth: return "Exceeds the maximum number of nested responses.";
case error::unexpected_bool_value: return "Unexpected bool value.";
case error::empty_field: return "Expected field value is empty.";
default: assert(false);
}
}
};
boost::system::error_category const& category()
{
static error_category_impl instance;
return instance;
}
} // detail
boost::system::error_code make_error_code(error e)
{
return boost::system::error_code{static_cast<int>(e), detail::category()};
}
boost::system::error_condition make_error_condition(error e)
{
return boost::system::error_condition(static_cast<int>(e), detail::category());
}
} // resp3
} // aedis

View File

@@ -1,67 +0,0 @@
/* Copyright (c) 2019 - 2022 Marcelo Zimbres Silva (mzimbres at gmail dot com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <aedis/resp3/node.hpp>
namespace aedis {
namespace resp3 {
std::string to_string(node const& in)
{
std::string out;
out += std::to_string(in.depth);
out += '\t';
out += to_string(in.data_type);
out += '\t';
out += std::to_string(in.aggregate_size);
out += '\t';
if (!is_aggregate(in.data_type))
out += in.data;
return out;
}
bool operator==(node const& a, node const& b)
{
return a.aggregate_size == b.aggregate_size
&& a.depth == b.depth
&& a.data_type == b.data_type
&& a.data == b.data;
};
std::ostream& operator<<(std::ostream& os, node const& o)
{
os << to_string(o);
return os;
}
std::ostream& operator<<(std::ostream& os, std::vector<node> const& r)
{
os << to_string(r);
return os;
}
// TODO: Output like in redis-cli.
std::string to_string(std::vector<node> const& vec)
{
if (std::empty(vec))
return {};
auto begin = std::cbegin(vec);
std::string res;
for (; begin != std::prev(std::cend(vec)); ++begin) {
res += to_string(*begin);
res += '\n';
}
res += to_string(*begin);
return res;
}
} // resp3
} // aedis

View File

@@ -1,4 +1,4 @@
/* Copyright (c) 2019 - 2021 Marcelo Zimbres Silva (mzimbres at gmail dot com)
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this

View File

@@ -20,10 +20,21 @@ namespace resp3 {
*
* Redis responses are the pre-order view of the response tree (see
* https://en.wikipedia.org/wiki/Tree_traversal#Pre-order,_NLR).
*
* The node class represent one element in the response tree. The string type
* is a template give more flexibility, for example
*
* @li @c boost::string_view
* @li @c std::string
* @li @c boost::static_string
*
* \remark Any Redis response can be received in an array of nodes, for
* example \c std::vector<node<std::string>>.
*/
template <class String>
struct node {
/// The RESP3 type of the data in this node.
type data_type;
resp3::type data_type;
/// The number of elements of an aggregate.
std::size_t aggregate_size;
@@ -31,38 +42,54 @@ struct node {
/// The depth of this node in the response tree.
std::size_t depth;
/// The actual data. For aggregate data types this is always empty.
std::string data;
/// The actual data. For aggregate types this is always empty.
String value;
};
/** \brief Converts the node to a string.
* \ingroup any
*
* \param obj The node object.
* \param in The node object.
*/
std::string to_string(node const& obj);
template <class String>
std::string to_string(node<String> const& in)
{
std::string out;
out += std::to_string(in.depth);
out += '\t';
out += to_string(in.data_type);
out += '\t';
out += std::to_string(in.aggregate_size);
out += '\t';
if (!is_aggregate(in.data_type))
out.append(in.value.data(), in.value.size());
return out;
}
/** \brief Compares a node for equality.
* \ingroup any
*/
bool operator==(node const& a, node const& b);
template <class String>
bool operator==(node<String> const& a, node<String> const& b)
{
return a.aggregate_size == b.aggregate_size
&& a.depth == b.depth
&& a.data_type == b.data_type
&& a.value == b.value;
};
/** \brief Writes the node to the stream.
* \ingroup any
*
* NOTE: Binary data is not converted to text.
*/
std::ostream& operator<<(std::ostream& os, node const& o);
template <class String>
std::ostream& operator<<(std::ostream& os, node<String> const& o)
{
os << to_string(o);
return os;
}
/** \brief Writes the response to the output stream
* \ingroup any
*/
std::string to_string(std::vector<node> const& vec);
/** \brief Writes the response to the output stream
* \ingroup any
*/
std::ostream& operator<<(std::ostream& os, std::vector<node> const& r);
} // resp3
} // adapter
} // aedis

View File

@@ -7,13 +7,13 @@
#pragma once
#include <aedis/config.hpp>
#include <aedis/resp3/type.hpp>
#include <aedis/resp3/adapt.hpp>
#include <aedis/resp3/response_traits.hpp>
#include <aedis/resp3/detail/parser.hpp>
#include <aedis/resp3/detail/read_ops.hpp>
#include <boost/asio/read.hpp>
#include <boost/asio/compose.hpp>
#include <boost/asio/async_result.hpp>
#include <boost/asio/yield.hpp>
namespace aedis {
@@ -44,12 +44,12 @@ read(
ResponseAdapter adapter,
boost::system::error_code& ec)
{
detail::parser p {adapter};
detail::parser<ResponseAdapter> p {adapter};
std::size_t n = 0;
std::size_t consumed = 0;
do {
if (p.bulk() == type::invalid) {
n = net::read_until(stream, buf, "\r\n", ec);
n = boost::asio::read_until(stream, buf, "\r\n", ec);
if (ec)
return 0;
@@ -58,12 +58,12 @@ read(
return 0;
}
} else {
auto const s = std::size(buf);
auto const s = buf.size();
auto const l = p.bulk_length();
if (s < (l + 2)) {
auto const to_read = l + 2 - s;
buf.grow(to_read);
n = net::read(stream, buf.data(s, to_read), ec);
n = boost::asio::read(stream, buf.data(s, to_read), ec);
if (ec)
return 0;
@@ -74,9 +74,8 @@ read(
}
}
std::error_code ec;
auto const* data = (char const*) buf.data(0, n).data();
n = p.advance(data, n, ec);
n = p.consume(data, n, ec);
if (ec)
return 0;
@@ -102,12 +101,12 @@ read(
template<
class SyncReadStream,
class DynamicBuffer,
class ResponseAdapter = response_traits<void>::adapter_type>
class ResponseAdapter = detail::ignore_response>
std::size_t
read(
SyncReadStream& stream,
DynamicBuffer buf,
ResponseAdapter adapter = adapt())
ResponseAdapter adapter = ResponseAdapter{})
{
boost::system::error_code ec;
auto const n = resp3::read(stream, buf, adapter, ec);
@@ -141,17 +140,17 @@ read(
template <
class AsyncReadStream,
class DynamicBuffer,
class ResponseAdapter = response_traits<void>::adapter_type,
class CompletionToken = net::default_completion_token_t<typename AsyncReadStream::executor_type>
class ResponseAdapter = detail::ignore_response,
class CompletionToken = boost::asio::default_completion_token_t<typename AsyncReadStream::executor_type>
>
auto async_read(
AsyncReadStream& stream,
DynamicBuffer buffer,
ResponseAdapter adapter = adapt(),
ResponseAdapter adapter = ResponseAdapter{},
CompletionToken&& token =
net::default_completion_token_t<typename AsyncReadStream::executor_type>{})
boost::asio::default_completion_token_t<typename AsyncReadStream::executor_type>{})
{
return net::async_compose
return boost::asio::async_compose
< CompletionToken
, void(boost::system::error_code, std::size_t)
>(detail::parse_op<AsyncReadStream, DynamicBuffer, ResponseAdapter> {stream, buffer, adapter},
@@ -177,15 +176,15 @@ template <
class AsyncReadStream,
class DynamicBuffer,
class CompletionToken =
net::default_completion_token_t<typename AsyncReadStream::executor_type>
boost::asio::default_completion_token_t<typename AsyncReadStream::executor_type>
>
auto async_read_type(
AsyncReadStream& stream,
DynamicBuffer buffer,
CompletionToken&& token =
net::default_completion_token_t<typename AsyncReadStream::executor_type>{})
boost::asio::default_completion_token_t<typename AsyncReadStream::executor_type>{})
{
return net::async_compose
return boost::asio::async_compose
< CompletionToken
, void(boost::system::error_code, type)
>(detail::type_op<AsyncReadStream, DynamicBuffer> {stream, buffer}, token, stream);

View File

@@ -1,229 +0,0 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <set>
#include <array>
#include <unordered_set>
#include <list>
#include <deque>
#include <vector>
#include <charconv>
#include <tuple>
#include <variant>
#include <boost/mp11.hpp>
#include <aedis/resp3/type.hpp>
#include <aedis/resp3/adapter/detail/adapters.hpp>
#include <aedis/resp3/adapter/error.hpp>
namespace aedis {
namespace resp3 {
/** \brief Traits class for response objects.
* \ingroup any
*/
template <class T>
struct response_traits
{
/// The response type.
using response_type = T;
/// The adapter type.
using adapter_type = adapter::detail::simple<response_type>;
/// Returns an adapter for the reponse r
static auto adapt(response_type& r) noexcept { return adapter_type{&r}; }
};
/// Template typedef for response_traits.
template <class T>
using response_traits_t = typename response_traits<T>::adapter_type;
template <class T>
struct response_traits<std::optional<T>>
{
using response_type = std::optional<T>;
using adapter_type = adapter::detail::simple_optional<typename response_type::value_type>;
static auto adapt(response_type& i) noexcept { return adapter_type{&i}; }
};
template <class T, class Allocator>
struct response_traits<std::vector<T, Allocator>>
{
using response_type = std::vector<T, Allocator>;
using adapter_type = adapter::detail::vector<response_type>;
static auto adapt(response_type& v) noexcept { return adapter_type{&v}; }
};
template <>
struct response_traits<node>
{
using response_type = node;
using adapter_type = adapter::detail::adapter_node<response_type>;
static auto adapt(response_type& v) noexcept { return adapter_type{&v}; }
};
template <class Allocator>
struct response_traits<std::vector<node, Allocator>>
{
using response_type = std::vector<node, Allocator>;
using adapter_type = adapter::detail::general<response_type>;
static auto adapt(response_type& v) noexcept { return adapter_type{&v}; }
};
template <class T, class Allocator>
struct response_traits<std::list<T, Allocator>>
{
using response_type = std::list<T, Allocator>;
using adapter_type = adapter::detail::list<response_type>;
static auto adapt(response_type& v) noexcept { return adapter_type{&v}; }
};
template <class T, class Allocator>
struct response_traits<std::deque<T, Allocator>>
{
using response_type = std::deque<T, Allocator>;
using adapter_type = adapter::detail::list<response_type>;
static auto adapt(response_type& v) noexcept { return adapter_type{&v}; }
};
template <class Key, class Compare, class Allocator>
struct response_traits<std::set<Key, Compare, Allocator>>
{
using response_type = std::set<Key, Compare, Allocator>;
using adapter_type = adapter::detail::set<response_type>;
static auto adapt(response_type& s) noexcept { return adapter_type{&s}; }
};
template <class Key, class Hash, class KeyEqual, class Allocator>
struct response_traits<std::unordered_set<Key, Hash, KeyEqual, Allocator>>
{
using response_type = std::unordered_set<Key, Hash, KeyEqual, Allocator>;
using adapter_type = adapter::detail::set<response_type>;
static auto adapt(response_type& s) noexcept { return adapter_type{&s}; }
};
template <class Key, class T, class Compare, class Allocator>
struct response_traits<std::map<Key, T, Compare, Allocator>>
{
using response_type = std::map<Key, T, Compare, Allocator>;
using adapter_type = adapter::detail::map<response_type>;
static auto adapt(response_type& s) noexcept { return adapter_type{&s}; }
};
template <class Key, class Hash, class KeyEqual, class Allocator>
struct response_traits<std::unordered_map<Key, Hash, KeyEqual, Allocator>>
{
using response_type = std::unordered_map<Key, Hash, KeyEqual, Allocator>;
using adapter_type = adapter::detail::map<response_type>;
static auto adapt(response_type& s) noexcept { return adapter_type{&s}; }
};
template <>
struct response_traits<void>
{
using response_type = void;
using adapter_type = adapter::detail::ignore;
static auto adapt() noexcept { return adapter_type{}; }
};
namespace adapter {
namespace detail {
// Duplicated here to avoid circular include dependency.
template<class T>
auto internal_adapt(T& t) noexcept
{ return response_traits<T>::adapt(t); }
template <std::size_t N>
struct assigner {
template <class T1, class T2>
static void assign(T1& dest, T2& from)
{
dest[N].template emplace<N>(internal_adapt(std::get<N>(from)));
assigner<N - 1>::assign(dest, from);
}
};
template <>
struct assigner<0> {
template <class T1, class T2>
static void assign(T1& dest, T2& from)
{
dest[0] = internal_adapt(std::get<0>(from));
}
};
template <class Tuple>
class flat_transaction_adapter {
private:
using variant_type =
boost::mp11::mp_rename<boost::mp11::mp_transform<response_traits_t, Tuple>, std::variant>;
std::size_t i_ = 0;
std::size_t aggregate_size_ = 0;
std::array<variant_type, std::tuple_size<Tuple>::value> adapters_;
public:
flat_transaction_adapter(Tuple* r)
{ assigner<std::tuple_size<Tuple>::value - 1>::assign(adapters_, *r); }
void count(type t, std::size_t aggregate_size, std::size_t depth)
{
if (depth == 1) {
if (is_aggregate(t))
aggregate_size_ = aggregate_size;
else
++i_;
return;
}
if (--aggregate_size_ == 0)
++i_;
}
void
operator()(
type t,
std::size_t aggregate_size,
std::size_t depth,
char const* data,
std::size_t size,
std::error_code& ec)
{
if (depth == 0) {
if (aggregate_size != std::tuple_size<Tuple>::value) {
ec = adapter::error::incompatible_tuple_size;
return;
}
return;
}
std::visit([&](auto& arg){arg(t, aggregate_size, depth, data, size, ec);}, adapters_[i_]);
count(t, aggregate_size, depth);
}
};
} // detail
} // adapter
// The adapter of responses to transactions, move it to its own header?
template <class... Ts>
struct response_traits<std::tuple<Ts...>>
{
using response_type = std::tuple<Ts...>;
using adapter_type = adapter::detail::flat_transaction_adapter<response_type>;
static auto adapt(response_type& r) noexcept { return adapter_type{&r}; }
};
} // resp3
} // aedis

View File

@@ -52,7 +52,13 @@ enum class command {
/// https://redis.io/commands/unsubscribe
unsubscribe,
/// Unknown/invalid command.
unknown
invalid,
// For internal usege only, users should ignore this.
multi,
// For internal usege only, users should ignore this.
discard,
// For internal usege only, users should ignore this.
exec,
};
/** \brief Converts a sentinel command to a string

View File

@@ -10,6 +10,6 @@
#include <aedis/redis/impl/command.ipp>
#include <aedis/sentinel/impl/command.ipp>
#include <aedis/resp3/impl/type.ipp>
#include <aedis/resp3/impl/node.ipp>
#include <aedis/redis/experimental/impl/client.ipp>
#include <aedis/resp3/detail/impl/parser.ipp>
#include <aedis/resp3/impl/error.ipp>
#include <aedis/adapter/impl/error.ipp>

View File

@@ -1,5 +1,5 @@
AC_PREREQ([2.69])
AC_INIT([Aedis], [0.0.1], [mzimbres@gmail.com])
AC_INIT([Aedis], [0.1.0], [mzimbres@gmail.com])
AC_CONFIG_MACRO_DIR([m4])
#AC_CONFIG_SRCDIR([src/aedis.cpp])
AC_CONFIG_HEADERS([config.h])
@@ -18,5 +18,10 @@ AC_CHECK_HEADER_STDBOOL
AC_TYPE_UINT64_T
AC_CHECK_TYPES([ptrdiff_t])
AX_CXX_COMPILE_STDCXX(14, , mandatory)
AX_CXX_COMPILE_STDCXX(20, , optional)
AM_CONDITIONAL(HAVE_CXX20,[test x$HAVE_CXX20 == x1])
AC_CONFIG_FILES([Makefile doc/Doxyfile])
AC_OUTPUT

View File

@@ -32,13 +32,13 @@ DOXYFILE_ENCODING = UTF-8
# title of most generated pages and in a few other places.
# The default value is: My Project.
PROJECT_NAME = "Aedis"
PROJECT_NAME = "@PACKAGE_NAME@"
# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
# could be handy for archiving the generated documentation or if some version
# control system is used.
PROJECT_NUMBER = "0.0.1"
PROJECT_NUMBER = "@PACKAGE_VERSION@"
# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a
@@ -179,7 +179,7 @@ STRIP_FROM_PATH =
# specify the list of include paths that are normally passed to the compiler
# using the -I flag.
STRIP_FROM_INC_PATH =
STRIP_FROM_INC_PATH = .
# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but
# less readable) file names. This can be useful is your file systems doesn't
@@ -850,52 +850,7 @@ INPUT_ENCODING = UTF-8
# C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f, *.for, *.tcl, *.vhd,
# *.vhdl, *.ucf, *.qsf and *.ice.
FILE_PATTERNS = *.c \
*.cc \
*.cxx \
*.cpp \
*.c++ \
*.java \
*.ii \
*.ixx \
*.ipp \
*.i++ \
*.inl \
*.ddl \
*.odl \
*.h \
*.hh \
*.hxx \
*.hpp \
*.h++ \
*.cs \
*.d \
*.php \
*.php4 \
*.php5 \
*.phtml \
*.inc \
*.m \
*.markdown \
*.md \
*.mm \
*.dox \
*.doc \
*.txt \
*.py \
*.pyw \
*.f90 \
*.f95 \
*.f03 \
*.f08 \
*.f \
*.for \
*.tcl \
*.vhd \
*.vhdl \
*.ucf \
*.qsf \
*.ice
FILE_PATTERNS = *.hpp *.cpp
# The RECURSIVE tag can be used to specify whether or not subdirectories should
# be searched for input files as well.
@@ -910,7 +865,7 @@ RECURSIVE = YES
# Note that relative paths are relative to the directory from which doxygen is
# run.
EXCLUDE = include/aedis/impl include/aedis/resp3/impl include/aedis/resp3/detail
EXCLUDE =
# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
# directories that are symbolic links (a Unix file system feature) are excluded
@@ -943,7 +898,7 @@ EXCLUDE_SYMBOLS = std
# that contain example code fragments that are included (see the \include
# command).
EXAMPLE_PATH = examples
EXAMPLE_PATH =
# If the value of the EXAMPLE_PATH tag contains directories, you can use the
# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and

View File

@@ -24,9 +24,7 @@ div.contents {
padding: 15px;
}
/*
code
{
background-color:#EFD25E;
background-color:#f0e9ce;
}
*/

View File

@@ -1,123 +0,0 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include <iostream>
#include <vector>
#include <aedis/aedis.hpp>
#include <aedis/src.hpp>
#include "lib/user_session.hpp"
#include "lib/net_utils.hpp"
namespace net = aedis::net;
using aedis::redis::command;
using aedis::resp3::adapt;
using aedis::resp3::experimental::client;
using aedis::resp3::node;
using aedis::resp3::type;
using aedis::user_session;
using aedis::user_session_base;
// TODO: Delete sessions that have expired.
class receiver : public std::enable_shared_from_this<receiver> {
public:
private:
std::vector<node> resps_;
std::vector<std::weak_ptr<user_session_base>> sessions_;
public:
void on_message(std::error_code ec, command cmd)
{
if (ec) {
std::cerr << "Error: " << ec.message() << std::endl;
return;
}
switch (cmd) {
case command::incr:
{
std::cout << "Message so far: " << resps_.front().data << std::endl;
} break;
case command::unknown: // Push
{
for (auto& weak: sessions_) {
if (auto session = weak.lock()) {
session->deliver(resps_.at(3).data);
} else {
std::cout << "Session expired." << std::endl;
}
}
} break;
default: { /* Ignore */ }
}
resps_.clear();
}
auto get_extended_adapter()
{
return [adapter = adapt(resps_)](command, type t, std::size_t aggregate_size, std::size_t depth, char const* data, std::size_t size, std::error_code& ec) mutable
{ return adapter(t, aggregate_size, depth, data, size, ec); };
}
auto add(std::shared_ptr<user_session_base> session)
{ sessions_.push_back(session); }
};
net::awaitable<void> connection_manager(std::shared_ptr<client> db)
{
try {
auto socket = co_await connect();
co_await db->engage(std::move(socket));
} catch (std::exception const& e) {
std::cerr << "Error: " << e.what() << std::endl;
}
}
net::awaitable<void> listener()
{
auto ex = co_await net::this_coro::executor;
net::ip::tcp::acceptor acceptor(ex, {net::ip::tcp::v4(), 55555});
auto recv = std::make_shared<receiver>();
auto on_db_msg = [recv](std::error_code ec, command cmd)
{ recv->on_message(ec, cmd); };
auto db = std::make_shared<client>(ex);
db->set_extended_adapter(recv->get_extended_adapter());
db->set_msg_callback(on_db_msg);
net::co_spawn(ex, connection_manager(db), net::detached);
db->send(command::subscribe, "channel");
auto on_user_msg = [db](std::string const& msg)
{
db->send(command::publish, "channel", msg);
db->send(command::incr, "message-counter");
};
for (;;) {
auto socket = co_await acceptor.async_accept(net::use_awaitable);
auto session = std::make_shared<user_session>(std::move(socket));
recv->add(session);
session->start(on_user_msg);
}
}
int main()
{
try {
net::io_context ioc{1};
net::signal_set signals(ioc, SIGINT, SIGTERM);
signals.async_wait([&](auto, auto){ ioc.stop(); });
co_spawn(ioc, listener(), net::detached);
ioc.run();
} catch (std::exception const& e) {
std::cerr << e.what() << std::endl;
}
}

View File

@@ -1,121 +0,0 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include <iostream>
#include <queue>
#include <vector>
#include <aedis/aedis.hpp>
#include <aedis/src.hpp>
#include "lib/user_session.hpp"
#include "lib/net_utils.hpp"
namespace net = aedis::net;
using aedis::redis::command;
using aedis::user_session;
using aedis::user_session_base;
using aedis::resp3::node;
using aedis::resp3::adapt;
using aedis::resp3::experimental::client;
using aedis::resp3::type;
class receiver : public std::enable_shared_from_this<receiver> {
private:
std::vector<node> resps_;
std::queue<std::weak_ptr<user_session_base>> sessions_;
public:
void on_message(std::error_code ec, command cmd)
{
if (ec) {
std::cerr << "Error: " << ec.message() << std::endl;
return;
}
switch (cmd) {
case command::ping:
{
if (auto session = sessions_.front().lock()) {
session->deliver(resps_.front().data);
} else {
std::cout << "Session expired." << std::endl;
}
sessions_.pop();
} break;
case command::incr:
{
std::cout << "Echos so far: " << resps_.front().data << std::endl;
} break;
default: { /* Ignore */; }
}
resps_.clear();
}
auto get_extended_adapter()
{
return [adapter = adapt(resps_)](command, type t, std::size_t aggregate_size, std::size_t depth, char const* data, std::size_t size, std::error_code& ec) mutable
{ return adapter(t, aggregate_size, depth, data, size, ec); };
}
void add_user_session(std::shared_ptr<user_session_base> session)
{ sessions_.push(session); }
};
net::awaitable<void> connection_manager(std::shared_ptr<client> db)
{
try {
auto socket = co_await connect();
co_await db->engage(std::move(socket));
} catch (std::exception const& e) {
std::cerr << "Error: " << e.what() << std::endl;
}
}
net::awaitable<void> listener()
{
auto ex = co_await net::this_coro::executor;
net::ip::tcp::acceptor acceptor(ex, {net::ip::tcp::v4(), 55555});
auto recv = std::make_shared<receiver>();
auto on_db_msg = [recv](std::error_code ec, command cmd)
{ recv->on_message(ec, cmd); };
auto db = std::make_shared<client>(ex);
db->set_extended_adapter(recv->get_extended_adapter());
db->set_msg_callback(on_db_msg);
net::co_spawn(ex, connection_manager(db), net::detached);
for (;;) {
auto socket = co_await acceptor.async_accept(net::use_awaitable);
auto session = std::make_shared<user_session>(std::move(socket));
auto on_user_msg = [db, recv, session](std::string const& msg)
{
db->send(command::ping, msg);
db->send(command::incr, "echo-counter");
recv->add_user_session(session);
};
session->start(on_user_msg);
}
}
int main()
{
try {
net::io_context ioc{1};
net::signal_set signals(ioc, SIGINT, SIGTERM);
signals.async_wait([&](auto, auto){ ioc.stop(); });
co_spawn(ioc, listener(), net::detached);
ioc.run();
} catch (std::exception const& e) {
std::cerr << e.what() << std::endl;
}
}

View File

@@ -1,88 +0,0 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include <iostream>
#include <vector>
#include <map>
#include <unordered_map>
#include <aedis/aedis.hpp>
#include <aedis/src.hpp>
#include "lib/net_utils.hpp"
namespace resp3 = aedis::resp3;
using aedis::redis::command;
using aedis::redis::make_serializer;
using resp3::adapt;
namespace net = aedis::net;
using net::async_write;
using net::buffer;
using net::dynamic_buffer;
net::awaitable<void> containers()
{
try {
auto socket = co_await connect();
std::map<std::string, std::string> map
{ {"key1", "value1"}
, {"key2", "value2"}
, {"key3", "value3"}
};
// Creates and sends the request.
std::string request;
auto sr = make_serializer(request);
sr.push(command::hello, 3);
sr.push(command::flushall);
sr.push_range(command::hset, "key", std::cbegin(map), std::cend(map));
sr.push(command::hgetall, "key");
sr.push(command::hgetall, "key");
sr.push(command::hgetall, "key");
sr.push(command::quit);
co_await async_write(socket, buffer(request));
// The expected responses
int hset;
std::vector<std::string> hgetall1;
std::map<std::string, std::string> hgetall2;
std::unordered_map<std::string, std::string> hgetall3;
// Reads the responses.
std::string buffer;
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // hello
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // flushall
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(hset));
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(hgetall1));
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(hgetall2));
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(hgetall3));
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // quit
// Prints the responses.
std::cout << "hset: " << hset;
std::cout << "\nhgetall (as vector): ";
for (auto const& e: hgetall1) std::cout << e << ", ";
std::cout << "\nhgetall (as map): ";
for (auto const& e: hgetall2) std::cout << e.first << " ==> " << e.second << "; ";
std::cout << "\nhgetall (as unordered_map): ";
for (auto const& e: hgetall3) std::cout << e.first << " ==> " << e.second << "; ";
std::cout << "\n";
} catch (std::exception const& e) {
std::cerr << e.what() << std::endl;
exit(EXIT_FAILURE);
}
}
int main()
{
net::io_context ioc;
co_spawn(ioc, containers(), net::detached);
ioc.run();
}

View File

@@ -0,0 +1,110 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include <map>
#include <set>
#include <vector>
#include <iostream>
#include <aedis/aedis.hpp>
#include <aedis/src.hpp>
namespace net = boost::asio;
using aedis::resp3::node;
using aedis::adapter::adapt;
using aedis::redis::command;
using client_type = aedis::generic::client<net::ip::tcp::socket, command>;
using response_type = std::vector<node<std::string>>;
using adapter_type = aedis::adapter::adapter_t<response_type>;
// Prints aggregates that don't contain any nested aggregates.
void print_aggregate(response_type const& v)
{
auto const m = element_multiplicity(v.front().data_type);
for (auto i = 0lu; i < m * v.front().aggregate_size; ++i)
std::cout << v[i + 1].value << " ";
std::cout << "\n";
}
struct receiver {
public:
receiver(client_type& db)
: adapter_{adapt(resp_)}
, db_{&db} {}
void on_resp3(command cmd, node<boost::string_view> const& nd, boost::system::error_code& ec)
{
adapter_(nd, ec);
}
void on_read(command cmd)
{
switch (cmd) {
case command::hello:
{
std::map<std::string, std::string> map
{ {"key1", "value1"}
, {"key2", "value2"}
, {"key3", "value3"}
};
std::vector<int> vec
{1, 2, 3, 4, 5, 6};
std::set<std::string> set
{"one", "two", "three", "four"};
// Sends the stl containers.
db_->send_range(command::hset, "hset-key", map);
db_->send_range(command::rpush, "rpush-key", vec);
db_->send_range(command::sadd, "sadd-key", set);
// Retrieves the containers.
db_->send(command::hgetall, "hset-key");
db_->send(command::lrange, "rpush-key", 0, -1);
db_->send(command::smembers, "sadd-key");
db_->send(command::quit);
} break;
case command::lrange:
case command::smembers:
case command::hgetall:
print_aggregate(resp_);
break;
default:;
}
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()};
receiver recv{db};
db.async_run(
recv,
{net::ip::make_address("127.0.0.1"), 6379},
[](auto ec){ std::cout << ec.message() << std::endl;});
ioc.run();
}

View File

@@ -0,0 +1,125 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include <vector>
#include <iostream>
#include <boost/asio/signal_set.hpp>
#include <aedis/aedis.hpp>
#include <aedis/src.hpp>
#include "user_session.hpp"
namespace net = boost::asio;
using aedis::resp3::node;
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<net::ip::tcp::socket, command>;
using response_type = std::vector<node<std::string>>;
using adapter_type = aedis::adapter::adapter_t<response_type>;
class receiver {
public:
receiver(std::shared_ptr<client_type> db)
: adapter_{adapt(resp_)}
, db_{db}
{}
void on_resp3(command cmd, node<boost::string_view> const& nd, boost::system::error_code& ec)
{
adapter_(nd, ec);
}
void on_read(command cmd)
{
switch (cmd) {
case command::hello:
db_->send(command::subscribe, "channel");
break;
case command::incr:
std::cout << "Messages so far: " << resp_.front().value << std::endl;
break;
default:;
}
resp_.clear();
}
void on_write(std::size_t n)
{
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<user_session_base> session)
{ sessions_.push_back(session); }
private:
response_type resp_;
adapter_type adapter_;
std::shared_ptr<client_type> db_;
std::vector<std::shared_ptr<user_session_base>> sessions_;
};
net::awaitable<void>
listener(
std::shared_ptr<net::ip::tcp::acceptor> acc,
std::shared_ptr<client_type> db,
std::shared_ptr<receiver> recv)
{
auto on_user_msg = [db](std::string const& msg)
{
db->send(command::publish, "channel", msg);
db->send(command::incr, "message-counter");
};
for (;;) {
auto socket = co_await acc->async_accept(net::use_awaitable);
auto session = std::make_shared<user_session>(std::move(socket));
session->start(on_user_msg);
recv->add(session);
}
}
int main()
{
try {
net::io_context ioc{1};
auto db = std::make_shared<client_type>(ioc.get_executor());
auto recv = std::make_shared<receiver>(db);
db->async_run(
*recv,
{net::ip::make_address("127.0.0.1"), 6379},
[](auto ec){ std::cout << ec.message() << std::endl;});
auto endpoint = net::ip::tcp::endpoint{net::ip::tcp::v4(), 55555};
auto acc = std::make_shared<net::ip::tcp::acceptor>(ioc.get_executor(), endpoint);
co_spawn(ioc, listener(acc, db, recv), net::detached);
net::signal_set signals(ioc.get_executor(), SIGINT, SIGTERM);
signals.async_wait([&] (auto, int) { ioc.stop(); });
ioc.run();
} catch (std::exception const& e) {
std::cerr << e.what() << std::endl;
}
}

View File

@@ -0,0 +1,121 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include <iostream>
#include <queue>
#include <vector>
#include <string>
#include <boost/asio/signal_set.hpp>
#include <aedis/aedis.hpp>
#include <aedis/src.hpp>
#include "user_session.hpp"
namespace net = boost::asio;
using aedis::resp3::node;
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<net::ip::tcp::socket, command>;
using response_type = std::vector<node<std::string>>;
using adapter_type = aedis::adapter::adapter_t<response_type>;
class myreceiver {
public:
myreceiver()
: adapter_{adapt(resp_)}
{}
void on_resp3(command cmd, node<boost::string_view> const& nd, boost::system::error_code& ec)
{
adapter_(nd, ec);
}
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) {
case command::ping:
sessions_.front()->deliver(resp_.front().value);
sessions_.pop();
break;
case command::incr:
std::cout << "Echos so far: " << resp_.front().value << std::endl;
break;
default: /* Ignore */;
}
resp_.clear();
}
void add_user_session(std::shared_ptr<user_session_base> session)
{ sessions_.push(session); }
private:
response_type resp_;
adapter_type adapter_;
std::queue<std::shared_ptr<user_session_base>> sessions_;
};
net::awaitable<void>
listener(
std::shared_ptr<net::ip::tcp::acceptor> acc,
std::shared_ptr<client_type> db,
std::shared_ptr<myreceiver> recv)
{
for (;;) {
auto socket = co_await acc->async_accept(net::use_awaitable);
auto session = std::make_shared<user_session>(std::move(socket));
auto on_user_msg = [db, recv, session](std::string const& msg)
{
db->send(command::ping, msg);
db->send(command::incr, "echo-counter");
recv->add_user_session(session);
};
session->start(on_user_msg);
}
}
int main()
{
try {
net::io_context ioc;
auto db = std::make_shared<client_type>(ioc.get_executor());
auto recv = std::make_shared<myreceiver>();
db->async_run(
*recv,
{net::ip::make_address("127.0.0.1"), 6379},
[](auto ec){ std::cout << ec.message() << std::endl;});
auto endpoint = net::ip::tcp::endpoint{net::ip::tcp::v4(), 55555};
auto acc = std::make_shared<net::ip::tcp::acceptor>(ioc.get_executor(), endpoint);
co_spawn(ioc, listener(acc, db, recv), net::detached);
net::signal_set signals(ioc.get_executor(), SIGINT, SIGTERM);
signals.async_wait([&] (auto, int) { ioc.stop(); });
ioc.run();
} catch (std::exception const& e) {
std::cerr << e.what() << std::endl;
}
}

View File

@@ -0,0 +1,76 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include <string>
#include <iostream>
#include <aedis/aedis.hpp>
#include <aedis/src.hpp>
namespace net = boost::asio;
using aedis::resp3::node;
using aedis::redis::command;
using aedis::generic::client;
using aedis::adapter::adapt;
using client_type = client<net::ip::tcp::socket, command>;
using response_type = node<std::string>;
using adapter_type = aedis::adapter::adapter_t<response_type>;
struct myreceiver {
public:
myreceiver(client_type& db)
: adapter_{adapt(resp_)}
, db_{&db} {}
void on_resp3(command cmd, node<boost::string_view> const& nd, boost::system::error_code& ec)
{
adapter_(nd, ec);
}
void on_read(command cmd)
{
switch (cmd) {
case command::hello:
db_->send(command::ping, "O rato roeu a roupa do rei de Roma");
db_->send(command::incr, "intro-counter");
db_->send(command::set, "intro-key", "Três pratos de trigo para três tigres");
db_->send(command::get, "intro-key");
db_->send(command::quit);
break;
default:
std::cout << resp_.value << std::endl;
}
}
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};
db.async_run(
recv,
{net::ip::make_address("127.0.0.1"), 6379},
[](auto ec){ std::cout << ec.message() << std::endl;});
ioc.run();
}

View File

@@ -0,0 +1,183 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include <string>
#include <iostream>
#include <aedis/aedis.hpp>
#include <aedis/src.hpp>
// Arbitrary struct to de/serialize.
struct mystruct {
int a;
int b;
};
namespace net = boost::asio;
namespace adapter = aedis::adapter;
using aedis::resp3::node;
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<net::ip::tcp::socket, command>;
using responses_tuple_type =
std::tuple<
boost::optional<mystruct>,
std::list<mystruct>,
std::set<mystruct>,
std::map<std::string, mystruct>
>;
using adapters_tuple_type = adapters_tuple_t<responses_tuple_type>;
std::ostream& operator<<(std::ostream& os, mystruct const& obj)
{
os << "a: " << obj.a << ", b: " << obj.b;
return os;
}
bool operator<(mystruct const& a, mystruct const& b)
{
return std::tie(a.a, a.b) < std::tie(b.a, b.b);
}
// Dumy serialization.
void to_bulk(std::string& to, mystruct const& obj)
{
aedis::resp3::to_bulk(to, "Dummy serializaiton string.");
}
// Dummy deserialization.
void from_string(mystruct& obj, boost::string_view sv, boost::system::error_code& ec)
{
obj.a = 1;
obj.b = 2;
}
class receiver {
public:
receiver(client_type& db)
: adapters_(make_adapters_tuple(resps_))
, db_{&db} {}
void
on_resp3(
command cmd,
node<boost::string_view> const& nd,
boost::system::error_code& ec)
{
switch (cmd) {
case command::get: adapter::get<boost::optional<mystruct>>(adapters_)(nd, ec); break;
case command::lrange: adapter::get<std::list<mystruct>>(adapters_)(nd, ec); break;
case command::smembers: adapter::get<std::set<mystruct>>(adapters_)(nd, ec); break;
case command::hgetall: adapter::get<std::map<std::string, mystruct>>(adapters_)(nd, ec); break;
default:; // Ignore
}
}
void on_read(command cmd)
{
std::cout << cmd << "\n";
switch (cmd) {
case command::hello:
{
mystruct var{1, 2};
std::map<std::string, mystruct> map
{ {"key1", {1, 2}}
, {"key2", {3, 4}}
, {"key3", {5, 6}}};
std::vector<mystruct> vec
{{1, 2}, {3, 4}, {5, 6}};
std::set<std::string> set
{{1, 2}, {3, 4}, {5, 6}};
// Sends
db_->send(command::set, "serialization-var-key", var, "EX", "2");
db_->send_range(command::hset, "serialization-hset-key", map);
db_->send_range(command::rpush, "serialization-rpush-key", vec);
db_->send_range(command::sadd, "serialization-sadd-key", set);
// Retrieves
db_->send(command::get, "serialization-var-key");
db_->send(command::hgetall, "serialization-hset-key");
db_->send(command::lrange, "serialization-rpush-key", 0, -1);
db_->send(command::smembers, "serialization-sadd-key");
} break;
case command::get:
{
if (std::get<boost::optional<mystruct>>(resps_).has_value()) {
std::cout << std::get<boost::optional<mystruct>>(resps_).value() << "\n\n";
std::get<boost::optional<mystruct>>(resps_).reset();
} else {
std::cout << "Expired." << "\n";
}
} break;
case command::lrange:
for (auto const& e: std::get<std::list<mystruct>>(resps_))
std::cout << e << "\n";
std::cout << "\n";
std::get<std::list<mystruct>>(resps_).clear();
break;
case command::smembers:
for (auto const& e: std::get<std::set<mystruct>>(resps_))
std::cout << e << "\n";
std::cout << "\n";
std::get<std::set<mystruct>>(resps_).clear();
break;
case command::hgetall:
for (auto const& e: std::get<std::map<std::string, mystruct>>(resps_))
std::cout << e.first << ", " << e.second << std::endl;
std::cout << "\n";
std::get<std::map<std::string, mystruct>>(resps_).clear();
break;
default:;
}
}
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());
receiver recv{db};
db.async_run(
recv,
{net::ip::make_address("127.0.0.1"), 6379},
[](auto ec){ std::cout << ec.message() << std::endl;});
net::steady_timer tm{ioc, std::chrono::seconds{3}};
tm.async_wait([&db](auto ec){
db.send(command::get, "serialization-var-key");
db.send(command::quit);
});
ioc.run();
}

View File

@@ -0,0 +1,127 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include <map>
#include <vector>
#include <iostream>
#include <aedis/aedis.hpp>
#include <aedis/src.hpp>
namespace net = boost::asio;
namespace adapter = aedis::adapter;
using aedis::resp3::node;
using aedis::redis::command;
using aedis::generic::client;
using aedis::adapter::adapt;
using aedis::adapter::adapters_tuple_t;
using aedis::adapter::make_adapters_tuple;
using client_type = client<net::ip::tcp::socket, command>;
using responses_tuple_type =
std::tuple<
std::list<int>,
boost::optional<std::set<std::string>>,
std::vector<node<std::string>>
>;
using adapters_tuple_type = adapters_tuple_t<responses_tuple_type>;
template <class Container>
void print_and_clear(Container& cont)
{
std::cout << "\n";
for (auto const& e: cont) std::cout << e << " ";
std::cout << "\n";
cont.clear();
}
class myreceiver {
public:
myreceiver(client_type& db)
: adapters_(make_adapters_tuple(resps_))
, db_{&db} {}
void
on_resp3(
command cmd,
node<boost::string_view> const& nd,
boost::system::error_code& ec)
{
switch (cmd) {
case command::lrange: adapter::get<std::list<int>>(adapters_)(nd, ec); break;
case command::smembers: adapter::get<boost::optional<std::set<std::string>>>(adapters_)(nd, ec); break;
default:;
}
}
void on_read(command cmd)
{
switch (cmd) {
case command::hello:
{
std::map<std::string, std::string> map
{ {"key1", "value1"}
, {"key2", "value2"}
, {"key3", "value3"}
};
std::vector<int> vec
{1, 2, 3, 4, 5, 6};
std::set<std::string> set
{"one", "two", "three", "four"};
// Sends the stl containers.
db_->send_range(command::hset, "hset-key", map);
db_->send_range(command::rpush, "rpush-key", vec);
db_->send_range(command::sadd, "sadd-key", set);
//_ Retrieves the containers.
db_->send(command::hgetall, "hset-key");
db_->send(command::lrange, "rpush-key", 0, -1);
db_->send(command::smembers, "sadd-key");
db_->send(command::quit);
} break;
case command::lrange:
print_and_clear(std::get<std::list<int>>(resps_));
break;
case command::smembers:
print_and_clear(std::get<boost::optional<std::set<std::string>>>(resps_).value());
break;
default:;
}
}
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};
db.async_run(
recv,
{net::ip::make_address("127.0.0.1"), 6379},
[](auto ec){ std::cout << ec.message() << std::endl;});
ioc.run();
}

View File

@@ -0,0 +1,95 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres at gmail dot com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include <iostream>
#include <aedis/aedis.hpp>
#include <aedis/src.hpp>
namespace net = boost::asio;
using aedis::resp3::node;
using aedis::sentinel::command;
using aedis::generic::client;
using aedis::adapter::adapt;
using client_type = client<net::ip::tcp::socket, command>;
using response_type = std::vector<node<std::string>>;
using adapter_type = aedis::adapter::adapter_t<response_type>;
/* In this example we send a subscription to a channel and start
* reading server side messages indefinitely.
*
* After starting the example you can test it by sending messages with
* redis-cli like this
*
* $ redis-cli -3
* 127.0.0.1:6379> PUBLISH channel1 some-message
* (integer) 3
* 127.0.0.1:6379>
*
* The messages will then appear on the terminal you are running the
* example.
*/
class myreceiver {
public:
myreceiver(client_type& db)
: adapter_{adapt(resp_)}
, db_{&db} {}
void on_resp3(command cmd, node<boost::string_view> const& nd, boost::system::error_code& ec)
{
adapter_(nd, ec);
}
void on_read(command cmd)
{
switch (cmd) {
case command::hello:
db_->send(command::subscribe, "channel1", "channel2");
break;
default:;
}
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_;
client_type* db_;
};
int main()
{
net::io_context ioc;
client_type db{ioc.get_executor()};
myreceiver recv{db};
db.async_run(
recv,
{net::ip::make_address("127.0.0.1"), 6379},
[](auto ec){ std::cout << ec.message() << std::endl;});
ioc.run();
}

View File

@@ -9,7 +9,10 @@
#include <functional>
#include <aedis/config.hpp>
#include <boost/asio/awaitable.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/redirect_error.hpp>
// An example user session.
@@ -26,7 +29,7 @@ class user_session:
public user_session_base,
public std::enable_shared_from_this<user_session> {
public:
user_session(net::ip::tcp::socket socket)
user_session(boost::asio::ip::tcp::socket socket)
: socket_(std::move(socket))
, timer_(socket_.get_executor())
{ timer_.expires_at(std::chrono::steady_clock::time_point::max()); }
@@ -35,11 +38,11 @@ public:
{
co_spawn(socket_.get_executor(),
[self = shared_from_this(), on_msg]{ return self->reader(on_msg); },
net::detached);
boost::asio::detached);
co_spawn(socket_.get_executor(),
[self = shared_from_this()]{ return self->writer(); },
net::detached);
boost::asio::detached);
}
void deliver(std::string const& msg)
@@ -49,12 +52,12 @@ public:
}
private:
net::awaitable<void>
boost::asio::awaitable<void>
reader(std::function<void(std::string const&)> on_msg)
{
try {
for (std::string msg;;) {
auto const n = co_await net::async_read_until(socket_, net::dynamic_buffer(msg, 1024), "\n", net::use_awaitable);
auto const n = co_await boost::asio::async_read_until(socket_, boost::asio::dynamic_buffer(msg, 1024), "\n", boost::asio::use_awaitable);
on_msg(msg);
msg.erase(0, n);
}
@@ -63,15 +66,15 @@ private:
}
}
net::awaitable<void> writer()
boost::asio::awaitable<void> writer()
{
try {
while (socket_.is_open()) {
if (write_msgs_.empty()) {
boost::system::error_code ec;
co_await timer_.async_wait(redirect_error(net::use_awaitable, ec));
co_await timer_.async_wait(boost::asio::redirect_error(boost::asio::use_awaitable, ec));
} else {
co_await net::async_write(socket_, net::buffer(write_msgs_.front()), net::use_awaitable);
co_await boost::asio::async_write(socket_, boost::asio::buffer(write_msgs_.front()), boost::asio::use_awaitable);
write_msgs_.pop_front();
}
}
@@ -86,8 +89,8 @@ private:
timer_.cancel();
}
net::ip::tcp::socket socket_;
net::steady_timer timer_;
boost::asio::ip::tcp::socket socket_;
boost::asio::steady_timer timer_;
std::deque<std::string> write_msgs_;
};

View File

@@ -1,68 +0,0 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include <iostream>
#include <aedis/aedis.hpp>
#include <aedis/src.hpp>
#include "lib/net_utils.hpp"
namespace resp3 = aedis::resp3;
using aedis::redis::command;
using aedis::redis::make_serializer;
using resp3::adapt;
namespace net = aedis::net;
using net::async_write;
using net::buffer;
using net::dynamic_buffer;
net::awaitable<void> ping()
{
try {
auto socket = co_await connect(); // See lib/net_utils.hpp
// Creates and sends the request.
std::string request;
auto sr = make_serializer(request);
sr.push(command::hello, 3);
sr.push(command::flushall);
sr.push(command::ping);
sr.push(command::incr, "key");
sr.push(command::quit);
co_await async_write(socket, buffer(request));
// Responses we are interested in.
int incr;
std::string ping;
// Reads the responses to ping and incr, ignore the others.
std::string buffer;
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // hello
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // flushall
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(ping));
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(incr));
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // quit
// Print the responses.
std::cout
<< "ping: " << ping << "\n"
<< "incr: " << incr << "\n";
} catch (std::exception const& e) {
std::cerr << e.what() << std::endl;
exit(EXIT_FAILURE);
}
}
int main()
{
net::io_context ioc;
co_spawn(ioc, ping(), net::detached);
ioc.run();
}

View File

@@ -1,93 +0,0 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include <iostream>
#include <chrono>
#include <optional>
#include <aedis/src.hpp>
#include <aedis/aedis.hpp>
#include "lib/net_utils.hpp"
namespace resp3 = aedis::resp3;
using aedis::redis::command;
using aedis::redis::make_serializer;
using resp3::adapt;
using resp3::node;
namespace net = aedis::net;
using net::async_write;
using net::buffer;
using net::dynamic_buffer;
net::awaitable<void> key_expiration()
{
try {
auto socket = co_await connect();
// Creates and sends the first request.
std::string request;
auto sr = make_serializer(request);
sr.push(command::hello, 3);
sr.push(command::flushall);
sr.push(command::set, "key", "Some payload", "EX", "2");
sr.push(command::get, "key");
co_await async_write(socket, buffer(request));
// Will hold the response to get.
std::optional<std::string> get;
// Reads the responses.
std::string rbuffer;
co_await resp3::async_read(socket, dynamic_buffer(rbuffer)); // hello
co_await resp3::async_read(socket, dynamic_buffer(rbuffer)); // flushall
co_await resp3::async_read(socket, dynamic_buffer(rbuffer)); // set
co_await resp3::async_read(socket, dynamic_buffer(rbuffer), adapt(get));
std::cout
<< "Before expiration: " << get.has_value() << ", "
<< *get << std::endl;
// Waits some seconds for the key to expire.
timer tm{socket.get_executor(), std::chrono::seconds{3}};
co_await tm.async_wait();
// Creates a request to get after expiration.
get.reset(); request.clear();
sr.push(command::get, "key");
sr.push(command::get, "key");
sr.push(command::quit);
co_await async_write(socket, buffer(request));
// Reads the response to the second request.
co_await resp3::async_read(socket, dynamic_buffer(rbuffer), adapt(get));
// Reading without an optional will result in an error.
std::string str;
boost::system::error_code ec;
co_await resp3::async_read(socket, dynamic_buffer(rbuffer),
adapt(str), net::redirect_error(net::use_awaitable, ec));
// Quit
co_await resp3::async_read(socket, dynamic_buffer(rbuffer));
std::cout << "After expiration (optional): " << get.has_value() << "\n";
std::cout << "After expiration (non-optional): " << ec.message() << "\n";
} catch (std::exception const& e) {
std::cerr << e.what() << std::endl;
exit(EXIT_FAILURE);
}
}
int main()
{
net::io_context ioc;
co_spawn(ioc, key_expiration(), net::detached);
ioc.run();
}

View File

@@ -1,51 +0,0 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include <aedis/config.hpp>
using tcp_socket = aedis::net::use_awaitable_t<>::as_default_on_t<aedis::net::ip::tcp::socket>;
using tcp_resolver = aedis::net::use_awaitable_t<>::as_default_on_t<aedis::net::ip::tcp::resolver>;
using timer = aedis::net::use_awaitable_t<>::as_default_on_t<aedis::net::steady_timer>;
aedis::net::awaitable<tcp_socket>
connect(
std::string host = "127.0.0.1",
std::string port = "6379")
{
auto ex = co_await aedis::net::this_coro::executor;
tcp_resolver resolver{ex};
auto const res = co_await resolver.async_resolve(host, port);
tcp_socket socket{ex};
co_await aedis::net::async_connect(socket, res);
co_return std::move(socket);
}
//net::awaitable<void>
//client::connection_manager()
//{
// using namespace aedis::net::experimental::awaitable_operators;
// using tcp_resolver = aedis::net::use_awaitable_t<>::as_default_on_t<aedis::net::ip::tcp::resolver>;
//
// for (;;) {
// tcp_resolver resolver{socket_.get_executor()};
// auto const res = co_await resolver.async_resolve("127.0.0.1", "6379");
// co_await net::async_connect(socket_, res);
//
// co_await say_hello();
//
// timer_.expires_at(std::chrono::steady_clock::time_point::max());
// co_await (reader() && writer());
//
// socket_.close();
// timer_.cancel();
//
// timer_.expires_after(std::chrono::seconds{1});
// boost::system::error_code ec;
// co_await timer_.async_wait(net::redirect_error(net::use_awaitable, ec));
// }
//}

View File

@@ -1,89 +0,0 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include <iostream>
#include <string>
#include <vector>
#include <list>
#include <deque>
#include <aedis/aedis.hpp>
#include <aedis/src.hpp>
#include "lib/net_utils.hpp"
namespace resp3 = aedis::resp3;
using aedis::redis::command;
using aedis::redis::make_serializer;
using resp3::adapt;
namespace net = aedis::net;
using net::async_write;
using net::buffer;
using net::dynamic_buffer;
net::awaitable<void> ping()
{
try {
auto socket = co_await connect();
// Creates and sends the request.
auto vec = {1, 2, 3, 4, 5, 6};
std::string request;
auto sr = make_serializer(request);
sr.push(command::hello, 3);
sr.push(command::flushall);
sr.push_range(command::rpush, "key", std::cbegin(vec), std::cend(vec));
sr.push(command::lrange, "key", 0, -1);
sr.push(command::lrange, "key", 0, -1);
sr.push(command::lrange, "key", 0, -1);
sr.push(command::lrange, "key", 0, -1);
sr.push(command::quit);
co_await async_write(socket, buffer(request));
// Expected responses.
int rpush;
std::vector<std::string> svec;
std::list<std::string> slist;
std::deque<std::string> sdeq;
std::vector<int> ivec;
// Reads the responses.
std::string rbuffer;
co_await resp3::async_read(socket, dynamic_buffer(rbuffer)); // hello
co_await resp3::async_read(socket, dynamic_buffer(rbuffer)); // flushall
co_await resp3::async_read(socket, dynamic_buffer(rbuffer), adapt(rpush)); // rpush
co_await resp3::async_read(socket, dynamic_buffer(rbuffer), adapt(svec));
co_await resp3::async_read(socket, dynamic_buffer(rbuffer), adapt(slist));
co_await resp3::async_read(socket, dynamic_buffer(rbuffer), adapt(sdeq));
co_await resp3::async_read(socket, dynamic_buffer(rbuffer), adapt(ivec));
co_await resp3::async_read(socket, dynamic_buffer(rbuffer)); // quit
// Prints the responses.
std::cout << "rpush: " << rpush;
std::cout << "\nlrange (as vector): ";
for (auto e: svec) std::cout << e << " ";
std::cout << "\nlrange (as list): ";
for (auto e: slist) std::cout << e << " ";
std::cout << "\nlrange (as deque): ";
for (auto e: sdeq) std::cout << e << " ";
std::cout << "\nlrange (as vector<int>): ";
for (auto e: ivec) std::cout << e << " ";
std::cout << std::endl;
} catch (std::exception const& e) {
std::cerr << e.what() << std::endl;
exit(EXIT_FAILURE);
}
}
int main()
{
net::io_context ioc;
co_spawn(ioc, ping(), net::detached);
ioc.run();
}

View File

@@ -0,0 +1,65 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres.gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include <iostream>
#include <boost/asio/use_awaitable.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/co_spawn.hpp>
#include <aedis/aedis.hpp>
#include <aedis/src.hpp>
namespace net = boost::asio;
namespace resp3 = aedis::resp3;
using aedis::redis::command;
using aedis::generic::make_serializer;
using net::ip::tcp;
using net::write;
using net::buffer;
using net::dynamic_buffer;
using tcp_socket = net::use_awaitable_t<>::as_default_on_t<net::ip::tcp::socket>;
net::awaitable<void> example()
{
auto ex = co_await net::this_coro::executor;
tcp::resolver resv{ex};
auto const res = resv.resolve("127.0.0.1", "6379");
tcp_socket socket{ex};
co_await socket.async_connect(*std::begin(res));
std::string request, buffer;
auto sr = make_serializer(request);
sr.push(command::hello, 3);
sr.push(command::ping, "Some message.");
sr.push(command::quit);
co_await net::async_write(socket, net::buffer(request));
auto adapter = [](resp3::node<boost::string_view> const& nd, boost::system::error_code&)
{
std::cout << nd << std::endl;
};
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // hello
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapter);
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // quit
}
int main()
{
try {
net::io_context ioc;
net::co_spawn(ioc, example(), net::detached);
ioc.run();
} catch (std::exception const& e) {
std::cerr << e.what() << std::endl;
}
}

View File

@@ -0,0 +1,78 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres.gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include <iostream>
#include <boost/asio/use_awaitable.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/co_spawn.hpp>
#include <aedis/aedis.hpp>
#include <aedis/src.hpp>
namespace net = boost::asio;
namespace resp3 = aedis::resp3;
using aedis::redis::command;
using aedis::adapter::adapt;
using aedis::generic::make_serializer;
using net::ip::tcp;
using net::write;
using net::buffer;
using net::dynamic_buffer;
using tcp_socket = net::use_awaitable_t<>::as_default_on_t<net::ip::tcp::socket>;
using response_type = std::tuple<std::string, boost::optional<std::string>>;
net::awaitable<void> example()
{
auto ex = co_await net::this_coro::executor;
tcp::resolver resv{ex};
auto const res = resv.resolve("127.0.0.1", "6379");
tcp_socket socket{ex};
co_await socket.async_connect(*std::begin(res));
std::string request;
auto sr = make_serializer(request);
sr.push(command::hello, 3);
sr.push(command::multi);
sr.push(command::ping, "Some message.");
sr.push(command::set, "low-level-key", "some content", "EX", "2");
sr.push(command::exec);
sr.push(command::quit);
co_await net::async_write(socket, buffer(request));
std::tuple<std::string, boost::optional<std::string>> response;
std::string buffer;
co_await resp3::async_read(socket, dynamic_buffer(buffer));
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // multi
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // ping
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // set
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(response));
co_await resp3::async_read(socket, dynamic_buffer(buffer));
std::cout
<< "Ping: " << std::get<0>(response) << "\n"
<< "Get (has_value): " << std::get<1>(response).has_value()
<< std::endl;
if (std::get<1>(response).has_value())
std::cout << "Get (value): " << std::get<1>(response).value() << std::endl;
}
int main()
{
try {
net::io_context ioc;
net::co_spawn(ioc, example(), net::detached);
ioc.run();
} catch (std::exception const& e) {
std::cerr << e.what() << std::endl;
}
}

View File

@@ -0,0 +1,68 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres.gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include <iostream>
#include <boost/asio/use_awaitable.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/co_spawn.hpp>
#include <aedis/aedis.hpp>
#include <aedis/src.hpp>
namespace net = boost::asio;
namespace resp3 = aedis::resp3;
using aedis::resp3::node;
using aedis::redis::command;
using aedis::adapter::adapt;
using aedis::generic::make_serializer;
using net::ip::tcp;
using net::write;
using net::buffer;
using net::dynamic_buffer;
using tcp_socket = net::use_awaitable_t<>::as_default_on_t<net::ip::tcp::socket>;
using response_type = std::tuple<std::string, boost::optional<std::string>>;
net::awaitable<void> example()
{
auto ex = co_await net::this_coro::executor;
tcp::resolver resv{ex};
auto const res = resv.resolve("127.0.0.1", "6379");
tcp_socket socket{ex};
co_await socket.async_connect(*std::begin(res));
std::string request;
auto sr = make_serializer(request);
sr.push(command::hello, 3);
sr.push(command::subscribe, "channel1", "channel2");
co_await net::async_write(socket, buffer(request));
// Ignores the response to hello.
std::string buffer;
co_await resp3::async_read(socket, dynamic_buffer(buffer));
for (std::vector<node<std::string>> resp;;) {
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(resp));
for (auto const& e: resp)
std::cout << e << std::endl;
resp.clear();
}
}
int main()
{
try {
net::io_context ioc;
net::co_spawn(ioc, example(), net::detached);
ioc.run();
} catch (std::exception const& e) {
std::cerr << e.what() << std::endl;
}
}

View File

@@ -8,18 +8,29 @@
#include <iostream>
#include <string>
#include <boost/asio/connect.hpp>
#include <aedis/aedis.hpp>
#include <aedis/src.hpp>
namespace net = boost::asio;
namespace resp3 = aedis::resp3;
using aedis::redis::command;
using aedis::redis::make_serializer;
using aedis::resp3::adapt;
namespace net = aedis::net;
using aedis::redis::command;
using aedis::generic::make_serializer;
using aedis::adapter::adapt;
using net::dynamic_buffer;
using net::ip::tcp;
using hello_type = std::tuple<
std::string, std::string,
std::string, std::string,
std::string, int,
std::string, int,
std::string, std::string,
std::string, std::string,
std::string, std::vector<std::string>>;
int main()
{
try {
@@ -27,9 +38,9 @@ int main()
tcp::resolver resv{ioc};
auto const res = resv.resolve("127.0.0.1", "6379");
tcp::socket socket{ioc};
connect(socket, res);
net::connect(socket, res);
// Creates and sends the request to redis.
// Creates and sends a request to redis.
std::string request;
auto sr = make_serializer(request);
sr.push(command::hello, 3);
@@ -37,15 +48,17 @@ int main()
sr.push(command::quit);
net::write(socket, net::buffer(request));
// Will store the response to ping.
// Responses
std::string resp;
hello_type hello;
// Reads the responses to all commands in the request.
std::string buffer;
resp3::read(socket, dynamic_buffer(buffer)); // hello (ignored)
resp3::read(socket, dynamic_buffer(buffer), adapt(hello)); // hello
resp3::read(socket, dynamic_buffer(buffer), adapt(resp));
resp3::read(socket, dynamic_buffer(buffer)); // quit (ignored)
std::cout << std::get<0>(hello) << ": " << std::get<1>(hello) << std::endl;
std::cout << "Ping: " << resp << std::endl;
} catch (std::exception const& e) {

View File

@@ -1,82 +0,0 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres at gmail dot com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include <iostream>
#include <string_view>
#include <aedis/aedis.hpp>
#include <aedis/src.hpp>
#include "lib/net_utils.hpp"
namespace resp3 = aedis::resp3;
using aedis::redis::command;
using resp3::type;
using aedis::redis::make_serializer;
using resp3::adapt;
namespace net = aedis::net;
using net::async_write;
using net::buffer;
using net::dynamic_buffer;
// An adapter that prints the data it receives in the screen.
struct myadapter {
void operator()(
type t,
std::size_t aggregate_size,
std::size_t depth,
char const* data,
std::size_t size,
std::error_code&)
{
std::cout
<< "Type: " << t << "\n"
<< "Aggregate_size: " << aggregate_size << "\n"
<< "Depth: " << depth << "\n"
<< "Data: " << std::string_view(data, size) << "\n"
<< "----------------------" << "\n";
}
};
net::awaitable<void> adapter_example()
{
try {
auto socket = co_await connect();
auto list = {"one", "two", "three"};
// Creates and sends the request.
std::string request;
auto sr = make_serializer(request);
sr.push(command::hello, 3);
sr.push(command::flushall);
sr.push_range(command::rpush, "key", std::cbegin(list), std::cend(list));
sr.push(command::lrange, "key", 0, -1);
sr.push(command::quit);
co_await async_write(socket, buffer(request));
// Reads the responses.
std::string rbuffer;
co_await resp3::async_read(socket, dynamic_buffer(rbuffer)); // hello
co_await resp3::async_read(socket, dynamic_buffer(rbuffer)); // flushall
co_await resp3::async_read(socket, dynamic_buffer(rbuffer)); // rpush
co_await resp3::async_read(socket, dynamic_buffer(rbuffer), myadapter{}); // lrange
co_await resp3::async_read(socket, dynamic_buffer(rbuffer)); // quit
} catch (std::exception const& e) {
std::cerr << e.what() << std::endl;
exit(EXIT_FAILURE);
}
}
int main()
{
net::io_context ioc;
co_spawn(ioc, adapter_example(), net::detached);
ioc.run();
}

View File

@@ -1,109 +0,0 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include <string>
#include <iostream>
#include <charconv>
#include <aedis/aedis.hpp>
#include <aedis/src.hpp>
#include "lib/net_utils.hpp"
namespace resp3 = aedis::resp3;
using aedis::redis::make_serializer;
using aedis::redis::command;
using resp3::adapt;
namespace net = aedis::net;
using net::async_write;
using net::buffer;
using net::dynamic_buffer;
// Define the to_string and from_string functions for your own data
// types.
// The struct we would like to store in redis using our own
// serialization.
struct mydata {
int a;
int b;
};
// Serializes to Tab Separated Value (TSV).
std::string to_string(mydata const& obj)
{
return std::to_string(obj.a) + '\t' + std::to_string(obj.b);
}
// Deserializes TSV.
void
from_string(
mydata& obj,
char const* data,
std::size_t size,
std::error_code& ec)
{
auto const* end = data + size;
auto const* pos = std::find(data, end, '\t');
assert(pos != end); // Or use your own error code.
auto const res1 = std::from_chars(data, pos, obj.a);
if (res1.ec != std::errc()) {
ec = std::make_error_code(res1.ec);
return;
}
auto const res2 = std::from_chars(pos + 1, end, obj.b);
if (res2.ec != std::errc()) {
ec = std::make_error_code(res2.ec);
return;
}
}
net::awaitable<void> serialization()
{
try {
auto socket = co_await connect();
mydata obj{21, 22};
std::string request;
auto sr = make_serializer(request);
sr.push(command::hello, 3);
sr.push(command::flushall);
sr.push(command::set, "key", obj);
sr.push(command::get, "key");
sr.push(command::quit);
co_await async_write(socket, buffer(request));
// The response.
mydata get;
// Reads the responses.
std::string buffer;
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // hello
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // flushall
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // set
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(get));
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // quit
// Print the responses.
std::cout << "get: a = " << get.a << ", b = " << get.b << "\n";
} catch (std::exception const& e) {
std::cerr << e.what() << std::endl;
exit(EXIT_FAILURE);
}
}
int main()
{
net::io_context ioc;
co_spawn(ioc, serialization(), net::detached);
ioc.run();
}

View File

@@ -1,85 +0,0 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres at gmail dot com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include <iostream>
#include <set>
#include <vector>
#include <unordered_map>
#include <aedis/aedis.hpp>
#include <aedis/src.hpp>
#include "lib/net_utils.hpp"
namespace resp3 = aedis::resp3;
using aedis::redis::command;
using aedis::redis::make_serializer;
using resp3::adapt;
namespace net = aedis::net;
using net::async_write;
using net::buffer;
using net::dynamic_buffer;
net::awaitable<void> containers()
{
try {
auto socket = co_await connect();
std::set<std::string> set
{"one", "two", "three", "four"};
// Creates and sends the request.
std::string request;
auto sr = make_serializer(request);
sr.push(command::hello, 3);
sr.push(command::flushall);
sr.push_range(command::sadd, "key", std::cbegin(set), std::cend(set));
sr.push(command::smembers, "key");
sr.push(command::smembers, "key");
sr.push(command::smembers, "key");
sr.push(command::quit);
co_await async_write(socket, buffer(request));
// Expected responses.
int sadd;
std::vector<std::string> smembers1;
std::set<std::string> smembers2;
std::unordered_set<std::string> smembers3;
// Reads the responses.
std::string buffer;
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // hello
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // flushall
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(sadd));
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(smembers1));
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(smembers2));
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(smembers3));
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // quit
// Prints the responses.
std::cout << "sadd: " << sadd;
std::cout << "\nsmembers (as vector): ";
for (auto const& e: smembers1) std::cout << e << " ";
std::cout << "\nsmembers (as set): ";
for (auto const& e: smembers2) std::cout << e << " ";
std::cout << "\nsmembers (as unordered_set): ";
for (auto const& e: smembers3) std::cout << e << " ";
std::cout << "\n";
} catch (std::exception const& e) {
std::cerr << e.what() << std::endl;
exit(EXIT_FAILURE);
}
}
int main()
{
net::io_context ioc;
co_spawn(ioc, containers(), net::detached);
ioc.run();
}

View File

@@ -1,86 +0,0 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres at gmail dot com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include <iostream>
#include <chrono>
#include <aedis/aedis.hpp>
#include <aedis/src.hpp>
#include "lib/net_utils.hpp"
namespace resp3 = aedis::resp3;
using aedis::redis::command;
using aedis::redis::make_serializer;
using resp3::adapt;
using resp3::node;
namespace net = aedis::net;
using net::async_write;
using net::buffer;
using net::dynamic_buffer;
/* In this example we send a subscription to a channel and start
reading server side messages indefinitely.
Notice we store the id of the connection (attributed by the redis
server) to be able to identify it (in the logs for example).
After starting the example you can test it by sending messages with
redis-cli like this
$ redis-cli -3
127.0.0.1:6379> PUBLISH channel1 some-message
(integer) 3
127.0.0.1:6379>
The messages will then appear on the terminal you are running the
example.
*/
net::awaitable<void> subscriber()
{
try {
auto socket = co_await connect();
std::string request;
auto sr = make_serializer(request);
sr.push(command::hello, "3");
sr.push(command::subscribe, "channel1", "channel2");
co_await async_write(socket, buffer(request));
std::vector<node> resp;
// Reads the response to the hello command.
std::string buffer;
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(resp));
co_await resp3::async_read(socket, dynamic_buffer(buffer));
// Saves the id of this connection.
auto const id = resp.at(8).data;
// Loops to receive server pushes.
for (;;) {
resp.clear();
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(resp));
std::cout
<< "Subscriber " << id << ":\n"
<< resp << std::endl;
}
} catch (std::exception const& e) {
std::cerr << e.what() << std::endl;
}
}
int main()
{
net::io_context ioc;
co_spawn(ioc, subscriber(), net::detached);
co_spawn(ioc, subscriber(), net::detached);
co_spawn(ioc, subscriber(), net::detached);
ioc.run();
}

View File

@@ -1,85 +0,0 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres at gmail dot com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include <iostream>
#include <vector>
#include <tuple>
#include <array>
#include <aedis/aedis.hpp>
#include <aedis/src.hpp>
#include "lib/net_utils.hpp"
namespace resp3 = aedis::resp3;
using aedis::redis::command;
using aedis::redis::make_serializer;
using resp3::adapt;
namespace net = aedis::net;
using net::async_write;
using net::buffer;
using net::dynamic_buffer;
net::awaitable<void> transaction()
{
try {
auto socket = co_await connect();
auto list = {"one", "two", "three"};
std::string request;
auto sr = make_serializer(request);
sr.push(command::hello, 3);
sr.push(command::flushall);
sr.push(command::multi); // Starts a transaction
sr.push(command::ping, "Some message");
sr.push(command::incr, "incr1-key");
sr.push_range(command::rpush, "list-key", std::cbegin(list), std::cend(list));
sr.push(command::lrange, "list-key", 0, -1);
sr.push(command::incr, "incr2-key");
sr.push(command::exec); // Ends the transaction.
sr.push(command::quit);
co_await async_write(socket, buffer(request));
// Expected responses.
std::tuple<std::string, int, int, std::vector<std::string>, int> execs;
// Reads the response.
std::string buffer;
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // hello
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // flushall
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // multi
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // ping
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // incr
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // rpush
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // lrange
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // incr
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(execs));
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // quit
// Prints the response to the transaction.
std::cout << "ping: " << std::get<0>(execs) << "\n";
std::cout << "incr1: " << std::get<1>(execs) << "\n";
std::cout << "rpush: " << std::get<2>(execs) << "\n";
std::cout << "lrange: ";
for (auto const& e: std::get<3>(execs)) std::cout << e << " ";
std::cout << "\n";
std::cout << "incr2: " << std::get<4>(execs) << "\n";
} catch (std::exception const& e) {
std::cerr << e.what() << std::endl;
exit(EXIT_FAILURE);
}
}
int main()
{
net::io_context ioc;
co_spawn(ioc, transaction(), net::detached);
ioc.run();
}

1005
m4/ax_cxx_compile_stdcxx.m4 Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -6,12 +6,38 @@
*/
#include <iostream>
#include <stdlib.h>
template <class T>
void check_equal(T const& a, T const& b, std::string const& msg = "")
void expect_eq(T const& a, T const& b, std::string const& msg = "")
{
if (a == b)
if (a == b) {
std::cout << "Success: " << msg << std::endl;
else
} else {
std::cout << "Error: " << msg << std::endl;
exit(EXIT_FAILURE);
}
}
void expect_error(boost::system::error_code a, boost::system::error_condition expected = {})
{
if (a == expected) {
if (a)
std::cout << "Success: " << a.message() << " (" << a.category().name() << ")" << std::endl;
} else {
std::cout << "Error: " << a.message() << " (" << a.category().name() << ")" << std::endl;
exit(EXIT_FAILURE);
}
}
template <class T>
void check_empty(T const& t)
{
if (t.empty()) {
//std::cout << "Success: " << std::endl;
} else {
std::cout << "Error: Not empty" << std::endl;
exit(EXIT_FAILURE);
}
}

714
tests/low_level.cpp Normal file
View File

@@ -0,0 +1,714 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include <map>
#include <iostream>
#include <optional>
#include <boost/system/errc.hpp>
#include <boost/asio/awaitable.hpp>
#include <boost/asio/redirect_error.hpp>
#include <boost/asio/use_awaitable.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/beast/_experimental/test/stream.hpp>
#include <aedis/aedis.hpp>
#include <aedis/src.hpp>
#include "check.hpp"
#include "config.h"
namespace net = boost::asio;
namespace resp3 = aedis::resp3;
using test_stream = boost::beast::test::stream;
using aedis::adapter::adapt;
using node_type = aedis::resp3::node<std::string>;
//-------------------------------------------------------------------
template <class Result>
struct expect {
std::string in;
Result expected;
std::string name;
boost::system::error_condition ec;
};
template <class Result>
void test_sync(net::any_io_executor ex, expect<Result> e)
{
std::string rbuffer;
test_stream ts {ex};
ts.append(e.in);
Result result;
boost::system::error_code ec;
resp3::read(ts, net::dynamic_buffer(rbuffer), adapt(result), ec);
expect_error(ec, e.ec);
if (e.ec)
return;
check_empty(rbuffer);
expect_eq(result, e.expected, e.name);
}
template <class Result>
class async_test: public std::enable_shared_from_this<async_test<Result>> {
private:
std::string rbuffer_;
test_stream ts_;
expect<Result> data_;
Result result_;
public:
async_test(net::any_io_executor ex, expect<Result> e)
: ts_{ex}
, data_{e}
{
ts_.append(e.in);
}
void run()
{
auto self = this->shared_from_this();
auto f = [self](auto ec, auto n)
{
expect_error(ec, self->data_.ec);
if (self->data_.ec)
return;
check_empty(self->rbuffer_);
expect_eq(self->result_, self->data_.expected, self->data_.name);
};
resp3::async_read(
ts_,
net::dynamic_buffer(rbuffer_),
adapt(result_),
f);
}
};
template <class Result>
void test_async(net::any_io_executor ex, expect<Result> e)
{
std::make_shared<async_test<Result>>(ex, e)->run();
}
void test_number(net::io_context& ioc)
{
boost::optional<int> ok;
ok = 11;
// Success
auto const in01 = expect<node_type>{":3\r\n", node_type{resp3::type::number, 1UL, 0UL, {"3"}}, "number.node (positive)"};
auto const in02 = expect<node_type>{":-3\r\n", node_type{resp3::type::number, 1UL, 0UL, {"-3"}}, "number.node (negative)"};
auto const in03 = expect<int>{":11\r\n", int{11}, "number.int"};
auto const in04 = expect<boost::optional<int>>{":11\r\n", ok, "number.optional.int"};
auto const in05 = expect<std::tuple<int>>{"*1\r\n:11\r\n", std::tuple<int>{11}, "number.tuple.int"};
auto const in06 = expect<boost::optional<int>>{"%11\r\n", boost::optional<int>{}, "number.optional.int", aedis::adapter::make_error_condition(aedis::adapter::error::expects_simple_type)};
auto const in07 = expect<std::set<std::string>>{":11\r\n", std::set<std::string>{}, "number.optional.int", aedis::adapter::make_error_condition(aedis::adapter::error::expects_set_aggregate)};
auto const in08 = expect<std::unordered_set<std::string>>{":11\r\n", std::unordered_set<std::string>{}, "number.optional.int", aedis::adapter::make_error_condition(aedis::adapter::error::expects_set_aggregate)};
auto const in09 = expect<std::map<std::string, std::string>>{":11\r\n", std::map<std::string, std::string>{}, "number.optional.int", aedis::adapter::make_error_condition(aedis::adapter::error::expects_map_like_aggregate)};
auto const in10 = expect<std::unordered_map<std::string, std::string>>{":11\r\n", std::unordered_map<std::string, std::string>{}, "number.optional.int", aedis::adapter::make_error_condition(aedis::adapter::error::expects_map_like_aggregate)};
auto const in11 = expect<std::list<std::string>>{":11\r\n", std::list<std::string>{}, "number.optional.int", aedis::adapter::make_error_condition(aedis::adapter::error::expects_aggregate)};
auto ex = ioc.get_executor();
test_sync(ex, in01);
test_sync(ex, in02);
test_sync(ex, in03);
test_sync(ex, in04);
test_sync(ex, in05);
test_sync(ex, in06);
test_sync(ex, in07);
test_sync(ex, in08);
test_sync(ex, in09);
test_sync(ex, in10);
test_sync(ex, in11);
test_async(ex, in01);
test_async(ex, in02);
test_async(ex, in03);
test_async(ex, in04);
test_async(ex, in05);
test_async(ex, in06);
test_async(ex, in07);
test_async(ex, in08);
test_async(ex, in09);
test_async(ex, in10);
test_async(ex, in11);
}
void test_bool(net::io_context& ioc)
{
boost::optional<bool> ok;
ok = true;
// Success.
auto const in08 = expect<node_type>{"#f\r\n", node_type{resp3::type::boolean, 1UL, 0UL, {"f"}}, "bool.node (false)"};
auto const in09 = expect<node_type>{"#t\r\n", node_type{resp3::type::boolean, 1UL, 0UL, {"t"}}, "bool.node (true)"};
auto const in10 = expect<bool>{"#t\r\n", bool{true}, "bool.bool (true)"};
auto const in11 = expect<bool>{"#f\r\n", bool{false}, "bool.bool (true)"};
auto const in13 = expect<boost::optional<bool>>{"#t\r\n", ok, "optional.int"};
// Error
auto const in01 = expect<boost::optional<bool>>{"#11\r\n", boost::optional<bool>{}, "bool.error", aedis::resp3::make_error_condition(aedis::resp3::error::unexpected_bool_value)};
auto const in03 = expect<std::set<int>>{"#t\r\n", std::set<int>{}, "bool.error", aedis::adapter::make_error_condition(aedis::adapter::error::expects_set_aggregate)};
auto const in04 = expect<std::unordered_set<int>>{"#t\r\n", std::unordered_set<int>{}, "bool.error", aedis::adapter::make_error_condition(aedis::adapter::error::expects_set_aggregate)};
auto const in05 = expect<std::map<int, int>>{"#t\r\n", std::map<int, int>{}, "bool.error", aedis::adapter::make_error_condition(aedis::adapter::error::expects_map_like_aggregate)};
auto const in06 = expect<std::unordered_map<int, int>>{"#t\r\n", std::unordered_map<int, int>{}, "bool.error", aedis::adapter::make_error_condition(aedis::adapter::error::expects_map_like_aggregate)};
auto ex = ioc.get_executor();
test_sync(ex, in01);
test_sync(ex, in03);
test_sync(ex, in04);
test_sync(ex, in05);
test_sync(ex, in06);
test_sync(ex, in08);
test_sync(ex, in09);
test_sync(ex, in10);
test_sync(ex, in11);
test_async(ex, in01);
test_async(ex, in03);
test_async(ex, in04);
test_async(ex, in05);
test_async(ex, in06);
test_async(ex, in08);
test_async(ex, in09);
test_async(ex, in10);
test_async(ex, in11);
}
void test_streamed_string(net::io_context& ioc)
{
std::string const wire = "$?\r\n;4\r\nHell\r\n;5\r\no wor\r\n;1\r\nd\r\n;0\r\n";
std::vector<node_type> e1a
{ {aedis::resp3::type::streamed_string_part, 1, 1, "Hell"}
, {aedis::resp3::type::streamed_string_part, 1, 1, "o wor"}
, {aedis::resp3::type::streamed_string_part, 1, 1, "d"}
, {aedis::resp3::type::streamed_string_part, 1, 1, ""}
};
std::vector<node_type> e1b { {resp3::type::streamed_string_part, 1UL, 1UL, {}} };
auto const in01 = expect<std::vector<node_type>>{wire, e1a, "streamed_string.node"};
auto const in03 = expect<std::string>{wire, std::string{"Hello word"}, "streamed_string.string"};
auto const in02 = expect<std::vector<node_type>>{"$?\r\n;0\r\n", e1b, "streamed_string.node.empty"};
auto ex = ioc.get_executor();
test_sync(ex, in01);
test_sync(ex, in02);
test_sync(ex, in03);
test_async(ex, in01);
test_async(ex, in02);
test_async(ex, in03);
}
void test_push(net::io_context& ioc)
{
std::string const wire = ">4\r\n+pubsub\r\n+message\r\n+some-channel\r\n+some message\r\n";
std::vector<node_type> e1a
{ {resp3::type::push, 4UL, 0UL, {}}
, {resp3::type::simple_string, 1UL, 1UL, "pubsub"}
, {resp3::type::simple_string, 1UL, 1UL, "message"}
, {resp3::type::simple_string, 1UL, 1UL, "some-channel"}
, {resp3::type::simple_string, 1UL, 1UL, "some message"}
};
std::vector<node_type> e1b { {resp3::type::push, 0UL, 0UL, {}} };
auto const in01 = expect<std::vector<node_type>>{wire, e1a, "push.node"};
auto const in02 = expect<std::vector<node_type>>{">0\r\n", e1b, "push.node.empty"};
auto ex = ioc.get_executor();
test_sync(ex, in01);
test_sync(ex, in02);
test_async(ex, in01);
test_async(ex, in02);
}
void test_map(net::io_context& ioc)
{
using map_type = std::map<std::string, std::string>;
using mmap_type = std::multimap<std::string, std::string>;
using umap_type = std::unordered_map<std::string, std::string>;
using mumap_type = std::unordered_multimap<std::string, std::string>;
using vec_type = std::vector<std::string>;
using op_map_type = boost::optional<std::map<std::string, std::string>>;
using op_vec_type = boost::optional<std::vector<std::string>>;
using tuple_type = std::tuple<std::string, std::string, std::string, std::string, std::string, std::string, std::string, std::string>;
std::string const wire = "%4\r\n$4\r\nkey1\r\n$6\r\nvalue1\r\n$4\r\nkey2\r\n$6\r\nvalue2\r\n$4\r\nkey3\r\n$6\r\nvalue3\r\n$4\r\nkey3\r\n$6\r\nvalue3\r\n";
std::vector<node_type> expected_1a
{ {resp3::type::map, 4UL, 0UL, {}}
, {resp3::type::blob_string, 1UL, 1UL, {"key1"}}
, {resp3::type::blob_string, 1UL, 1UL, {"value1"}}
, {resp3::type::blob_string, 1UL, 1UL, {"key2"}}
, {resp3::type::blob_string, 1UL, 1UL, {"value2"}}
, {resp3::type::blob_string, 1UL, 1UL, {"key3"}}
, {resp3::type::blob_string, 1UL, 1UL, {"value3"}}
, {resp3::type::blob_string, 1UL, 1UL, {"key3"}}
, {resp3::type::blob_string, 1UL, 1UL, {"value3"}}
};
map_type expected_1b
{ {"key1", "value1"}
, {"key2", "value2"}
, {"key3", "value3"}
};
umap_type e1g
{ {"key1", "value1"}
, {"key2", "value2"}
, {"key3", "value3"}
};
mmap_type e1k
{ {"key1", "value1"}
, {"key2", "value2"}
, {"key3", "value3"}
, {"key3", "value3"}
};
mumap_type e1l
{ {"key1", "value1"}
, {"key2", "value2"}
, {"key3", "value3"}
, {"key3", "value3"}
};
std::vector<std::string> expected_1c
{ "key1", "value1"
, "key2", "value2"
, "key3", "value3"
, "key3", "value3"
};
op_map_type expected_1d;
expected_1d = expected_1b;
op_vec_type expected_1e;
expected_1e = expected_1c;
tuple_type e1f
{ std::string{"key1"}, std::string{"value1"}
, std::string{"key2"}, std::string{"value2"}
, std::string{"key3"}, std::string{"value3"}
, std::string{"key3"}, std::string{"value3"}
};
auto const in00 = expect<std::vector<node_type>>{wire, expected_1a, "map.node"};
auto const in01 = expect<map_type>{"%0\r\n", map_type{}, "map.map.empty"};
auto const in02 = expect<map_type>{wire, expected_1b, "map.map"};
auto const in03 = expect<mmap_type>{wire, e1k, "map.multimap"};
auto const in04 = expect<umap_type>{wire, e1g, "map.unordered_map"};
auto const in05 = expect<mumap_type>{wire, e1l, "map.unordered_multimap"};
auto const in06 = expect<vec_type>{wire, expected_1c, "map.vector"};
auto const in07 = expect<op_map_type>{wire, expected_1d, "map.optional.map"};
auto const in08 = expect<op_vec_type>{wire, expected_1e, "map.optional.vector"};
auto const in09 = expect<std::tuple<op_map_type>>{"*1\r\n" + wire, std::tuple<op_map_type>{expected_1d}, "map.transaction.optional.map"};
auto const in10 = expect<int>{"%11\r\n", int{}, "map.invalid.int", aedis::adapter::make_error_condition(aedis::adapter::error::expects_simple_type)};
auto const in11 = expect<tuple_type>{wire, e1f, "map.tuple"};
auto ex = ioc.get_executor();
test_sync(ex, in00);
test_sync(ex, in01);
test_sync(ex, in02);
test_sync(ex, in03);
test_sync(ex, in04);
test_sync(ex, in05);
test_sync(ex, in07);
test_sync(ex, in08);
test_sync(ex, in09);
test_sync(ex, in00);
test_sync(ex, in11);
test_async(ex, in00);
test_async(ex, in01);
test_async(ex, in02);
test_async(ex, in03);
test_async(ex, in04);
test_async(ex, in05);
test_async(ex, in07);
test_async(ex, in08);
test_async(ex, in09);
test_async(ex, in00);
test_async(ex, in11);
}
void test_attribute(net::io_context& ioc)
{
char const* wire = "|1\r\n+key-popularity\r\n%2\r\n$1\r\na\r\n,0.1923\r\n$1\r\nb\r\n,0.0012\r\n";
std::vector<node_type> e1a
{ {resp3::type::attribute, 1UL, 0UL, {}}
, {resp3::type::simple_string, 1UL, 1UL, "key-popularity"}
, {resp3::type::map, 2UL, 1UL, {}}
, {resp3::type::blob_string, 1UL, 2UL, "a"}
, {resp3::type::doublean, 1UL, 2UL, "0.1923"}
, {resp3::type::blob_string, 1UL, 2UL, "b"}
, {resp3::type::doublean, 1UL, 2UL, "0.0012"}
};
std::vector<node_type> e1b;
auto const in01 = expect<std::vector<node_type>>{wire, e1a, "attribute.node"};
auto const in02 = expect<std::vector<node_type>>{"|0\r\n", e1b, "attribute.node.empty"};
auto ex = ioc.get_executor();
test_sync(ex, in01);
test_sync(ex, in02);
test_async(ex, in01);
test_async(ex, in02);
}
void test_array(net::io_context& ioc)
{
char const* wire = "*3\r\n$2\r\n11\r\n$2\r\n22\r\n$1\r\n3\r\n";
std::vector<node_type> e1a
{ {resp3::type::array, 3UL, 0UL, {}}
, {resp3::type::blob_string, 1UL, 1UL, {"11"}}
, {resp3::type::blob_string, 1UL, 1UL, {"22"}}
, {resp3::type::blob_string, 1UL, 1UL, {"3"}}
};
std::vector<int> const e1b{11, 22, 3};
std::vector<std::string> const e1c{"11", "22", "3"};
std::vector<std::string> const e1d{};
std::vector<node_type> const e1e{{resp3::type::array, 0UL, 0UL, {}}};
std::array<int, 3> const e1f{11, 22, 3};
std::list<int> const e1g{11, 22, 3};
std::deque<int> const e1h{11, 22, 3};
auto const in01 = expect<std::vector<node_type>>{wire, e1a, "array.node"};
auto const in02 = expect<std::vector<int>>{wire, e1b, "array.int"};
auto const in03 = expect<std::vector<node_type>>{"*0\r\n", e1e, "array.node.empty"};
auto const in04 = expect<std::vector<std::string>>{"*0\r\n", e1d, "array.string.empty"};
auto const in05 = expect<std::vector<std::string>>{wire, e1c, "array.string"};
auto const in06 = expect<std::array<int, 3>>{wire, e1f, "array.array"};
auto const in07 = expect<std::list<int>>{wire, e1g, "array.list"};
auto const in08 = expect<std::deque<int>>{wire, e1h, "array.deque"};
auto ex = ioc.get_executor();
test_sync(ex, in01);
test_sync(ex, in02);
test_sync(ex, in03);
test_sync(ex, in04);
test_sync(ex, in05);
test_sync(ex, in06);
test_sync(ex, in07);
test_sync(ex, in08);
test_async(ex, in01);
test_async(ex, in02);
test_async(ex, in03);
test_async(ex, in04);
test_async(ex, in05);
test_async(ex, in06);
test_async(ex, in07);
test_async(ex, in08);
}
void test_set(net::io_context& ioc)
{
using set_type = std::set<std::string>;
using mset_type = std::multiset<std::string>;
using uset_type = std::unordered_set<std::string>;
using muset_type = std::unordered_multiset<std::string>;
using vec_type = std::vector<std::string>;
using op_vec_type = boost::optional<std::vector<std::string>>;
std::string const wire = "~6\r\n+orange\r\n+apple\r\n+one\r\n+two\r\n+three\r\n+orange\r\n";
std::vector<node_type> const expected1a
{ {resp3::type::set, 6UL, 0UL, {}}
, {resp3::type::simple_string, 1UL, 1UL, {"orange"}}
, {resp3::type::simple_string, 1UL, 1UL, {"apple"}}
, {resp3::type::simple_string, 1UL, 1UL, {"one"}}
, {resp3::type::simple_string, 1UL, 1UL, {"two"}}
, {resp3::type::simple_string, 1UL, 1UL, {"three"}}
, {resp3::type::simple_string, 1UL, 1UL, {"orange"}}
};
mset_type const e1f{"apple", "one", "orange", "orange", "three", "two"};
uset_type const e1c{"apple", "one", "orange", "three", "two"};
muset_type const e1g{"apple", "one", "orange", "orange", "three", "two"};
vec_type const e1d = {"orange", "apple", "one", "two", "three", "orange"};
op_vec_type expected_1e;
expected_1e = e1d;
auto const in00 = expect<std::vector<node_type>>{wire, expected1a, "set.node"};
auto const in01 = expect<std::vector<node_type>>{"~0\r\n", std::vector<node_type>{ {resp3::type::set, 0UL, 0UL, {}} }, "set.node (empty)"};
auto const in02 = expect<set_type>{wire, set_type{"apple", "one", "orange", "three", "two"}, "set.set"};
auto const in03 = expect<mset_type>{wire, e1f, "set.multiset"};
auto const in04 = expect<vec_type>{wire, e1d, "set.vector "};
auto const in05 = expect<op_vec_type>{wire, expected_1e, "set.vector "};
auto const in06 = expect<uset_type>{wire, e1c, "set.unordered_set"};
auto const in07 = expect<muset_type>{wire, e1g, "set.unordered_multiset"};
auto const in08 = expect<std::tuple<uset_type>>{"*1\r\n" + wire, std::tuple<uset_type>{e1c}, "set.tuple"};
auto ex = ioc.get_executor();
test_sync(ex, in00);
test_sync(ex, in01);
test_sync(ex, in02);
test_sync(ex, in03);
test_sync(ex, in04);
test_sync(ex, in05);
test_sync(ex, in06);
test_sync(ex, in07);
test_sync(ex, in08);
test_async(ex, in00);
test_async(ex, in01);
test_async(ex, in02);
test_async(ex, in03);
test_async(ex, in04);
test_async(ex, in05);
test_async(ex, in06);
test_async(ex, in07);
test_async(ex, in08);
}
void test_simple_error(net::io_context& ioc)
{
auto const in01 = expect<node_type>{"-Error\r\n", node_type{resp3::type::simple_error, 1UL, 0UL, {"Error"}}, "simple_error.node"};
auto const in02 = expect<node_type>{"-\r\n", node_type{resp3::type::simple_error, 1UL, 0UL, {""}}, "simple_error.node.empty"};
auto ex = ioc.get_executor();
test_sync(ex, in01);
test_sync(ex, in02);
test_async(ex, in01);
test_async(ex, in02);
}
void test_blob_string(net::io_context& ioc)
{
std::string str(100000, 'a');
str[1000] = '\r';
str[1001] = '\n';
std::string wire;
wire += '$';
wire += std::to_string(str.size());
wire += "\r\n";
wire += str;
wire += "\r\n";
auto const in01 = expect<node_type>{"$2\r\nhh\r\n", node_type{resp3::type::blob_string, 1UL, 0UL, {"hh"}}, "blob_string.node"};
auto const in02 = expect<node_type>{"$26\r\nhhaa\aaaa\raaaaa\r\naaaaaaaaaa\r\n", node_type{resp3::type::blob_string, 1UL, 0UL, {"hhaa\aaaa\raaaaa\r\naaaaaaaaaa"}}, "blob_string.node (with separator)"};
auto const in03 = expect<node_type>{"$0\r\n\r\n", node_type{resp3::type::blob_string, 1UL, 0UL, {}}, "blob_string.node.empty"};
auto const in04 = expect<node_type>{wire, node_type{resp3::type::blob_string, 1UL, 0UL, {str}}, "blob_string.node (long string)"};
auto ex = ioc.get_executor();
test_sync(ex, in01);
test_sync(ex, in02);
test_sync(ex, in03);
test_sync(ex, in04);
test_async(ex, in01);
test_async(ex, in02);
test_async(ex, in03);
test_async(ex, in04);
}
void test_double(net::io_context& ioc)
{
auto const in01 = expect<node_type>{",1.23\r\n", node_type{resp3::type::doublean, 1UL, 0UL, {"1.23"}}, "double.node"};
auto const in02 = expect<node_type>{",inf\r\n", node_type{resp3::type::doublean, 1UL, 0UL, {"inf"}}, "double.node (inf)"};
auto const in03 = expect<node_type>{",-inf\r\n", node_type{resp3::type::doublean, 1UL, 0UL, {"-inf"}}, "double.node (-inf)"};
auto ex = ioc.get_executor();
test_sync(ex, in01);
test_sync(ex, in02);
test_sync(ex, in03);
test_async(ex, in01);
test_async(ex, in02);
test_async(ex, in03);
}
void test_blob_error(net::io_context& ioc)
{
auto const in01 = expect<node_type>{"!21\r\nSYNTAX invalid syntax\r\n", node_type{resp3::type::blob_error, 1UL, 0UL, {"SYNTAX invalid syntax"}}, "blob_error"};
auto const in02 = expect<node_type>{"!0\r\n\r\n", node_type{resp3::type::blob_error, 1UL, 0UL, {}}, "blob_error.empty"};
auto ex = ioc.get_executor();
test_sync(ex, in01);
test_sync(ex, in02);
test_async(ex, in01);
test_async(ex, in02);
}
void test_verbatim_string(net::io_context& ioc)
{
auto const in01 = expect<node_type>{"=15\r\ntxt:Some string\r\n", node_type{resp3::type::verbatim_string, 1UL, 0UL, {"txt:Some string"}}, "verbatim_string"};
auto const in02 = expect<node_type>{"=0\r\n\r\n", node_type{resp3::type::verbatim_string, 1UL, 0UL, {}}, "verbatim_string.empty"};
auto ex = ioc.get_executor();
test_sync(ex, in01);
test_sync(ex, in02);
test_async(ex, in01);
test_async(ex, in02);
}
void test_big_number(net::io_context& ioc)
{
auto const in01 = expect<node_type>{"(3492890328409238509324850943850943825024385\r\n", node_type{resp3::type::big_number, 1UL, 0UL, {"3492890328409238509324850943850943825024385"}}, "big_number.node"};
auto const in02 = expect<int>{"(\r\n", int{}, "big_number.error (empty field)", aedis::resp3::make_error_condition(aedis::resp3::error::empty_field)};
auto ex = ioc.get_executor();
test_sync(ex, in01);
test_sync(ex, in02);
test_async(ex, in01);
test_async(ex, in02);
}
void test_simple_string(net::io_context& ioc)
{
boost::optional<std::string> ok1, ok2;
ok1 = "OK";
ok2 = "";
auto in00 = expect<std::string>{"+OK\r\n", std::string{"OK"}, "simple_string.string"};
auto in01 = expect<std::string>{"+\r\n", std::string{""}, "simple_string.string.empty"};
auto in02 = expect<boost::optional<std::string>>{"+OK\r\n", boost::optional<std::string>{"OK"}, "simple_string.optional"};
auto in03 = expect<boost::optional<std::string>>{"+\r\n", boost::optional<std::string>{""}, "simple_string.optional.empty"};
auto ex = ioc.get_executor();
test_sync(ex, in00);
test_sync(ex, in01);
test_sync(ex, in02);
test_sync(ex, in03);
test_async(ex, in00);
test_async(ex, in01);
test_async(ex, in02);
test_async(ex, in03);
}
void test_resp3(net::io_context& ioc)
{
auto const in01 = expect<int>{"s11\r\n", int{}, "number.error", aedis::resp3::make_error_condition(aedis::resp3::error::invalid_type)};
auto const in02 = expect<int>{":adf\r\n", int{11}, "number.int", aedis::resp3::make_error_condition(aedis::resp3::error::not_a_number)};
auto const in03 = expect<int>{":\r\n", int{}, "number.error (empty field)", aedis::resp3::make_error_condition(aedis::resp3::error::empty_field)};
auto const in04 = expect<boost::optional<bool>>{"#\r\n", boost::optional<bool>{}, "bool.error", aedis::resp3::make_error_condition(aedis::resp3::error::empty_field)};
auto const in05 = expect<std::string>{",\r\n", std::string{}, "double.error (empty field)", aedis::resp3::make_error_condition(aedis::resp3::error::empty_field)};
auto ex = ioc.get_executor();
test_sync(ex, in01);
test_sync(ex, in02);
test_sync(ex, in03);
test_sync(ex, in04);
test_sync(ex, in05);
test_async(ex, in01);
test_async(ex, in02);
test_async(ex, in03);
test_async(ex, in04);
test_async(ex, in05);
}
void test_null(net::io_context& ioc)
{
using op_type_01 = boost::optional<bool>;
using op_type_02 = boost::optional<int>;
using op_type_03 = boost::optional<std::string>;
using op_type_04 = boost::optional<std::vector<std::string>>;
using op_type_05 = boost::optional<std::list<std::string>>;
using op_type_06 = boost::optional<std::map<std::string, std::string>>;
using op_type_07 = boost::optional<std::unordered_map<std::string, std::string>>;
using op_type_08 = boost::optional<std::set<std::string>>;
using op_type_09 = boost::optional<std::unordered_set<std::string>>;
auto const in01 = expect<op_type_01>{"_\r\n", op_type_01{}, "null.optional.bool"};
auto const in02 = expect<op_type_02>{"_\r\n", op_type_02{}, "null.optional.int"};
auto const in03 = expect<op_type_03>{"_\r\n", op_type_03{}, "null.optional.string"};
auto const in04 = expect<op_type_04>{"_\r\n", op_type_04{}, "null.optional.vector"};
auto const in05 = expect<op_type_05>{"_\r\n", op_type_05{}, "null.optional.list"};
auto const in06 = expect<op_type_06>{"_\r\n", op_type_06{}, "null.optional.map"};
auto const in07 = expect<op_type_07>{"_\r\n", op_type_07{}, "null.optional.unordered_map"};
auto const in08 = expect<op_type_08>{"_\r\n", op_type_08{}, "null.optional.set"};
auto const in09 = expect<op_type_09>{"_\r\n", op_type_09{}, "null.optional.unordered_set"};
auto ex = ioc.get_executor();
test_sync(ex, in01);
test_sync(ex, in02);
test_sync(ex, in03);
test_sync(ex, in04);
test_sync(ex, in05);
test_sync(ex, in06);
test_sync(ex, in07);
test_sync(ex, in08);
test_sync(ex, in09);
test_async(ex, in01);
test_async(ex, in02);
test_async(ex, in03);
test_async(ex, in04);
test_async(ex, in05);
test_async(ex, in06);
test_async(ex, in07);
test_async(ex, in08);
test_async(ex, in09);
}
int main()
{
net::io_context ioc {1};
// Simple types.
test_simple_string(ioc);
test_simple_error(ioc);
test_blob_string(ioc);
test_blob_error(ioc);
test_number(ioc);
test_double(ioc);
test_bool(ioc);
test_null(ioc);
test_big_number(ioc);
test_verbatim_string(ioc);
// Aggregates.
test_array(ioc);
test_set(ioc);
test_map(ioc);
test_push(ioc);
test_streamed_string(ioc);
// RESP3
test_resp3(ioc);
ioc.run();
}

View File

@@ -1,648 +0,0 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include <iostream>
#include <map>
#include <aedis/src.hpp>
#include <aedis/aedis.hpp>
#include "test_stream.hpp"
#include "check.hpp"
// Consider using Beast test_stream and instantiate the test socket
// only once.
namespace net = aedis::net;
using tcp = net::ip::tcp;
using test_tcp_socket = net::use_awaitable_t<>::as_default_on_t<aedis::test_stream<aedis::net::system_executor>>;
namespace this_coro = net::this_coro;
using namespace aedis;
using namespace aedis::resp3;
std::vector<node> gresp;
//-------------------------------------------------------------------
void simple_string_sync()
{
test_tcp_socket ts {"+OK\r\n"};
std::string rbuffer;
auto dbuffer = net::dynamic_buffer(rbuffer);
{
node result;
resp3::read(ts, dbuffer, adapt(result));
node expected {resp3::type::simple_string, 1UL, 0UL, {"OK"}};
check_equal(result, expected, "simple_string (node-sync)");
}
{
std::string result;
resp3::read(ts, dbuffer, adapt(result));
std::string expected {"OK"};
check_equal(result, expected, "simple_string (string-sync)");
}
{
std::optional<std::string> result;
resp3::read(ts, dbuffer, adapt(result));
std::optional<std::string> expected {"OK"};
check_equal(result, expected, "simple_string (optional-string-sync)");
}
}
void simple_string_sync_empty()
{
test_tcp_socket ts {"+\r\n"};
std::string rbuffer;
auto dbuffer = net::dynamic_buffer(rbuffer);
{
node result;
resp3::read(ts, dbuffer, adapt(result));
node expected {resp3::type::simple_string, 1UL, 0UL, {""}};
check_equal(result, expected, "simple_string (empty-node-sync)");
}
{
std::string result;
resp3::read(ts, dbuffer, adapt(result));
std::string expected {""};
check_equal(result, expected, "simple_string (empty-string-sync)");
}
{
std::optional<std::string> result;
resp3::read(ts, dbuffer, adapt(result));
std::optional<std::string> expected {""};
check_equal(result, expected, "simple_string (empty-optional-string-sync)");
}
}
net::awaitable<void> simple_string_async()
{
test_tcp_socket ts {"+OK\r\n"};
std::string rbuffer;
auto dbuffer = net::dynamic_buffer(rbuffer);
{
node result;
co_await resp3::async_read(ts, dbuffer, adapt(result));
node expected {resp3::type::simple_string, 1UL, 0UL, {"OK"}};
check_equal(result, expected, "simple_string (node-async)");
}
{
std::string result;
co_await resp3::async_read(ts, dbuffer, adapt(result));
std::string expected {"OK"};
check_equal(result, expected, "simple_string (string-async)");
}
{
std::optional<std::string> result;
co_await resp3::async_read(ts, dbuffer, adapt(result));
std::optional<std::string> expected {"OK"};
check_equal(result, expected, "simple_string (optional-string-async)");
}
}
net::awaitable<void> simple_string_async_empty()
{
test_tcp_socket ts {"+\r\n"};
std::string rbuffer;
auto dbuffer = net::dynamic_buffer(rbuffer);
{
node result;
co_await resp3::async_read(ts, dbuffer, adapt(result));
node expected {resp3::type::simple_string, 1UL, 0UL, {""}};
check_equal(result, expected, "simple_string (empty-node-async)");
}
{
std::string result;
co_await resp3::async_read(ts, dbuffer, adapt(result));
std::string expected {""};
check_equal(result, expected, "simple_string (empty-string-async)");
}
{
std::optional<std::string> result;
co_await resp3::async_read(ts, dbuffer, adapt(result));
std::optional<std::string> expected {""};
check_equal(result, expected, "simple_string (empty-optional-string-async)");
}
}
// TODO: Test a large simple string. For example
// std::string str(10000, 'a');
// std::string cmd;
// cmd += '+';
// cmd += str;
// cmd += "\r\n";
//-------------------------------------------------------------------------
net::awaitable<void> test_simple_error_async()
{
std::string buf;
{
test_tcp_socket ts {"-Error\r\n"};
node result;
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(result));
node expected {resp3::type::simple_error, 1UL, 0UL, {"Error"}};
check_equal(result, expected, "simple_error (node-async)");
}
{
test_tcp_socket ts {"-Error\r\n"};
std::string result;
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(result));
std::string expected {"Error"};
check_equal(result, expected, "simple_error (string-async)");
}
{
test_tcp_socket ts {"-\r\n"};
std::string result;
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(result));
std::string expected {""};
check_equal(result, expected, "simple_error (empty-string-async)");
}
// TODO: Test with optional.
// TODO: Test with a very long string?
}
//-------------------------------------------------------------------------
net::awaitable<void> test_number()
{
std::string buf;
{
test_tcp_socket ts {":-3\r\n"};
node result;
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(result));
node expected {resp3::type::number, 1UL, 0UL, {"-3"}};
check_equal(result, expected, "number (node-async)");
}
{
test_tcp_socket ts {":-3\r\n"};
long long result;
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(result));
check_equal(result, -3LL, "number (signed-async)");
}
{
test_tcp_socket ts {":3\r\n"};
std::size_t result;
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(result));
check_equal(result, 3UL, "number (unsigned-async)");
}
}
//-------------------------------------------------------------------------
net::awaitable<void> array_async()
{
test_tcp_socket ts {"*3\r\n$2\r\n11\r\n$2\r\n22\r\n$1\r\n3\r\n"};
std::string buf;
auto dbuf = net::dynamic_buffer(buf);
{
std::vector<node> result;
boost::system::error_code ec;
co_await resp3::async_read(ts, dbuf, adapt(result), net::redirect_error(net::use_awaitable, ec));
std::vector<node> expected
{ {resp3::type::array, 3UL, 0UL, {}}
, {resp3::type::blob_string, 1UL, 1UL, {"11"}}
, {resp3::type::blob_string, 1UL, 1UL, {"22"}}
, {resp3::type::blob_string, 1UL, 1UL, {"3"}}
};
check_equal(result, expected, "array (node-async)");
}
{
std::vector<int> result;
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(result));
std::vector<int> expected {11, 22, 3};
check_equal(result, expected, "array (int-async)");
}
{
test_tcp_socket ts {"*0\r\n"};
std::vector<int> result;
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(result));
std::vector<int> expected;
check_equal(result, expected, "array (empty)");
}
}
//-------------------------------------------------------------------------
net::awaitable<void> test_blob_string()
{
using namespace aedis;
std::string buf;
{
std::string cmd {"$2\r\nhh\r\n"};
test_tcp_socket ts {cmd};
gresp.clear();
std::vector<node> expected
{ {resp3::type::blob_string, 1UL, 0UL, {"hh"}} };
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(gresp));
check_equal(gresp, expected, "blob_string");
}
{
std::string cmd {"$26\r\nhhaa\aaaa\raaaaa\r\naaaaaaaaaa\r\n"};
test_tcp_socket ts {cmd};
gresp.clear();
std::vector<node> expected
{ {resp3::type::blob_string, 1UL, 0UL, {"hhaa\aaaa\raaaaa\r\naaaaaaaaaa"}} };
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(gresp));
check_equal(gresp, expected, "blob_string (with separator)");
}
{
std::string cmd {"$0\r\n\r\n"};
test_tcp_socket ts {cmd};
gresp.clear();
std::vector<node> expected
{ {resp3::type::blob_string, 1UL, 0UL, {}} };
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(gresp));
check_equal(gresp, expected, "blob_string (size 0)");
}
}
net::awaitable<void> test_floating_point()
{
using namespace aedis;
std::string buf;
{
std::string cmd {",1.23\r\n"};
test_tcp_socket ts {cmd};
std::vector<node> resp;
std::vector<node> expected
{ {resp3::type::doublean, 1UL, 0UL, {"1.23"}} };
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(resp));
check_equal(resp, expected, "double");
}
{
std::string cmd {",inf\r\n"};
test_tcp_socket ts {cmd};
std::vector<node> resp;
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(resp));
std::vector<node> expected
{ {resp3::type::doublean, 1UL, 0UL, {"inf"}} };
check_equal(resp, expected, "double (inf)");
}
{
std::string cmd {",-inf\r\n"};
test_tcp_socket ts {cmd};
std::vector<node> resp;
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(resp));
std::vector<node> expected
{ {resp3::type::doublean, 1UL, 0UL, {"-inf"}} };
check_equal(resp, expected, "double (-inf)");
}
}
net::awaitable<void> test_boolean()
{
using namespace aedis;
std::string buf;
{
std::string cmd {"#f\r\n"};
test_tcp_socket ts {cmd};
std::vector<node> resp;
std::vector<node> expected
{ {resp3::type::boolean, 1UL, 0UL, {"f"}} };
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(resp));
check_equal(resp, expected, "bool (false)");
}
{
std::string cmd {"#t\r\n"};
test_tcp_socket ts {cmd};
std::vector<node> resp;
std::vector<node> expected
{ {resp3::type::boolean, 1UL, 0UL, {"t"}} };
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(resp));
check_equal(resp, expected, "bool (true)");
}
}
net::awaitable<void> test_blob_error()
{
using namespace aedis;
std::string buf;
{
std::string cmd {"!21\r\nSYNTAX invalid syntax\r\n"};
test_tcp_socket ts {cmd};
gresp.clear();
std::vector<node> expected
{ {resp3::type::blob_error, 1UL, 0UL, {"SYNTAX invalid syntax"}} };
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(gresp));
check_equal(gresp, expected, "blob_error (message)");
}
{
std::string cmd {"!0\r\n\r\n"};
test_tcp_socket ts {cmd};
gresp.clear();
std::vector<node> expected
{ {resp3::type::blob_error, 1UL, 0UL, {}} };
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(gresp));
check_equal(gresp, expected, "blob_error (empty message)");
}
}
net::awaitable<void> test_verbatim_string()
{
using namespace aedis;
std::string buf;
{
std::string cmd {"=15\r\ntxt:Some string\r\n"};
test_tcp_socket ts {cmd};
gresp.clear();
std::vector<node> expected
{ {resp3::type::verbatim_string, 1UL, 0UL, {"txt:Some string"}} };
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(gresp));
check_equal(gresp, expected, "verbatim_string");
}
{
std::string cmd {"=0\r\n\r\n"};
test_tcp_socket ts {cmd};
gresp.clear();
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(gresp));
std::vector<node> expected
{ {resp3::type::verbatim_string, 1UL, 0UL, {}} };
check_equal(gresp, expected, "verbatim_string (empty)");
}
}
net::awaitable<void> test_set2()
{
using namespace aedis;
std::string buf;
{
std::string cmd {"~5\r\n+orange\r\n+apple\r\n+one\r\n+two\r\n+three\r\n"};
test_tcp_socket ts {cmd};
gresp.clear();
std::vector<node> expected
{ {resp3::type::set, 5UL, 0UL, {}}
, {resp3::type::simple_string, 1UL, 1UL, {"orange"}}
, {resp3::type::simple_string, 1UL, 1UL, {"apple"}}
, {resp3::type::simple_string, 1UL, 1UL, {"one"}}
, {resp3::type::simple_string, 1UL, 1UL, {"two"}}
, {resp3::type::simple_string, 1UL, 1UL, {"three"}}
};
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(gresp));
check_equal(gresp, expected, "test set (1)");
}
{
std::string cmd {"~0\r\n"};
test_tcp_socket ts {cmd};
gresp.clear();
std::vector<node> expected
{ {resp3::type::set, 0UL, 0UL, {}}
};
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(gresp));
check_equal(gresp, expected, "test set (2)");
}
}
net::awaitable<void> test_flat_map_async()
{
std::string buf;
auto dbuf = net::dynamic_buffer(buf);
{
test_tcp_socket ts {"%3\r\n$6\r\nserver\r\n$5\r\nredis\r\n$7\r\nversion\r\n$5\r\n6.0.9\r\n$5\r\nproto\r\n:3\r\n"};
std::map<std::string, std::string> result;
co_await resp3::async_read(ts, dbuf, adapt(result));
std::map<std::string, std::string> expected
{ {"server", "redis"}
, {"version", "6.0.9"}
, {"proto", "3"}
};
check_equal(result, expected, "map (flat-map-async)");
}
{
test_tcp_socket ts {"%0\r\n"};
std::map<std::string, std::string> result;
co_await resp3::async_read(ts, dbuf, adapt(result));
std::map<std::string, std::string> expected;
check_equal(result, expected, "map (flat-empty-map-async)");
}
{
// TODO: Why do we get a crash when %3. It should produce an
// error instead.
test_tcp_socket ts {"%2\r\n$6\r\nserver\r\n$2\r\n10\r\n$7\r\nversion\r\n$2\r\n30\r\n"};
std::map<std::string, int> result;
co_await resp3::async_read(ts, dbuf, adapt(result));
std::map<std::string, int> expected
{ {"server", 10}
, {"version", 30}
};
check_equal(result, expected, "map (flat-map-string-int-async)");
}
// TODO: Test optional map.
// TODO: Test serializaition with different key and value types.
}
net::awaitable<void> test_map()
{
using namespace aedis;
std::string buf;
{
std::string cmd {"%7\r\n$6\r\nserver\r\n$5\r\nredis\r\n$7\r\nversion\r\n$5\r\n6.0.9\r\n$5\r\nproto\r\n:3\r\n$2\r\nid\r\n:203\r\n$4\r\nmode\r\n$10\r\nstandalone\r\n$4\r\nrole\r\n$6\r\nmaster\r\n$7\r\nmodules\r\n*0\r\n"};
test_tcp_socket ts {cmd};
gresp.clear();
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(gresp));
std::vector<node> expected
{ {resp3::type::map, 7UL, 0UL, {}}
, {resp3::type::blob_string, 1UL, 1UL, {"server"}}
, {resp3::type::blob_string, 1UL, 1UL, {"redis"}}
, {resp3::type::blob_string, 1UL, 1UL, {"version"}}
, {resp3::type::blob_string, 1UL, 1UL, {"6.0.9"}}
, {resp3::type::blob_string, 1UL, 1UL, {"proto"}}
, {resp3::type::number, 1UL, 1UL, {"3"}}
, {resp3::type::blob_string, 1UL, 1UL, {"id"}}
, {resp3::type::number, 1UL, 1UL, {"203"}}
, {resp3::type::blob_string, 1UL, 1UL, {"mode"}}
, {resp3::type::blob_string, 1UL, 1UL, {"standalone"}}
, {resp3::type::blob_string, 1UL, 1UL, {"role"}}
, {resp3::type::blob_string, 1UL, 1UL, {"master"}}
, {resp3::type::blob_string, 1UL, 1UL, {"modules"}}
, {resp3::type::array, 0UL, 1UL, {}}
};
check_equal(gresp, expected, "test map");
}
{
std::string cmd {"%0\r\n"};
test_tcp_socket ts {cmd};
gresp.clear();
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(gresp));
std::vector<node> expected
{ {resp3::type::map, 0UL, 0UL, {}} };
check_equal(gresp, expected, "test map (empty)");
}
}
net::awaitable<void> test_streamed_string()
{
using namespace aedis;
std::string buf;
{
std::string cmd {"$?\r\n;4\r\nHell\r\n;5\r\no wor\r\n;1\r\nd\r\n;0\r\n"};
test_tcp_socket ts {cmd};
gresp.clear();
std::vector<node> expected
{ {resp3::type::streamed_string_part, 1UL, 0UL, {"Hello world"}} };
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(gresp));
check_equal(gresp, expected, "streamed string");
}
{
std::string cmd {"$?\r\n;0\r\n"};
test_tcp_socket ts {cmd};
std::vector<node> resp;
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(resp));
std::vector<node> expected
{ {resp3::type::streamed_string_part, 1UL, 0UL, {}} };
check_equal(resp, expected, "streamed string (empty)");
}
}
//net::awaitable<void> offline()
//{
// std::string buf;
// //{
// // std::string cmd {"|1\r\n+key-popularity\r\n%2\r\n$1\r\na\r\n,0.1923\r\n$1\r\nb\r\n,0.0012\r\n"};
// // test_tcp_socket ts {cmd};
// // resp3::flat_radapter res;
// // co_await async_read(ts, buf, res);
// // check_equal(res.result, {"key-popularity", "a", "0.1923", "b", "0.0012"}, "attribute");
// //}
//
// //{
// // std::string cmd {">4\r\n+pubsub\r\n+message\r\n+foo\r\n+bar\r\n"};
// // test_tcp_socket ts {cmd};
// // resp3::flat_radapter res;
// // co_await async_read(ts, buf, res);
// // check_equal(res.result, {"pubsub", "message", "foo", "bar"}, "push type");
// //}
//
// //{
// // std::string cmd {">0\r\n"};
// // test_tcp_socket ts {cmd};
// // resp3::flat_radapter res;
// // co_await async_read(ts, buf, res);
// // check_equal(res.result, {}, "push type (empty)");
// //}
//}
net::awaitable<void> optional_async()
{
test_tcp_socket ts {"_\r\n"};
std::string buf;
auto dbuf = net::dynamic_buffer(buf);
{
node result;
co_await resp3::async_read(ts, dbuf, adapt(result));
node expected {resp3::type::null, 1UL, 0UL, {""}};
check_equal(result, expected, "optional (node-async)");
}
{
int result;
boost::system::error_code ec;
co_await resp3::async_read(ts, dbuf, adapt(result), net::redirect_error(net::use_awaitable, ec));
//auto const expected = make_error_code(adapter::error::null);
//std::cout << expected.message() << std::endl;
// TODO: Convert to std::error_code.
//check_equal(ec, expected, "optional (int-async)");
}
{
std::optional<int> result;
co_await resp3::async_read(ts, dbuf, adapt(result));
std::optional<int> expected;
check_equal(result, expected, "optional (optional-int-async)");
}
{
std::optional<std::string> result;
co_await resp3::async_read(ts, dbuf, adapt(result));
std::optional<std::string> expected;
check_equal(result, expected, "optional (optional-int-async)");
}
}
int main()
{
simple_string_sync();
simple_string_sync_empty();
net::io_context ioc {1};
co_spawn(ioc, simple_string_async(), net::detached);
co_spawn(ioc, simple_string_async_empty(), net::detached);
co_spawn(ioc, test_simple_error_async(), net::detached);
co_spawn(ioc, test_number(), net::detached);
co_spawn(ioc, test_map(), net::detached);
co_spawn(ioc, test_flat_map_async(), net::detached);
co_spawn(ioc, optional_async(), net::detached);
co_spawn(ioc, array_async(), net::detached);
co_spawn(ioc, test_blob_string(), net::detached);
co_spawn(ioc, test_floating_point(), net::detached);
co_spawn(ioc, test_boolean(), net::detached);
co_spawn(ioc, test_blob_error(), net::detached);
co_spawn(ioc, test_verbatim_string(), net::detached);
co_spawn(ioc, test_set2(), net::detached);
ioc.run();
}

View File

@@ -8,31 +8,35 @@
#include <iostream>
#include <map>
#include <boost/system/errc.hpp>
#include <boost/asio/awaitable.hpp>
#include <boost/asio/redirect_error.hpp>
#include <boost/asio/use_awaitable.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/connect.hpp>
#include <aedis/aedis.hpp>
#include <aedis/src.hpp>
#include "test_stream.hpp"
#include "check.hpp"
namespace net = aedis::net;
namespace net = boost::asio;
namespace resp3 = aedis::resp3;
using aedis::redis::command;
using aedis::generic::make_serializer;
using aedis::adapter::adapt;
using node_type = aedis::resp3::node<std::string>;
using tcp = net::ip::tcp;
using tcp_socket = net::use_awaitable_t<>::as_default_on_t<tcp::socket>;
using test_tcp_socket = net::use_awaitable_t<>::as_default_on_t<aedis::test_stream<aedis::net::system_executor>>;
namespace this_coro = net::this_coro;
using namespace aedis;
using namespace aedis::resp3;
using aedis::redis::command;
using aedis::redis::make_serializer;
std::vector<node> gresp;
//-------------------------------------------------------------------
std::vector<node_type> gresp;
net::awaitable<void>
test_general(net::ip::tcp::resolver::results_type const& res)
{
auto ex = co_await this_coro::executor;
auto ex = co_await net::this_coro::executor;
std::vector<int> list_ {1 ,2, 3, 4, 5, 6};
std::string set_ {"aaa"};
@@ -42,7 +46,7 @@ test_general(net::ip::tcp::resolver::results_type const& res)
auto sr = make_serializer(request);
sr.push(command::hello, 3);
sr.push(command::flushall);
sr.push_range(command::rpush, "a", std::cbegin(list_), std::cend(list_));
sr.push_range(command::rpush, "a", list_);
sr.push(command::llen, "a");
sr.push(command::lrange, "a", 0, -1);
sr.push(command::ltrim, "a", 2, -2);
@@ -71,7 +75,7 @@ test_general(net::ip::tcp::resolver::results_type const& res)
{ {"field1", "value1"}
, {"field2", "value2"}};
sr.push_range(command::hset, "d", std::cbegin(m1), std::cend(m1));
sr.push_range(command::hset, "d", m1);
sr.push(command::hget, "d", "field2");
sr.push(command::hgetall, "d");
sr.push(command::hdel, "d", "field1", "field2"); // TODO: Test as range too.
@@ -83,7 +87,7 @@ test_general(net::ip::tcp::resolver::results_type const& res)
sr.push(command::zremrangebyscore, "f", "-inf", "+inf");
auto const v = std::vector<int>{1, 2, 3};
sr.push_range(command::sadd, "g", std::cbegin(v), std::cend(v));
sr.push_range(command::sadd, "g", v);
sr.push(command::smembers, "g");
sr.push(command::quit);
//----------------------------------
@@ -102,28 +106,28 @@ test_general(net::ip::tcp::resolver::results_type const& res)
co_await resp3::async_read(socket, net::dynamic_buffer(buffer), adapt(), net::use_awaitable);
{ // rpush:
std::vector<node> resp;
std::vector<node_type> resp;
co_await resp3::async_read(socket, net::dynamic_buffer(buffer), adapt(resp), net::use_awaitable);
auto const n = std::to_string(std::size(list_));
std::vector<node> expected
std::vector<node_type> expected
{ {resp3::type::number, 1UL, 0UL, n} };
check_equal(resp, expected, "rpush (value)");
expect_eq(resp, expected, "rpush (value)");
}
{ // llen
std::vector<node> resp;
std::vector<node_type> resp;
co_await resp3::async_read(socket, net::dynamic_buffer(buffer), adapt(resp), net::use_awaitable);
std::vector<node> expected
std::vector<node_type> expected
{ {resp3::type::number, 1UL, 0UL, {"6"}} };
check_equal(resp, expected, "llen");
expect_eq(resp, expected, "llen");
}
{ // lrange
std::vector<node> resp;
std::vector<node_type> resp;
co_await resp3::async_read(socket, net::dynamic_buffer(buffer), adapt(resp), net::use_awaitable);
std::vector<node> expected
std::vector<node_type> expected
{ {resp3::type::array, 6UL, 0UL, {}}
, {resp3::type::blob_string, 1UL, 1UL, {"1"}}
, {resp3::type::blob_string, 1UL, 1UL, {"2"}}
@@ -133,47 +137,47 @@ test_general(net::ip::tcp::resolver::results_type const& res)
, {resp3::type::blob_string, 1UL, 1UL, {"6"}}
};
check_equal(resp, expected, "lrange ");
expect_eq(resp, expected, "lrange ");
}
{ // ltrim
std::vector<node> resp;
std::vector<node_type> resp;
co_await resp3::async_read(socket, net::dynamic_buffer(buffer), adapt(resp), net::use_awaitable);
std::vector<node> expected
std::vector<node_type> expected
{ {resp3::type::simple_string, 1UL, 0UL, {"OK"}} };
check_equal(resp, expected, "ltrim");
expect_eq(resp, expected, "ltrim");
}
{ // lpop
std::vector<node> resp;
std::vector<node_type> resp;
co_await resp3::async_read(socket, net::dynamic_buffer(buffer), adapt(resp), net::use_awaitable);
std::vector<node> expected
std::vector<node_type> expected
{ {resp3::type::blob_string, 1UL, 0UL, {"3"}} };
check_equal(resp, expected, "lpop");
expect_eq(resp, expected, "lpop");
}
//{ // lpop
// std::vector<node> resp;
// std::vector<node_type> resp;
// co_await resp3::async_read(socket, net::dynamic_buffer(buffer), adapt(resp), net::use_awaitable);
// std::vector<node> expected
// std::vector<node_type> expected
// { {resp3::type::array, 2UL, 0UL, {}}
// , {resp3::type::array, 1UL, 1UL, {"4"}}
// , {resp3::type::array, 1UL, 1UL, {"5"}}
// };
// check_equal(resp, expected, "lpop");
// expect_eq(resp, expected, "lpop");
//}
//{ // lrange
// static int c = 0;
// if (c == 0) {
// std::vector<node> expected
// std::vector<node_type> expected
// { {resp3::type::array, 6UL, 0UL, {}}
// , {resp3::type::blob_string, 1UL, 1UL, {"1"}}
// , {resp3::type::blob_string, 1UL, 1UL, {"2"}}
@@ -183,12 +187,12 @@ test_general(net::ip::tcp::resolver::results_type const& res)
// , {resp3::type::blob_string, 1UL, 1UL, {"6"}}
// };
// check_equal(resp, expected, "lrange ");
// expect_eq(resp, expected, "lrange ");
// } else {
// std::vector<node> expected
// std::vector<node_type> expected
// { {resp3::type::simple_string, 1UL, 0UL, {"QUEUED"}} };
// check_equal(resp, expected, "lrange (inside transaction)");
// expect_eq(resp, expected, "lrange (inside transaction)");
// }
//
// ++c;
@@ -198,154 +202,154 @@ test_general(net::ip::tcp::resolver::results_type const& res)
// switch (cmd) {
// case command::multi:
// {
// std::vector<node> expected
// std::vector<node_type> expected
// { {resp3::type::simple_string, 1UL, 0UL, {"OK"}} };
// check_equal(resp, expected, "multi");
// expect_eq(resp, expected, "multi");
// } break;
// case command::ping:
// {
// std::vector<node> expected
// std::vector<node_type> expected
// { {resp3::type::simple_string, 1UL, 0UL, {"QUEUED"}} };
// check_equal(resp, expected, "ping");
// expect_eq(resp, expected, "ping");
// } break;
// case command::set:
// {
// std::vector<node> expected
// std::vector<node_type> expected
// { {resp3::type::simple_string, 1UL, 0UL, {"OK"}} };
// check_equal(resp, expected, "set");
// expect_eq(resp, expected, "set");
// } break;
// case command::quit:
// {
// std::vector<node> expected
// std::vector<node_type> expected
// { {resp3::type::simple_string, 1UL, 0UL, {"OK"}} };
// check_equal(resp, expected, "quit");
// expect_eq(resp, expected, "quit");
// } break;
// case command::flushall:
// {
// std::vector<node> expected
// std::vector<node_type> expected
// { {resp3::type::simple_string, 1UL, 0UL, {"OK"}} };
// check_equal(resp, expected, "flushall");
// expect_eq(resp, expected, "flushall");
// } break;
// case command::append:
// {
// std::vector<node> expected
// std::vector<node_type> expected
// { {resp3::type::number, 1UL, 0UL, {"4"}} };
// check_equal(resp, expected, "append");
// expect_eq(resp, expected, "append");
// } break;
// case command::hset:
// {
// std::vector<node> expected
// std::vector<node_type> expected
// { {resp3::type::number, 1UL, 0UL, {"2"}} };
// check_equal(resp, expected, "hset");
// expect_eq(resp, expected, "hset");
// } break;
// case command::del:
// {
// std::vector<node> expected
// std::vector<node_type> expected
// { {resp3::type::number, 1UL, 0UL, {"1"}} };
// check_equal(resp, expected, "del");
// expect_eq(resp, expected, "del");
// } break;
// case command::incr:
// {
// std::vector<node> expected
// std::vector<node_type> expected
// { {resp3::type::number, 1UL, 0UL, {"1"}} };
// check_equal(resp, expected, "incr");
// expect_eq(resp, expected, "incr");
// } break;
// case command::publish:
// {
// std::vector<node> expected
// std::vector<node_type> expected
// { {resp3::type::number, 1UL, 0UL, {"1"}} };
// check_equal(resp, expected, "publish");
// expect_eq(resp, expected, "publish");
// } break;
// case command::hincrby:
// {
// std::vector<node> expected
// std::vector<node_type> expected
// { {resp3::type::number, 1UL, 0UL, {"10"}} };
// check_equal(resp, expected, "hincrby");
// expect_eq(resp, expected, "hincrby");
// } break;
// case command::zadd:
// {
// std::vector<node> expected
// std::vector<node_type> expected
// { {resp3::type::number, 1UL, 0UL, {"1"}} };
// check_equal(resp, expected, "zadd");
// expect_eq(resp, expected, "zadd");
// } break;
// case command::sadd:
// {
// std::vector<node> expected
// std::vector<node_type> expected
// { {resp3::type::number, 1UL, 0UL, {"3"}} };
// check_equal(resp, expected, "sadd");
// expect_eq(resp, expected, "sadd");
// } break;
// case command::hdel:
// {
// std::vector<node> expected
// std::vector<node_type> expected
// { {resp3::type::number, 1UL, 0UL, {"2"}} };
// check_equal(resp, expected, "hdel");
// expect_eq(resp, expected, "hdel");
// } break;
// case command::zremrangebyscore:
// {
// std::vector<node> expected
// std::vector<node_type> expected
// { {resp3::type::number, 1UL, 0UL, {"1"}} };
// check_equal(resp, expected, "zremrangebyscore");
// expect_eq(resp, expected, "zremrangebyscore");
// } break;
// case command::get:
// {
// std::vector<node> expected
// std::vector<node_type> expected
// { {resp3::type::blob_string, 1UL, 0UL, test.set_} };
// check_equal(resp, expected, "get");
// expect_eq(resp, expected, "get");
// } break;
// case command::hget:
// {
// std::vector<node> expected
// std::vector<node_type> expected
// { {resp3::type::blob_string, 1UL, 0UL, std::string{"value2"}} };
// check_equal(resp, expected, "hget");
// expect_eq(resp, expected, "hget");
// } break;
// case command::hvals:
// {
// std::vector<node> expected
// std::vector<node_type> expected
// { {resp3::type::array, 2UL, 0UL, {}}
// , {resp3::type::array, 1UL, 1UL, {"value1"}}
// , {resp3::type::array, 1UL, 1UL, {"value2"}}
// };
// check_equal(resp, expected, "hvals");
// expect_eq(resp, expected, "hvals");
// } break;
// case command::zrange:
// {
// std::vector<node> expected
// std::vector<node_type> expected
// { {resp3::type::array, 1UL, 0UL, {}}
// , {resp3::type::blob_string, 1UL, 1UL, {"Marcelo"}}
// };
// check_equal(resp, expected, "hvals");
// expect_eq(resp, expected, "hvals");
// } break;
// case command::zrangebyscore:
// {
// std::vector<node> expected
// std::vector<node_type> expected
// { {resp3::type::array, 1UL, 0UL, {}}
// , {resp3::type::blob_string, 1UL, 1UL, {"Marcelo"}}
// };
// check_equal(resp, expected, "zrangebyscore");
// expect_eq(resp, expected, "zrangebyscore");
// } break;
// case command::exec:
// {
// std::vector<node> expected
// std::vector<node_type> expected
// { {resp3::type::array, 3UL, 0UL, {}}
// , {resp3::type::simple_string, 1UL, 1UL, {"PONG"}}
// , {resp3::type::array, 2UL, 1UL, {}}
@@ -354,29 +358,29 @@ test_general(net::ip::tcp::resolver::results_type const& res)
// , {resp3::type::simple_string, 1UL, 1UL, {"PONG"}}
// };
// check_equal(resp, expected, "transaction");
// expect_eq(resp, expected, "transaction");
// } break;
// case command::hgetall:
// {
// std::vector<node> expected
// std::vector<node_type> expected
// { {resp3::type::map, 2UL, 0UL, {}}
// , {resp3::type::blob_string, 1UL, 1UL, {"field1"}}
// , {resp3::type::blob_string, 1UL, 1UL, {"value1"}}
// , {resp3::type::blob_string, 1UL, 1UL, {"field2"}}
// , {resp3::type::blob_string, 1UL, 1UL, {"value2"}}
// };
// check_equal(resp, expected, "hgetall (value)");
// expect_eq(resp, expected, "hgetall (value)");
// } break;
// case command::smembers:
// {
// std::vector<node> expected
// std::vector<node_type> expected
// { {resp3::type::set, 3UL, 0UL, {}}
// , {resp3::type::blob_string, 1UL, 1UL, {"1"}}
// , {resp3::type::blob_string, 1UL, 1UL, {"2"}}
// , {resp3::type::blob_string, 1UL, 1UL, {"3"}}
// };
// check_equal(resp, expected, "smembers (value)");
// expect_eq(resp, expected, "smembers (value)");
// } break;
// default: { std::cout << "Error: " << resp.front().data_type << " " << cmd << std::endl; }
// }
@@ -387,194 +391,12 @@ test_general(net::ip::tcp::resolver::results_type const& res)
//-------------------------------------------------------------------
//net::awaitable<void>
//test_list(net::ip::tcp::resolver::results_type const& results)
//{
// std::vector<int> list {1 ,2, 3, 4, 5, 6};
//
// resp3::serializer p;
// p.push(command::hello, 3);
// p.push(command::flushall);
// p.push_range(command::rpush, "a", std::cbegin(list), std::cend(list));
// p.push(command::lrange, "a", 0, -1);
// p.push(command::lrange, "a", 2, -2);
// p.push(command::ltrim, "a", 2, -2);
// p.push(command::lpop, "a");
// p.push(command::quit);
//
// auto ex = co_await this_coro::executor;
// tcp_socket socket {ex};
// co_await async_connect(socket, results);
// co_await async_write(socket, net::buffer(p.payload));
// std::string buf;
//
// { // hello
// gresp.clear();
// co_await async_read(socket, buf, gresp);
// }
//
// { // flushall
// gresp.clear();
// co_await async_read(socket, buf, gresp);
// std::vector<node> expected
// { {resp3::type::simple_string, 1UL, 0UL, {"OK"}} };
// check_equal(gresp, expected, "flushall");
// }
//
// { // rpush
// gresp.clear();
// std::vector<node> expected
// { {resp3::type::number, 1UL, 0UL, {"6"}} };
// co_await async_read(socket, buf, gresp);
// check_equal(gresp, expected, "rpush");
// }
//
// { // lrange
// resp3::flat_array_int_type buffer;
// resp3::detail::basic_flat_array_adapter<int> res{&buffer};
// co_await async_read(socket, buf, res);
// check_equal(buffer, list, "lrange-1");
// }
//
// { // lrange
// resp3::flat_array_int_type buffer;
// resp3::detail::basic_flat_array_adapter<int> res{&buffer};
// co_await async_read(socket, buf, res);
// check_equal(buffer, std::vector<int>{3, 4, 5}, "lrange-2");
// }
//
// { // ltrim
// gresp.clear();
// std::vector<node> expected
// { {resp3::type::simple_string, 1UL, 0UL, {"OK"}} };
//
// co_await async_read(socket, buf, gresp);
// check_equal(gresp, expected, "ltrim");
// }
//
// { // lpop. Why a blob string instead of a number?
// gresp.clear();
// std::vector<node> expected
// { {resp3::type::blob_string, 1UL, 0UL, {"3"}} };
//
// co_await async_read(socket, buf, gresp);
// check_equal(gresp, expected, "lpop");
// }
//
// { // quit
// gresp.clear();
// co_await async_read(socket, buf, gresp);
// std::vector<node> expected
// { {resp3::type::simple_string, 1UL, 0UL, {"OK"}} };
// check_equal(gresp, expected, "ltrim");
// }
//}
std::string test_bulk1(10000, 'a');
net::awaitable<void>
test_set(net::ip::tcp::resolver::results_type const& results)
{
using namespace aedis;
// Tests whether the parser can handle payloads that contain the separator.
test_bulk1[30] = '\r';
test_bulk1[31] = '\n';
std::string test_bulk2 = "aaaaa";
std::string request;
auto sr = make_serializer(request);
sr.push(command::hello, 3);
sr.push(command::flushall);
sr.push(command::set, "s", test_bulk1);
sr.push(command::get, "s");
sr.push(command::set, "s", test_bulk2);
sr.push(command::get, "s");
sr.push(command::set, "s", "");
sr.push(command::get, "s");
sr.push(command::quit);
auto ex = co_await this_coro::executor;
tcp_socket socket {ex};
co_await async_connect(socket, results);
co_await net::async_write(socket, net::buffer(request));
std::string buf;
{ // hello, flushall
gresp.clear();
co_await resp3::async_read(socket, net::dynamic_buffer(buf), adapt(gresp));
co_await resp3::async_read(socket, net::dynamic_buffer(buf), adapt(gresp));
}
{ // set
gresp.clear();
co_await resp3::async_read(socket, net::dynamic_buffer(buf), adapt(gresp));
std::vector<node> expected
{ {resp3::type::simple_string, 1UL, 0UL, {"OK"}} };
check_equal(gresp, expected, "set1");
}
{ // get
gresp.clear();
std::vector<node> expected
{ {resp3::type::blob_string, 1UL, 0UL, test_bulk1} };
co_await resp3::async_read(socket, net::dynamic_buffer(buf), adapt(gresp));
check_equal(gresp, expected, "get1");
}
{ // set
gresp.clear();
co_await resp3::async_read(socket, net::dynamic_buffer(buf), adapt(gresp));
std::vector<node> expected
{ {resp3::type::simple_string, 1UL, 0UL, {"OK"}} };
check_equal(gresp, expected, "ltrim");
}
{ // get
gresp.clear();
std::vector<node> expected
{ {resp3::type::blob_string, 1UL, 0UL, test_bulk2} };
co_await resp3::async_read(socket, net::dynamic_buffer(buf), adapt(gresp));
check_equal(gresp, expected, "get2");
}
{ // set
gresp.clear();
co_await resp3::async_read(socket, net::dynamic_buffer(buf), adapt(gresp));
std::vector<node> expected
{ {resp3::type::simple_string, 1UL, 0UL, {"OK"}} };
check_equal(gresp, expected, "set3");
}
{ // get
gresp.clear();
std::vector<node> expected
{ {resp3::type::blob_string, 1UL, 0UL, {}} };
co_await resp3::async_read(socket, net::dynamic_buffer(buf), adapt(gresp));
check_equal(gresp, expected, "get3");
}
{ // quit
gresp.clear();
co_await resp3::async_read(socket, net::dynamic_buffer(buf), adapt(gresp));
std::vector<node> expected
{ {resp3::type::simple_string, 1UL, 0UL, {"OK"}} };
check_equal(gresp, expected, "quit");
}
}
int main()
{
net::io_context ioc {1};
tcp::resolver resv(ioc);
auto const res = resv.resolve("127.0.0.1", "6379");
//co_spawn(ioc, test_list(res), net::detached);
co_spawn(ioc, test_set(res), net::detached);
co_spawn(ioc, test_general(res), net::detached);
ioc.run();
}

View File

@@ -1,110 +0,0 @@
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres at gmail dot com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include <aedis/aedis.hpp>
namespace aedis {
struct initiate_async_receive {
using executor_type = aedis::net::system_executor;
std::string::const_iterator pbegin;
std::string::const_iterator pend;
executor_type get_executor() noexcept
{ return aedis::net::system_executor(); }
template <
class ReadHandler,
class MutableBufferSequence>
void operator()(
ReadHandler&& handler,
MutableBufferSequence const& buffers)
{
boost::system::error_code ec;
if (std::size(buffers) == 0) {
handler(ec, 0);
return;
}
auto begin = boost::asio::buffer_sequence_begin(buffers);
auto end = boost::asio::buffer_sequence_end(buffers);
//std::cout << "Buffers size: " << std::size(buffers) << std::endl;
std::size_t transferred = 0;
while (begin != end) {
//std::cout << "Buffer size: " << std::ssize(*begin) << std::endl;
auto const min = std::min(std::ssize(*begin), pend - pbegin);
std::copy(pbegin, pbegin + min, static_cast<char*>(begin->data()));
std::advance(pbegin, min);
transferred += min;
++begin;
}
handler(ec, transferred);
}
};
template <class Executor>
struct test_stream {
std::string const payload;
using executor_type = Executor;
template<
class MutableBufferSequence,
class ReadHandler =
aedis::net::default_completion_token_t<executor_type>
>
auto async_read_some(
MutableBufferSequence const& buffers,
ReadHandler&& handler = net::default_completion_token_t<executor_type>{})
{
return aedis::net::async_initiate<ReadHandler,
void (boost::system::error_code, std::size_t)>(
initiate_async_receive
{std::cbegin(payload), std::cend(payload)},
handler, buffers);
}
template<class MutableBufferSequence>
std::size_t read_some(
MutableBufferSequence const& buffers,
boost::system::error_code&)
{
if (std::size(buffers) == 0)
return 0;
boost::system::error_code ec;
auto pbegin = std::cbegin(payload);
auto pend = std::cend(payload);
auto begin = boost::asio::buffer_sequence_begin(buffers);
auto end = boost::asio::buffer_sequence_end(buffers);
std::size_t transferred = 0;
while (begin != end) {
auto const min = std::min(std::ssize(*begin), pend - pbegin);
std::copy(pbegin, pbegin + min, static_cast<char*>(begin->data()));
std::advance(pbegin, min);
transferred += min;
++begin;
}
return transferred;
}
executor_type get_executor() noexcept
{ return aedis::net::system_executor(); }
template<class Executor1>
struct rebind_executor {
using other = test_stream<Executor1>;
};
};
}

View File

@@ -9,16 +9,18 @@
#include <algorithm>
#include <cctype>
#include <boost/asio/connect.hpp>
#include <aedis/aedis.hpp>
#include <aedis/src.hpp>
namespace net = boost::asio;
namespace resp3 = aedis::resp3;
using aedis::redis::command;
using aedis::redis::make_serializer;
using resp3::adapt;
using resp3::node;
namespace net = aedis::net;
using aedis::redis::command;
using aedis::generic::make_serializer;
using aedis::resp3::node;
using aedis::adapter::adapt;
using net::ip::tcp;
using net::write;
using net::buffer;
@@ -32,17 +34,17 @@ std::string toupper(std::string s)
}
std::vector<std::string>
get_cmd_names(std::vector<node> const& resp)
get_cmd_names(std::vector<node<std::string>> const& resp)
{
if (std::empty(resp)) {
if (resp.empty()) {
std::cerr << "Response is empty." << std::endl;
return {};
}
std::vector<std::string> ret;
for (auto i = 0ULL; i < std::size(resp); ++i) {
for (auto i = 0ULL; i < resp.size(); ++i) {
if (resp.at(i).depth == 1)
ret.push_back(resp.at(i + 1).data);
ret.push_back(resp.at(i + 1).value);
}
std::sort(std::begin(ret), std::end(ret));
@@ -58,7 +60,7 @@ void print_cmds_enum(std::vector<std::string> const& cmds)
<< " " << cmd << ",\n";
}
std::cout << " unknown\n};\n";
std::cout << " invalid\n};\n";
}
void print_cmds_strs(std::vector<std::string> const& cmds)
@@ -78,7 +80,7 @@ int main()
tcp::resolver resv{ioc};
auto const res = resv.resolve("127.0.0.1", "6379");
tcp::socket socket{ioc};
connect(socket, res);
net::connect(socket, res);
std::string request;
auto sr = make_serializer(request);
@@ -87,7 +89,7 @@ int main()
sr.push(command::quit);
write(socket, buffer(request));
std::vector<node> resp;
std::vector<node<std::string>> resp;
std::string buffer;
resp3::read(socket, dynamic_buffer(buffer));