2
0
mirror of https://github.com/boostorg/redis.git synced 2026-01-19 04:42:09 +00:00

Uses system::result to implement per request error handling.

This commit is contained in:
Marcelo Zimbres
2023-02-05 22:36:09 +01:00
parent 3a4445022e
commit a5c86107f8
36 changed files with 883 additions and 458 deletions

View File

@@ -275,6 +275,15 @@ if (MSVC)
target_compile_definitions(test_conn_exec_cancel PRIVATE _WIN32_WINNT=0x0601)
endif()
add_executable(test_conn_exec_error tests/conn_exec_error.cpp)
target_compile_features(test_conn_exec_error PUBLIC cxx_std_17)
target_link_libraries(test_conn_exec_error common)
add_test(test_conn_exec_error test_conn_exec_error)
if (MSVC)
target_compile_options(test_conn_exec_error PRIVATE /bigobj)
target_compile_definitions(test_conn_exec_error PRIVATE _WIN32_WINNT=0x0601)
endif()
add_executable(test_conn_echo_stress tests/conn_echo_stress.cpp)
target_compile_features(test_conn_echo_stress PUBLIC cxx_std_20)
target_link_libraries(test_conn_echo_stress common)

View File

@@ -870,6 +870,10 @@ Acknowledgement to people that helped shape Boost.Redis
* Adds new typedef `boost::redis::generic_response` that should be used instead of `std::vector<resp3::node<std::string>>`.
* Renames `redis::ignore` to `redis::ignore_t`.
* Changes the signature from `async_exec` to receive a `redis::response` instead of an adapter.
* Adds `boost::redis::adapter::result` to store responses to commands
including possible resp3 errors without losing the error diagnostic
part. Basicaly instead of accessing values as `std::get<N>(resp)`
users have to type `std::get<N>(resp).value()`
### v1.4.0-1

View File

@@ -14,6 +14,7 @@ namespace redis = boost::redis;
using redis::operation;
using redis::request;
using redis::response;
using redis::ignore_t;
void log(boost::system::error_code const& ec, char const* prefix)
{
@@ -38,7 +39,7 @@ auto main(int argc, char * argv[]) -> int
req.push("QUIT");
// The response.
response<redis::ignore_t, std::string, redis::ignore_t> resp;
response<ignore_t, std::string, ignore_t> resp;
net::io_context ioc;
@@ -64,7 +65,7 @@ auto main(int argc, char * argv[]) -> int
return log(ec, "on_exec: ");
}
std::cout << "PING: " << std::get<1>(resp) << std::endl;
std::cout << "PING: " << std::get<1>(resp).value() << std::endl;
};
// Connect callback.

View File

@@ -15,10 +15,10 @@
#include <boost/redis/src.hpp>
namespace net = boost::asio;
namespace redis = boost::redis;
using connection = redis::connection;
using connection = boost::redis::connection;
using boost::redis::request;
using boost::redis::response;
using boost::redis::ignore_t;
template <class Response>
auto exec(std::shared_ptr<connection> conn, request const& req, Response& resp)
@@ -66,12 +66,12 @@ auto main(int argc, char * argv[]) -> int
req.push("PING");
req.push("QUIT");
response<boost::redis::ignore_t, std::string, boost::redis::ignore_t> resp;
response<ignore_t, std::string, ignore_t> resp;
// Executes commands synchronously.
exec(conn, req, resp);
std::cout << "Response: " << std::get<1>(resp) << std::endl;
std::cout << "Response: " << std::get<1>(resp).value() << std::endl;
t.join();
} catch (std::exception const& e) {

View File

@@ -12,10 +12,10 @@
#include <boost/redis/src.hpp>
namespace net = boost::asio;
namespace redis = boost::redis;
namespace resp3 = redis::resp3;
using redis::adapter::adapt2;
namespace resp3 = boost::redis::resp3;
using boost::redis::adapter::adapt2;
using boost::redis::request;
using boost::redis::adapter::result;
auto main(int argc, char * argv[]) -> int
{
@@ -41,8 +41,8 @@ auto main(int argc, char * argv[]) -> int
req.push("QUIT");
resp3::write(socket, req);
// Responses
std::string buffer, resp;
std::string buffer;
result<std::string> resp;
// Reads the responses to all commands in the request.
auto dbuffer = net::dynamic_buffer(buffer);
@@ -50,7 +50,7 @@ auto main(int argc, char * argv[]) -> int
resp3::read(socket, dbuffer, adapt2(resp));
resp3::read(socket, dbuffer);
std::cout << "Ping: " << resp << std::endl;
std::cout << "Ping: " << resp.value() << std::endl;
} catch (std::exception const& e) {
std::cerr << e.what() << std::endl;

View File

@@ -29,8 +29,8 @@ auto receiver(std::shared_ptr<connection> conn) -> net::awaitable<void>
{
for (generic_response resp;;) {
co_await conn->async_receive(resp);
std::cout << resp.at(1).value << " " << resp.at(2).value << " " << resp.at(3).value << std::endl;
resp.clear();
std::cout << resp.value().at(1).value << " " << resp.value().at(2).value << " " << resp.value().at(3).value << std::endl;
resp.value().clear();
}
}

View File

@@ -18,6 +18,7 @@ namespace redis = boost::redis;
using namespace net::experimental::awaitable_operators;
using boost::redis::request;
using boost::redis::response;
using boost::redis::ignore_t;
void print(std::map<std::string, std::string> const& cont)
{
@@ -62,12 +63,12 @@ auto hgetall(std::shared_ptr<connection> conn) -> net::awaitable<void>
req.push("HGETALL", "hset-key");
// Responses as tuple elements.
response<redis::ignore_t, std::map<std::string, std::string>> resp;
response<ignore_t, std::map<std::string, std::string>> resp;
// Executes the request and reads the response.
co_await conn->async_exec(req, resp);
print(std::get<1>(resp));
print(std::get<1>(resp).value());
}
// Retrieves in a transaction.
@@ -81,17 +82,17 @@ auto transaction(std::shared_ptr<connection> conn) -> net::awaitable<void>
req.push("EXEC");
response<
redis::ignore_t, // hello
redis::ignore_t, // multi
redis::ignore_t, // lrange
redis::ignore_t, // hgetall
ignore_t, // hello
ignore_t, // multi
ignore_t, // lrange
ignore_t, // hgetall
response<std::optional<std::vector<int>>, std::optional<std::map<std::string, std::string>>> // exec
> resp;
co_await conn->async_exec(req, resp);
print(std::get<0>(std::get<4>(resp)).value());
print(std::get<1>(std::get<4>(resp)).value());
print(std::get<0>(std::get<4>(resp).value()).value().value());
print(std::get<1>(std::get<4>(resp).value()).value().value());
}
auto quit(std::shared_ptr<connection> conn) -> net::awaitable<void>

View File

@@ -27,8 +27,8 @@ auto echo_server_session(tcp_socket socket, std::shared_ptr<connection> conn) ->
auto n = co_await net::async_read_until(socket, net::dynamic_buffer(buffer, 1024), "\n");
req.push("PING", buffer);
co_await conn->async_exec(req, resp);
co_await net::async_write(socket, net::buffer(std::get<0>(resp)));
std::get<0>(resp).clear();
co_await net::async_write(socket, net::buffer(std::get<0>(resp).value()));
std::get<0>(resp).value().clear();
req.clear();
buffer.erase(0, n);
}

View File

@@ -36,7 +36,7 @@ auto ping(std::shared_ptr<connection> conn) -> net::awaitable<void>
response<std::string> resp;
co_await conn->async_exec(req, resp);
std::cout << "PING: " << std::get<0>(resp) << std::endl;
std::cout << "PING: " << std::get<0>(resp).value() << std::endl;
}
auto quit(std::shared_ptr<connection> conn) -> net::awaitable<void>

View File

@@ -14,22 +14,27 @@ namespace net = boost::asio;
using namespace net::experimental::awaitable_operators;
using boost::redis::request;
using boost::redis::response;
using boost::redis::ignore_t;
// Called from the main function (see main.cpp)
auto co_main(std::string host, std::string port) -> net::awaitable<void>
{
request req;
req.push("HELLO", 3);
req.push("PING", "Hello world");
req.push("QUIT");
try {
request req;
req.push("HELLO", 3);
req.push("PING", "Hello world");
req.push("QUIT");
response<boost::redis::ignore_t, std::string, boost::redis::ignore_t> resp;
response<ignore_t, std::string, ignore_t> resp;
auto conn = std::make_shared<connection>(co_await net::this_coro::executor);
co_await connect(conn, host, port);
co_await (conn->async_run() || conn->async_exec(req, resp));
auto conn = std::make_shared<connection>(co_await net::this_coro::executor);
co_await connect(conn, host, port);
co_await (conn->async_run() || conn->async_exec(req, resp));
std::cout << "PING: " << std::get<1>(resp) << std::endl;
std::cout << "PING: " << std::get<1>(resp).value() << std::endl;
} catch (std::exception const& e) {
std::cout << e.what() << std::endl;
}
}
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)

View File

@@ -23,6 +23,7 @@ using resolver = net::use_awaitable_t<>::as_default_on_t<net::ip::tcp::resolver>
using connection = net::use_awaitable_t<>::as_default_on_t<redis::ssl::connection>;
using boost::redis::request;
using boost::redis::response;
using boost::redis::ignore_t;
auto verify_certificate(bool, net::ssl::verify_context&) -> bool
{
@@ -37,7 +38,7 @@ net::awaitable<void> co_main(std::string, std::string)
req.push("PING");
req.push("QUIT");
response<redis::ignore_t, std::string, redis::ignore_t> resp;
response<ignore_t, std::string, ignore_t> resp;
// Resolve
auto ex = co_await net::this_coro::executor;
@@ -53,7 +54,7 @@ net::awaitable<void> co_main(std::string, std::string)
co_await conn.next_layer().async_handshake(net::ssl::stream_base::client);
co_await (conn.async_run() || conn.async_exec(req, resp));
std::cout << "Response: " << std::get<1>(resp) << std::endl;
std::cout << "Response: " << std::get<1>(resp).value() << std::endl;
}
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)

View File

@@ -17,6 +17,7 @@ using tcp_socket = net::use_awaitable_t<>::as_default_on_t<net::ip::tcp::socket>
using boost::redis::adapter::adapt2;
using net::ip::tcp;
using boost::redis::request;
using boost::redis::adapter::result;
auto co_main(std::string host, std::string port) -> net::awaitable<void>
{
@@ -35,7 +36,8 @@ auto co_main(std::string host, std::string port) -> net::awaitable<void>
co_await resp3::async_write(socket, req);
// Responses
std::string buffer, resp;
std::string buffer;
result<std::string> resp;
// Reads the responses to all commands in the request.
auto dbuffer = net::dynamic_buffer(buffer);
@@ -43,7 +45,7 @@ auto co_main(std::string host, std::string port) -> net::awaitable<void>
co_await resp3::async_read(socket, dbuffer, adapt2(resp));
co_await resp3::async_read(socket, dbuffer);
std::cout << "Ping: " << resp << std::endl;
std::cout << "Ping: " << resp.value() << std::endl;
}
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)

View File

@@ -16,6 +16,7 @@ using namespace net::experimental::awaitable_operators;
using endpoints = net::ip::tcp::resolver::results_type;
using boost::redis::request;
using boost::redis::response;
using boost::redis::ignore_t;
auto redir(boost::system::error_code& ec)
{ return net::redirect_error(net::use_awaitable, ec); }
@@ -36,14 +37,14 @@ auto resolve_master_address(std::vector<address> const& endpoints) -> net::await
auto conn = std::make_shared<connection>(co_await net::this_coro::executor);
response<std::optional<std::array<std::string, 2>>, boost::redis::ignore_t> addr;
response<std::optional<std::array<std::string, 2>>, ignore_t> addr;
for (auto ep : endpoints) {
boost::system::error_code ec;
co_await connect(conn, ep.host, ep.port);
co_await (conn->async_run() && conn->async_exec(req, addr, redir(ec)));
conn->reset_stream();
if (std::get<0>(addr))
co_return address{std::get<0>(addr).value().at(0), std::get<0>(addr).value().at(1)};
co_return address{std::get<0>(addr).value().value().at(0), std::get<0>(addr).value().value().at(1)};
}
co_return address{};

View File

@@ -28,6 +28,7 @@ using namespace net::experimental::awaitable_operators;
using namespace boost::json;
using boost::redis::request;
using boost::redis::response;
using boost::redis::ignore_t;
struct user {
std::string name;
@@ -98,14 +99,14 @@ net::awaitable<void> co_main(std::string host, std::string port)
req.push("SMEMBERS", "sadd-key"); // Retrieves
req.push("QUIT");
response<redis::ignore_t, int, std::set<user>, std::string> resp;
response<ignore_t, int, std::set<user>, std::string> resp;
auto conn = std::make_shared<connection>(co_await net::this_coro::executor);
co_await connect(conn, host, port);
co_await (conn->async_run() || conn->async_exec(req, resp));
for (auto const& e: std::get<2>(resp))
for (auto const& e: std::get<2>(resp).value())
std::cout << e << "\n";
}

View File

@@ -38,8 +38,8 @@ auto receiver(std::shared_ptr<connection> conn) -> net::awaitable<void>
{
for (generic_response resp;;) {
co_await conn->async_receive(resp);
std::cout << resp.at(1).value << " " << resp.at(2).value << " " << resp.at(3).value << std::endl;
resp.clear();
std::cout << resp.value().at(1).value << " " << resp.value().at(2).value << " " << resp.value().at(3).value << std::endl;
resp.value().clear();
}
}

View File

@@ -9,39 +9,14 @@
#include <boost/redis/adapter/detail/response_traits.hpp>
namespace boost::redis::adapter {
namespace boost::redis::adapter
{
template <class T>
using adapter_t = typename detail::adapter_t<T>;
/** \brief Creates a dummy response adapter.
\ingroup low-level-api
The adapter returned by this function ignores responses. It is
useful to avoid wasting time with responses which are not needed.
Example:
@code
// Pushes and writes some commands to the server.
sr.push(command::hello, 3);
sr.push(command::ping);
sr.push(command::quit);
net::write(socket, net::buffer(request));
// Ignores all responses except for the response to ping.
std::string buffer;
resp3::read(socket, dynamic_buffer(buffer), adapt2()); // hello
resp3::read(socket, dynamic_buffer(buffer), adapt2(resp)); // ping
resp3::read(socket, dynamic_buffer(buffer, adapt2())); // quit
@endcode
*/
inline
auto adapt2() noexcept
{ return detail::response_traits<void>::adapt(); }
/** \brief Adapts user data to read operations.
* \ingroup low-level-api
/** @brief Adapts user data to read operations.
* @ingroup low-level-api
*
* STL containers, \c resp3::response and built-in types are supported and
* can be used in conjunction with \c std::optional<T>.
@@ -72,7 +47,7 @@ auto adapt2() noexcept
* @endcode
*/
template<class T>
auto adapt2(T& t) noexcept
auto adapt2(T& t = redis::ignore) noexcept
{ return detail::response_traits<T>::adapt(t); }
} // boost::redis::adapter

View File

@@ -12,6 +12,8 @@
#include <boost/redis/resp3/serialization.hpp>
#include <boost/redis/resp3/detail/parser.hpp>
#include <boost/redis/resp3/node.hpp>
#include <boost/redis/adapter/result.hpp>
#include <boost/redis/adapter/ignore.hpp>
#include <boost/assert.hpp>
#include <set>
@@ -28,7 +30,8 @@
#include <string_view>
#include <charconv>
namespace boost::redis::adapter::detail {
namespace boost::redis::adapter::detail
{
// Serialization.
@@ -37,7 +40,7 @@ auto boost_redis_from_bulk(T& i, std::string_view sv, system::error_code& ec) ->
{
auto const res = std::from_chars(sv.data(), sv.data() + std::size(sv), i);
if (res.ec != std::errc())
ec = error::not_a_number;
ec = redis::error::not_a_number;
}
inline
@@ -51,7 +54,7 @@ void boost_redis_from_bulk(double& d, std::string_view sv, system::error_code& e
{
auto const res = std::from_chars(sv.data(), sv.data() + std::size(sv), d);
if (res.ec != std::errc())
ec = error::not_a_double;
ec = redis::error::not_a_double;
}
template <class CharT, class Traits, class Allocator>
@@ -66,17 +69,6 @@ boost_redis_from_bulk(
//================================================
inline
void set_on_resp3_error(resp3::type t, system::error_code& ec)
{
switch (t) {
case resp3::type::simple_error: ec = error::resp3_simple_error; return;
case resp3::type::blob_error: ec = error::resp3_blob_error; return;
case resp3::type::null: ec = error::resp3_null; return;
default: return;
}
}
template <class Result>
class general_aggregate {
private:
@@ -84,9 +76,17 @@ private:
public:
explicit general_aggregate(Result* c = nullptr): result_(c) {}
void operator()(resp3::node<std::string_view> const& n, system::error_code&)
void operator()(resp3::node<std::string_view> const& nd, system::error_code&)
{
result_->push_back({n.data_type, n.aggregate_size, n.depth, std::string{std::cbegin(n.value), std::cend(n.value)}});
BOOST_ASSERT_MSG(!!result_, "Unexpected null pointer");
switch (nd.data_type) {
case resp3::type::blob_error:
case resp3::type::simple_error:
*result_ = error{nd.data_type, std::string{std::cbegin(nd.value), std::cend(nd.value)}};
break;
default:
result_->value().push_back({nd.data_type, nd.aggregate_size, nd.depth, std::string{std::cbegin(nd.value), std::cend(nd.value)}});
}
}
};
@@ -98,13 +98,20 @@ private:
public:
explicit general_simple(Node* t = nullptr) : result_(t) {}
void operator()(resp3::node<std::string_view> const& n, system::error_code& ec)
void operator()(resp3::node<std::string_view> const& nd, 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());
set_on_resp3_error(n.data_type, ec);
BOOST_ASSERT_MSG(!!result_, "Unexpected null pointer");
switch (nd.data_type) {
case resp3::type::blob_error:
case resp3::type::simple_error:
*result_ = error{nd.data_type, std::string{std::cbegin(nd.value), std::cend(nd.value)}};
break;
default:
result_->value().data_type = nd.data_type;
result_->value().aggregate_size = nd.aggregate_size;
result_->value().depth = nd.depth;
result_->value().value.assign(nd.value.data(), nd.value.size());
}
}
};
@@ -119,12 +126,8 @@ public:
resp3::node<std::string_view> const& n,
system::error_code& ec)
{
set_on_resp3_error(n.data_type, ec);
if (ec)
return;
if (is_aggregate(n.data_type)) {
ec = error::expects_resp3_simple_type;
ec = redis::error::expects_resp3_simple_type;
return;
}
@@ -147,20 +150,16 @@ public:
resp3::node<std::string_view> const& nd,
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_resp3_set;
ec = redis::error::expects_resp3_set;
return;
}
BOOST_ASSERT(nd.aggregate_size == 1);
if (nd.depth < 1) {
ec = error::expects_resp3_set;
ec = redis::error::expects_resp3_set;
return;
}
@@ -186,20 +185,16 @@ public:
resp3::node<std::string_view> const& nd,
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_resp3_map;
ec = redis::error::expects_resp3_map;
return;
}
BOOST_ASSERT(nd.aggregate_size == 1);
if (nd.depth < 1) {
ec = error::expects_resp3_map;
ec = redis::error::expects_resp3_map;
return;
}
@@ -228,10 +223,6 @@ public:
resp3::node<std::string_view> const& nd,
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);
@@ -256,23 +247,19 @@ public:
resp3::node<std::string_view> const& nd,
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 = error::nested_aggregate_not_supported;
ec = redis::error::nested_aggregate_not_supported;
return;
}
if (result.size() != nd.aggregate_size * element_multiplicity(nd.data_type)) {
ec = error::incompatible_size;
ec = redis::error::incompatible_size;
return;
}
} else {
if (i_ == -1) {
ec = error::expects_resp3_aggregate;
ec = redis::error::expects_resp3_aggregate;
return;
}
@@ -295,14 +282,10 @@ struct list_impl {
resp3::node<std::string_view> const& nd,
system::error_code& ec)
{
set_on_resp3_error(nd.data_type, ec);
if (ec)
return;
if (!is_aggregate(nd.data_type)) {
BOOST_ASSERT(nd.aggregate_size == 1);
if (nd.depth < 1) {
ec = error::expects_resp3_aggregate;
ec = redis::error::expects_resp3_aggregate;
return;
}
@@ -355,49 +338,103 @@ struct impl_map<std::deque<T, Allocator>> { using type = list_impl<std::deque<T,
//---------------------------------------------------
template <class>
class wrapper;
template <class Result>
class wrapper {
class wrapper<result<Result>> {
public:
using response_type = result<Result>;
private:
Result* result_;
response_type* result_;
typename impl_map<Result>::type impl_;
bool set_if_resp3_error(resp3::node<std::string_view> const& nd) noexcept
{
switch (nd.data_type) {
case resp3::type::null:
case resp3::type::simple_error:
case resp3::type::blob_error:
*result_ = error{nd.data_type, {std::cbegin(nd.value), std::cend(nd.value)}};
return true;
default:
return false;
}
}
public:
explicit wrapper(Result* t = nullptr) : result_(t)
{ impl_.on_value_available(*result_); }
explicit wrapper(response_type* t = nullptr) : result_(t)
{
if (result_) {
result_->value() = Result{};
impl_.on_value_available(result_->value());
}
}
void
operator()(
resp3::node<std::string_view> const& nd,
system::error_code& ec)
{
BOOST_ASSERT_MSG(!!result_, "Unexpected null pointer");
if (result_->has_error())
return;
if (set_if_resp3_error(nd))
return;
BOOST_ASSERT(result_);
impl_(*result_, nd, ec);
impl_(result_->value(), nd, ec);
}
};
template <class T>
class wrapper<std::optional<T>> {
class wrapper<result<std::optional<T>>> {
public:
using response_type = result<std::optional<T>>;
private:
std::optional<T>* result_;
response_type* result_;
typename impl_map<T>::type impl_{};
bool set_if_resp3_error(resp3::node<std::string_view> const& nd) noexcept
{
switch (nd.data_type) {
case resp3::type::blob_error:
case resp3::type::simple_error:
*result_ = error{nd.data_type, {std::cbegin(nd.value), std::cend(nd.value)}};
return true;
default:
return false;
}
}
public:
explicit wrapper(std::optional<T>* o = nullptr) : result_(o) {}
explicit wrapper(response_type* o = nullptr) : result_(o) {}
void
operator()(
resp3::node<std::string_view> const& nd,
system::error_code& ec)
{
BOOST_ASSERT_MSG(!!result_, "Unexpected null pointer");
if (result_->has_error())
return;
if (set_if_resp3_error(nd))
return;
if (nd.data_type == resp3::type::null)
return;
if (!result_->has_value()) {
*result_ = T{};
impl_.on_value_available(result_->value());
if (!result_->value().has_value()) {
result_->value() = T{};
impl_.on_value_available(result_->value().value());
}
impl_(result_->value(), nd, ec);
impl_(result_->value().value(), nd, ec);
}
};

View File

@@ -9,8 +9,9 @@
#include <boost/redis/error.hpp>
#include <boost/redis/resp3/type.hpp>
#include <boost/redis/resp3/read.hpp>
#include <boost/redis/ignore.hpp>
#include <boost/redis/adapter/detail/adapters.hpp>
#include <boost/redis/adapter/result.hpp>
#include <boost/mp11.hpp>
#include <vector>
@@ -18,53 +19,51 @@
#include <string_view>
#include <variant>
namespace boost::redis::adapter::detail {
using ignore_t = std::decay_t<decltype(std::ignore)>;
namespace boost::redis::adapter::detail
{
/* Traits class for response objects.
*
* Provides traits for all supported response types i.e. all STL
* containers and C++ buil-in types.
*/
template <class ResponseType>
template <class Response>
struct response_traits {
using adapter_type = adapter::detail::wrapper<typename std::decay<ResponseType>::type>;
static auto adapt(ResponseType& r) noexcept { return adapter_type{&r}; }
using adapter_type = adapter::detail::wrapper<typename std::decay<Response>::type>;
static auto adapt(Response& r) noexcept { return adapter_type{&r}; }
};
template <>
struct response_traits<result<ignore_t>> {
using response_type = result<ignore_t>;
using adapter_type = ignore;
static auto adapt(response_type) noexcept { return adapter_type{}; }
};
template <>
struct response_traits<ignore_t> {
using response_type = ignore_t;
using adapter_type = resp3::detail::ignore_response;
using adapter_type = ignore;
static auto adapt(response_type) noexcept { return adapter_type{}; }
};
template <class T>
struct response_traits<resp3::node<T>> {
using response_type = resp3::node<T>;
struct response_traits<result<resp3::node<T>>> {
using response_type = result<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>;
struct response_traits<result<std::vector<resp3::node<String>, Allocator>>> {
using response_type = result<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{}; }
};
template <class T>
using adapter_t = typename response_traits<std::decay_t<T>>::adapter_type;
// Duplicated here to avoid circular include dependency.
template<class T>
auto internal_adapt(T& t) noexcept
{ return response_traits<std::decay_t<T>>::adapt(t); }
@@ -89,7 +88,10 @@ struct assigner<0> {
};
template <class Tuple>
class static_aggregate_adapter {
class static_aggregate_adapter;
template <class Tuple>
class static_aggregate_adapter<result<Tuple>> {
private:
using adapters_array_type =
std::array<
@@ -102,11 +104,15 @@ private:
std::size_t i_ = 0;
std::size_t aggregate_size_ = 0;
adapters_array_type adapters_;
result<Tuple>* res_ = nullptr;
public:
explicit static_aggregate_adapter(Tuple* r = nullptr)
explicit static_aggregate_adapter(result<Tuple>* r = nullptr)
{
detail::assigner<std::tuple_size<Tuple>::value - 1>::assign(adapters_, *r);
if (r) {
res_ = r;
detail::assigner<std::tuple_size<Tuple>::value - 1>::assign(adapters_, r->value());
}
}
void count(resp3::node<std::string_view> const& nd)
@@ -124,17 +130,14 @@ public:
++i_;
}
void
operator()(
resp3::node<std::string_view> const& nd,
system::error_code& ec)
void operator()(resp3::node<std::string_view> const& nd, system::error_code& ec)
{
using std::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;
ec = redis::error::incompatible_size;
return;
}
@@ -145,9 +148,9 @@ public:
};
template <class... Ts>
struct response_traits<std::tuple<Ts...>>
struct response_traits<result<std::tuple<Ts...>>>
{
using response_type = std::tuple<Ts...>;
using response_type = result<std::tuple<Ts...>>;
using adapter_type = static_aggregate_adapter<response_type>;
static auto adapt(response_type& r) noexcept { return adapter_type{&r}; }
};

View File

@@ -0,0 +1,31 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#ifndef BOOST_REDIS_ADAPTER_IGNORE_HPP
#define BOOST_REDIS_ADAPTER_IGNORE_HPP
#include <boost/redis/resp3/node.hpp>
#include <boost/system/error_code.hpp>
#include <string>
namespace boost::redis::adapter
{
struct ignore {
void operator()(resp3::node<std::string_view> const& nd, system::error_code& ec)
{
switch (nd.data_type) {
case resp3::type::simple_error: ec = redis::error::resp3_simple_error; break;
case resp3::type::blob_error: ec = redis::error::resp3_blob_error; break;
case resp3::type::null: ec = redis::error::resp3_null; break;
default:;
}
}
};
} // boost::redis::adapter
#endif // BOOST_REDIS_ADAPTER_IGNORE_HPP

View File

@@ -0,0 +1,80 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#ifndef BOOST_REDIS_ADAPTER_RESULT_HPP
#define BOOST_REDIS_ADAPTER_RESULT_HPP
#include <boost/redis/resp3/type.hpp>
#include <boost/system/result.hpp>
#include <string>
namespace boost::redis::adapter
{
/** @brief Stores any resp3 error
* @ingroup high-level-api
*/
struct error {
/// RESP3 error data type.
resp3::type data_type = resp3::type::invalid;
/// Diagnostic error message sent by Redis.
std::string diagnostic;
};
/** @brief Compares two error objects for equality
* @relates error
*
* @param a Left hand side error object.
* @param b Right hand side error object.
*/
inline bool operator==(error const& a, error const& b)
{
return a.data_type == b.data_type && a.diagnostic == b.diagnostic;
}
/** @brief Compares two error objects for difference
* @relates error
*
* @param a Left hand side error object.
* @param b Right hand side error object.
*/
inline bool operator!=(error const& a, error const& b)
{
return !(a == b);
}
/** @brief Stores response to individual Redis commands
* @ingroup high-level-api
*/
template <class Value>
using result = system::result<Value, error>;
BOOST_NORETURN inline void
throw_exception_from_error(error const & e, boost::source_location const &)
{
system::error_code ec;
switch (e.data_type) {
case resp3::type::simple_error:
ec = redis::error::resp3_simple_error;
break;
case resp3::type::blob_error:
ec = redis::error::resp3_blob_error;
break;
case resp3::type::null:
ec = redis::error::resp3_null;
break;
default:
BOOST_ASSERT_MSG(false, "Unexpected data type.");
}
throw system::system_error(ec, e.diagnostic);
}
} // boost::redis::adapter
#endif // BOOST_REDIS_ADAPTER_RESULT_HPP

View File

@@ -25,26 +25,33 @@ namespace boost::redis::detail
class ignore_adapter {
public:
void
operator()(
std::size_t, resp3::node<std::string_view> const&, system::error_code&) { }
operator()(std::size_t, resp3::node<std::string_view> const& nd, system::error_code& ec)
{
switch (nd.data_type) {
case resp3::type::simple_error: ec = redis::error::resp3_simple_error; break;
case resp3::type::blob_error: ec = redis::error::resp3_blob_error; break;
case resp3::type::null: ec = redis::error::resp3_null; break;
default:;
}
}
[[nodiscard]]
auto get_supported_response_size() const noexcept
{ return static_cast<std::size_t>(-1);}
};
template <class Tuple>
template <class Response>
class static_adapter {
private:
static constexpr auto size = std::tuple_size<Tuple>::value;
using adapter_tuple = mp11::mp_transform<adapter::adapter_t, Tuple>;
static constexpr auto size = std::tuple_size<Response>::value;
using adapter_tuple = mp11::mp_transform<adapter::adapter_t, Response>;
using variant_type = mp11::mp_rename<adapter_tuple, std::variant>;
using adapters_array_type = std::array<variant_type, size>;
adapters_array_type adapters_;
public:
explicit static_adapter(Tuple& r)
explicit static_adapter(Response& r)
{
adapter::detail::assigner<size - 1>::assign(adapters_, r);
}
@@ -97,16 +104,25 @@ struct response_traits;
template <>
struct response_traits<ignore_t> {
using response_type = void;
using response_type = ignore_t;
using adapter_type = detail::ignore_adapter;
static auto adapt(ignore_t&) noexcept
static auto adapt(response_type&) noexcept
{ return detail::ignore_adapter{}; }
};
template <>
struct response_traits<adapter::result<ignore_t>> {
using response_type = adapter::result<ignore_t>;
using adapter_type = detail::ignore_adapter;
static auto adapt(response_type&) noexcept
{ return detail::ignore_adapter{}; }
};
template <class String, class Allocator>
struct response_traits<std::vector<resp3::node<String>, Allocator>> {
using response_type = std::vector<resp3::node<String>, Allocator>;
struct response_traits<adapter::result<std::vector<resp3::node<String>, Allocator>>> {
using response_type = adapter::result<std::vector<resp3::node<String>, Allocator>>;
using adapter_type = vector_adapter<response_type>;
static auto adapt(response_type& v) noexcept
@@ -159,7 +175,7 @@ auto make_adapter_wrapper(Adapter adapter)
template<class T>
auto boost_redis_adapt(T& t) noexcept
{
return detail::response_traits<T>::adapt(t);
return response_traits<T>::adapt(t);
}
} // boost::redis::detail

View File

@@ -0,0 +1,49 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#ifndef BOOST_REDIS_IGNORE_HPP
#define BOOST_REDIS_IGNORE_HPP
#include <boost/system/result.hpp>
#include <tuple>
#include <type_traits>
namespace boost::redis
{
/** @brief Type used to ignore responses.
* @ingroup high-level-api
*
* For example
*
* @code
* response<ignore_t, std::string, ignore_t> resp;
* @endcode
*
* will ignore the first and third responses. RESP3 errors won't be
* ignore but will cause `async_exec` to complete with an error.
*/
using ignore_t = std::decay_t<decltype(std::ignore)>;
/** @brief Global ignore object.
* @ingroup high-level-api
*
* Can be used to ignore responses to a request
*
* @code
* conn->async_exec(req, ignore, ...);
* @endcode
*
* RESP3 errors won't be ignore but will cause `async_exec` to
* complete with an error.
*/
extern ignore_t ignore;
} // boost::redis
#endif // BOOST_REDIS_IGNORE_HPP

View File

@@ -4,7 +4,7 @@
* accompanying file LICENSE.txt)
*/
#include <boost/redis/response.hpp>
#include <boost/redis/ignore.hpp>
namespace boost::redis
{

View File

@@ -276,11 +276,6 @@ private:
bool has_hello_priority_ = false;
};
/** @brief The response to a request.
*/
template <class... Ts>
using response = std::tuple<Ts...>;
} // boost::redis::resp3
#endif // BOOST_REDIS_REQUEST_HPP

View File

@@ -41,17 +41,6 @@ auto is_cancelled(T const& self)
namespace boost::redis::resp3::detail {
struct ignore_response {
void operator()(node<std::string_view> nd, system::error_code& ec)
{
switch (nd.data_type) {
case resp3::type::simple_error: ec = error::resp3_simple_error; return;
case resp3::type::blob_error: ec = error::resp3_blob_error; return;
default: return;
}
}
};
template <
class AsyncReadStream,
class DynamicBuffer,

View File

@@ -10,6 +10,7 @@
#include <boost/redis/resp3/type.hpp>
#include <boost/redis/resp3/detail/parser.hpp>
#include <boost/redis/resp3/detail/read_ops.hpp>
#include <boost/redis/adapter/ignore.hpp>
#include <boost/asio/read.hpp>
#include <boost/asio/compose.hpp>
#include <boost/asio/async_result.hpp>
@@ -47,8 +48,8 @@ namespace boost::redis::resp3 {
* the bytes after it returns.
*/
template <
class SyncReadStream,
class DynamicBuffer,
class SyncReadStream,
class DynamicBuffer,
class ResponseAdapter
>
auto
@@ -105,7 +106,7 @@ read(
template<
class SyncReadStream,
class DynamicBuffer,
class ResponseAdapter = detail::ignore_response>
class ResponseAdapter = adapter::ignore>
auto
read(
SyncReadStream& stream,
@@ -162,7 +163,7 @@ read(
template <
class AsyncReadStream,
class DynamicBuffer,
class ResponseAdapter = detail::ignore_response,
class ResponseAdapter = adapter::ignore,
class CompletionToken = asio::default_completion_token_t<typename AsyncReadStream::executor_type>
>
auto async_read(

View File

@@ -8,7 +8,7 @@
#define BOOST_REDIS_RESPONSE_HPP
#include <boost/redis/resp3/node.hpp>
#include <boost/redis/adapter/adapt.hpp>
#include <boost/redis/adapter/result.hpp>
#include <vector>
#include <string>
@@ -16,46 +16,21 @@
namespace boost::redis {
/** @brief The response to a request.
/** @brief Response with compile-time size.
* @ingroup high-level-api
*/
template <class... Ts>
using response = std::tuple<Ts...>;
using response = std::tuple<adapter::result<Ts>...>;
/** @brief A generic response to a request
* @ingroup high-level-api
*
* It contains the
* This response type can store any type of RESP3 data structure. It
* contains the
* [pre-order](https://en.wikipedia.org/wiki/Tree_traversal#Pre-order,_NLR)
* view of the response tree. Any Redis response can be received in
* an array of nodes.
* view of the response tree.
*/
using generic_response = std::vector<resp3::node<std::string>>;
/** @brief Type used to ignore responses.
* @ingroup high-level-api
*
* For example
*
* @code
* response<boost::redis::ignore_t, std::string, boost::redis::ignore_t> resp;
* @endcode
*
* will cause only the second tuple type to be parsed, the others
* will be ignored.
*/
using ignore_t = adapter::detail::ignore_t;
/** @brief Global ignore object.
* @ingroup high-level-api
*
* Can be used to ignore responses to a request
*
* @code
* conn->async_exec(req, ignore, ...);
* @endcode
*/
extern ignore_t ignore;
using generic_response = adapter::result<std::vector<resp3::node<std::string>>>;
} // boost::redis::resp3

View File

@@ -6,6 +6,6 @@
#include <boost/redis/impl/error.ipp>
#include <boost/redis/impl/request.ipp>
#include <boost/redis/impl/response.ipp>
#include <boost/redis/impl/ignore.ipp>
#include <boost/redis/resp3/impl/type.ipp>
#include <boost/redis/resp3/detail/impl/parser.ipp>

View File

@@ -21,6 +21,7 @@ using boost::redis::operation;
using boost::redis::request;
using boost::redis::response;
using boost::redis::ignore;
using boost::redis::ignore_t;
auto push_consumer(std::shared_ptr<connection> conn, int expected) -> net::awaitable<void>
{
@@ -42,7 +43,7 @@ auto echo_session(std::shared_ptr<connection> conn, std::string id, int n) -> ne
auto ex = co_await net::this_coro::executor;
request req;
response<boost::redis::ignore_t, std::string> resp;
response<ignore_t, std::string> resp;
for (auto i = 0; i < n; ++i) {
auto const msg = id + "/" + std::to_string(i);
@@ -53,9 +54,9 @@ auto echo_session(std::shared_ptr<connection> conn, std::string id, int n) -> ne
boost::system::error_code ec;
co_await conn->async_exec(req, resp, redir(ec));
BOOST_CHECK_EQUAL(ec, boost::system::error_code{});
BOOST_CHECK_EQUAL(msg, std::get<1>(resp));
BOOST_CHECK_EQUAL(msg, std::get<1>(resp).value());
req.clear();
std::get<1>(resp).clear();
std::get<1>(resp).value().clear();
}
}

View File

@@ -25,6 +25,7 @@ using connection = boost::redis::connection;
using boost::redis::request;
using boost::redis::response;
using boost::redis::ignore;
using boost::redis::ignore_t;
BOOST_AUTO_TEST_CASE(hello_priority)
{
@@ -91,7 +92,7 @@ BOOST_AUTO_TEST_CASE(wrong_response_data_type)
req.push("QUIT");
// Wrong data type.
response<boost::redis::ignore_t, int> resp;
response<ignore_t, int> resp;
net::io_context ioc;
auto const endpoints = resolve();

View File

@@ -27,16 +27,20 @@ using namespace net::experimental::awaitable_operators;
using boost::redis::operation;
using boost::redis::request;
using boost::redis::response;
using boost::redis::generic_response;
using boost::redis::ignore;
using boost::redis::ignore_t;
auto async_ignore_explicit_cancel_of_req_written() -> net::awaitable<void>
{
auto ex = co_await net::this_coro::executor;
generic_response gresp;
auto conn = std::make_shared<connection>(ex);
co_await connect(conn, "127.0.0.1", "6379");
conn->async_run([conn](auto ec) {
std::cout << "async_run: " << ec.message() << std::endl;
BOOST_TEST(!ec);
});
@@ -47,14 +51,15 @@ auto async_ignore_explicit_cancel_of_req_written() -> net::awaitable<void>
request req0;
req0.get_config().coalesce = false;
req0.push("HELLO", 3);
std::ignore = co_await conn->async_exec(req0, ignore, net::use_awaitable);
co_await conn->async_exec(req0, gresp, net::use_awaitable);
request req1;
req1.get_config().coalesce = false;
req1.push("BLPOP", "any", 3);
// Should not be canceled.
conn->async_exec(req1, ignore, [](auto ec, auto){
conn->async_exec(req1, gresp, [](auto ec, auto){
std::cout << "async_exec (1): " << ec.message() << std::endl;
BOOST_TEST(!ec);
});
@@ -63,7 +68,8 @@ auto async_ignore_explicit_cancel_of_req_written() -> net::awaitable<void>
req2.push("PING", "second");
// Should be canceled.
conn->async_exec(req2, ignore, [](auto ec, auto){
conn->async_exec(req2, gresp, [](auto ec, auto){
std::cout << "async_exec (2): " << ec.message() << std::endl;
BOOST_CHECK_EQUAL(ec, net::error::basic_errors::operation_aborted);
});
@@ -79,7 +85,7 @@ auto async_ignore_explicit_cancel_of_req_written() -> net::awaitable<void>
// Test whether the connection remains usable after a call to
// cancel(exec).
co_await conn->async_exec(req3, ignore, net::redirect_error(net::use_awaitable, ec1));
co_await conn->async_exec(req3, gresp, net::redirect_error(net::use_awaitable, ec1));
BOOST_TEST(!ec1);
}

269
tests/conn_exec_error.cpp Normal file
View File

@@ -0,0 +1,269 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#include <iostream>
#include <boost/asio.hpp>
#include <boost/system/errc.hpp>
#define BOOST_TEST_MODULE low level
#include <boost/test/included/unit_test.hpp>
#include <boost/redis.hpp>
#include <boost/redis/src.hpp>
#include "common.hpp"
namespace net = boost::asio;
namespace redis = boost::redis;
namespace resp3 = redis::resp3;
using error_code = boost::system::error_code;
using connection = boost::redis::connection;
using boost::redis::request;
using boost::redis::response;
using boost::redis::generic_response;
using boost::redis::ignore;
using boost::redis::ignore_t;
using boost::redis::error;
BOOST_AUTO_TEST_CASE(no_ignore_error)
{
request req;
// HELLO expects a number, by feeding a string we should get a simple error.
req.push("HELLO", "not-a-number");
net::io_context ioc;
auto const endpoints = resolve();
connection conn{ioc};
net::connect(conn.next_layer(), endpoints);
conn.async_exec(req, ignore, [&](auto ec, auto){
BOOST_CHECK_EQUAL(ec, error::resp3_simple_error);
conn.cancel(redis::operation::run);
});
conn.async_run([](auto ec){
BOOST_CHECK_EQUAL(ec, boost::asio::error::basic_errors::operation_aborted);
});
ioc.run();
}
BOOST_AUTO_TEST_CASE(has_diagnostic)
{
request req;
// HELLO expects a number, by feeding a string we should get a simple error.
req.push("HELLO", "not-a-number");
// The second command should be also executed. Notice PING does not
// require resp3.
req.push("PING", "Barra do Una");
net::io_context ioc;
auto const endpoints = resolve();
connection conn{ioc};
net::connect(conn.next_layer(), endpoints);
response<std::string, std::string> resp;
conn.async_exec(req, resp, [&](auto ec, auto){
BOOST_TEST(!ec);
// HELLO
BOOST_TEST(std::get<0>(resp).has_error());
BOOST_CHECK_EQUAL(std::get<0>(resp).error().data_type, resp3::type::simple_error);
auto const diag = std::get<0>(resp).error().diagnostic;
BOOST_TEST(!std::empty(diag));
std::cout << "has_diagnostic: " << diag << std::endl;
// PING
BOOST_TEST(std::get<1>(resp).has_value());
BOOST_CHECK_EQUAL(std::get<1>(resp).value(), "Barra do Una");
conn.cancel(redis::operation::run);
});
conn.async_run([](auto ec){
BOOST_CHECK_EQUAL(ec, boost::asio::error::basic_errors::operation_aborted);
});
ioc.run();
}
BOOST_AUTO_TEST_CASE(resp3_error_in_cmd_pipeline)
{
request req1;
req1.push("HELLO", "3");
req1.push("PING", "req1-msg1");
req1.push("PING", "req1-msg2", "extra arg"); // Error.
req1.push("PING", "req1-msg3"); // Should run ok.
response<ignore_t, std::string, std::string, std::string> resp1;
request req2;
req2.push("PING", "req2-msg1");
response<std::string> resp2;
net::io_context ioc;
auto const endpoints = resolve();
connection conn{ioc};
net::connect(conn.next_layer(), endpoints);
auto c2 = [&](auto ec, auto)
{
BOOST_TEST(!ec);
BOOST_TEST(std::get<0>(resp2).has_value());
BOOST_CHECK_EQUAL(std::get<0>(resp2).value(), "req2-msg1");
conn.cancel(redis::operation::run);
};
auto c1 = [&](auto ec, auto)
{
BOOST_TEST(!ec);
BOOST_TEST(std::get<2>(resp1).has_error());
BOOST_CHECK_EQUAL(std::get<2>(resp1).error().data_type, resp3::type::simple_error);
auto const diag = std::get<2>(resp1).error().diagnostic;
BOOST_TEST(!std::empty(diag));
std::cout << "resp3_error_in_cmd_pipeline: " << diag << std::endl;
BOOST_TEST(std::get<3>(resp1).has_value());
BOOST_CHECK_EQUAL(std::get<3>(resp1).value(), "req1-msg3");
conn.async_exec(req2, resp2, c2);
};
conn.async_exec(req1, resp1, c1);
conn.async_run([](auto ec){
BOOST_CHECK_EQUAL(ec, boost::asio::error::basic_errors::operation_aborted);
});
ioc.run();
}
BOOST_AUTO_TEST_CASE(error_in_transaction)
{
request req;
req.push("HELLO", 3);
req.push("MULTI");
req.push("PING");
req.push("PING", "msg2", "error"); // Error.
req.push("PING");
req.push("EXEC");
req.push("PING");
response<
ignore_t, // hello
ignore_t, // multi
ignore_t, // ping
ignore_t, // ping
ignore_t, // ping
response<std::string, std::string, std::string>, // exec
std::string // ping
> resp;
net::io_context ioc;
auto const endpoints = resolve();
connection conn{ioc};
net::connect(conn.next_layer(), endpoints);
conn.async_exec(req, resp, [&](auto ec, auto){
BOOST_TEST(!ec);
BOOST_TEST(std::get<0>(resp).has_value());
BOOST_TEST(std::get<1>(resp).has_value());
BOOST_TEST(std::get<2>(resp).has_value());
BOOST_TEST(std::get<3>(resp).has_value());
BOOST_TEST(std::get<4>(resp).has_value());
BOOST_TEST(std::get<5>(resp).has_value());
// Test errors in the pipeline commands.
BOOST_TEST(std::get<0>(std::get<5>(resp).value()).has_value());
BOOST_CHECK_EQUAL(std::get<0>(std::get<5>(resp).value()).value(), "PONG");
// The ping in the transaction that should be an error.
BOOST_TEST(std::get<1>(std::get<5>(resp).value()).has_error());
BOOST_CHECK_EQUAL(std::get<1>(std::get<5>(resp).value()).error().data_type, resp3::type::simple_error);
auto const diag = std::get<1>(std::get<5>(resp).value()).error().diagnostic;
BOOST_TEST(!std::empty(diag));
// The ping thereafter in the transaction should not be an error.
BOOST_TEST(std::get<2>(std::get<5>(resp).value()).has_value());
//BOOST_CHECK_EQUAL(std::get<2>(std::get<4>(resp).value()).value(), "PONG");
// The command right after the pipeline should be successful.
BOOST_TEST(std::get<6>(resp).has_value());
BOOST_CHECK_EQUAL(std::get<6>(resp).value(), "PONG");
conn.cancel(redis::operation::run);
});
conn.async_run([](auto ec){
BOOST_CHECK_EQUAL(ec, boost::asio::error::basic_errors::operation_aborted);
});
ioc.run();
}
// This test is important because a subscriber has no response on
// success, but on error, for example when using a wrong syntax, the
// server will send a simple error response the client is not
// expecting.
BOOST_AUTO_TEST_CASE(subscriber_wrong_syntax)
{
request req1;
req1.push("HELLO", 3);
request req2;
req2.push("SUBSCRIBE"); // Wrong command synthax.
net::io_context ioc;
auto const endpoints = resolve();
connection conn{ioc};
net::connect(conn.next_layer(), endpoints);
auto c2 = [&](auto ec, auto)
{
std::cout << "async_exec: subscribe" << std::endl;
BOOST_TEST(!ec);
};
auto c1 = [&](auto ec, auto)
{
std::cout << "async_exec: hello" << std::endl;
BOOST_TEST(!ec);
conn.async_exec(req2, ignore, c2);
};
conn.async_exec(req1, ignore, c1);
generic_response gresp;
auto c3 = [&](auto ec, auto)
{
std::cout << "async_receive" << std::endl;
BOOST_TEST(!ec);
BOOST_TEST(gresp.has_error());
BOOST_CHECK_EQUAL(gresp.error().data_type, resp3::type::simple_error);
BOOST_TEST(!std::empty(gresp.error().diagnostic));
std::cout << gresp.error().diagnostic << std::endl;
conn.cancel(redis::operation::run);
};
conn.async_receive(gresp, c3);
conn.async_run([](auto ec){
std::cout << "async_run" << std::endl;
BOOST_CHECK_EQUAL(ec, boost::asio::error::basic_errors::operation_aborted);
});
ioc.run();
}

View File

@@ -27,6 +27,7 @@ using net::experimental::as_tuple;
using boost::redis::request;
using boost::redis::response;
using boost::redis::ignore;
using boost::redis::ignore_t;
BOOST_AUTO_TEST_CASE(push_filtered_out)
{
@@ -41,7 +42,7 @@ BOOST_AUTO_TEST_CASE(push_filtered_out)
req.push("SUBSCRIBE", "channel");
req.push("QUIT");
response<boost::redis::ignore_t, std::string, std::string> resp;
response<ignore_t, std::string, std::string> resp;
conn.async_exec(req, resp, [](auto ec, auto){
BOOST_TEST(!ec);
});
@@ -56,31 +57,8 @@ BOOST_AUTO_TEST_CASE(push_filtered_out)
ioc.run();
BOOST_CHECK_EQUAL(std::get<1>(resp), "PONG");
BOOST_CHECK_EQUAL(std::get<2>(resp), "OK");
}
void receive_wrong_syntax(request const& req)
{
net::io_context ioc;
auto const endpoints = resolve();
connection conn{ioc};
net::connect(conn.next_layer(), endpoints);
conn.async_exec(req, ignore, [](auto ec, auto){
BOOST_TEST(!ec);
});
conn.async_run([](auto ec){
BOOST_CHECK_EQUAL(ec, boost::asio::error::basic_errors::operation_aborted);
});
conn.async_receive(ignore, [&](auto ec, auto){
BOOST_TEST(!ec);
conn.cancel(boost::redis::operation::run);
});
ioc.run();
BOOST_CHECK_EQUAL(std::get<1>(resp).value(), "PONG");
BOOST_CHECK_EQUAL(std::get<2>(resp).value(), "OK");
}
#ifdef BOOST_ASIO_HAS_CO_AWAIT
@@ -301,33 +279,6 @@ BOOST_AUTO_TEST_CASE(many_subscribers)
}
#endif
BOOST_AUTO_TEST_CASE(receive_wrong_syntax1)
{
request req1{{false}};
req1.push("HELLO", 3);
req1.push("PING", "Message");
req1.push("SUBSCRIBE"); // Wrong command synthax.
req1.get_config().coalesce = true;
receive_wrong_syntax(req1);
req1.get_config().coalesce = false;
receive_wrong_syntax(req1);
}
BOOST_AUTO_TEST_CASE(receice_wrong_syntay2)
{
request req2{{false}};
req2.push("HELLO", 3);
req2.push("SUBSCRIBE"); // Wrong command syntax.
req2.get_config().coalesce = true;
receive_wrong_syntax(req2);
req2.get_config().coalesce = false;
receive_wrong_syntax(req2);
}
#else
int main() {}
#endif

View File

@@ -21,6 +21,7 @@ namespace net = boost::asio;
using connection = boost::redis::ssl::connection;
using boost::redis::request;
using boost::redis::response;
using boost::redis::ignore_t;
struct endpoint {
std::string host;
@@ -43,8 +44,7 @@ BOOST_AUTO_TEST_CASE(ping)
req.push("PING", in);
req.push("QUIT");
std::string out;
auto resp = std::tie(std::ignore, out, std::ignore);
response<ignore_t, std::string, ignore_t> resp;
auto const endpoints = resolve("db.occase.de", "6380");
@@ -67,6 +67,6 @@ BOOST_AUTO_TEST_CASE(ping)
ioc.run();
BOOST_CHECK_EQUAL(in, out);
BOOST_CHECK_EQUAL(in, std::get<1>(resp).value());
}

View File

@@ -16,6 +16,7 @@ using namespace net::experimental::awaitable_operators;
using steady_timer = net::use_awaitable_t<>::as_default_on_t<net::steady_timer>;
using boost::redis::request;
using boost::redis::response;
using boost::redis::ignore;
// Push consumer
auto receiver(std::shared_ptr<connection> conn) -> net::awaitable<void>
@@ -36,12 +37,11 @@ auto periodic_task(std::shared_ptr<connection> conn) -> net::awaitable<void>
// that result in the connection being closed.
request req;
req.push("GET", "mykey");
response<std::string> response;
auto [ec, u] = co_await conn->async_exec(req, response, net::as_tuple(net::use_awaitable));
auto [ec, u] = co_await conn->async_exec(req, ignore, net::as_tuple(net::use_awaitable));
if (ec) {
std::cout << "Error: " << ec << std::endl;
} else {
std::cout << "Response is: " << std::get<0>(response) << std::endl;
std::cout << "no error: " << std::endl;
}
}

View File

@@ -34,43 +34,46 @@ namespace net = boost::asio;
namespace resp3 = boost::redis::resp3;
using boost::redis::request;
using boost::redis::response;
using boost::redis::ignore;
using boost::redis::ignore_t;
using boost::redis::adapter::result;
using test_stream = boost::beast::test::stream;
using boost::redis::adapter::adapt2;
using node_type = boost::redis::resp3::node<std::string>;
using vec_node_type = std::vector<node_type>;
using vec_type = std::vector<std::string>;
using op_vec_type = std::optional<std::vector<std::string>>;
using node_type = result<boost::redis::resp3::node<std::string>>;
using vec_node_type = result<std::vector<boost::redis::resp3::node<std::string>>>;
using vec_type = result<std::vector<std::string>>;
using op_vec_type = result<std::optional<std::vector<std::string>>>;
// Set
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 set_type = result<std::set<std::string>>;
using mset_type = result<std::multiset<std::string>>;
using uset_type = result<std::unordered_set<std::string>>;
using muset_type = result<std::unordered_multiset<std::string>>;
// Array
using tuple_int_2 = response<int, int>;
using array_type = std::array<int, 3>;
using array_type2 = std::array<int, 1>;
using tuple_int_2 = result<response<int, int>>;
using array_type = result<std::array<int, 3>>;
using array_type2 = result<std::array<int, 1>>;
// Map
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 op_map_type = std::optional<std::map<std::string, std::string>>;
using tuple8_type = response<std::string, std::string, std::string, std::string, std::string, std::string, std::string, std::string>;
using map_type = result<std::map<std::string, std::string>>;
using mmap_type = result<std::multimap<std::string, std::string>>;
using umap_type = result<std::unordered_map<std::string, std::string>>;
using mumap_type = result<std::unordered_multimap<std::string, std::string>>;
using op_map_type = result<std::optional<std::map<std::string, std::string>>>;
using tuple8_type = result<response<std::string, std::string, std::string, std::string, std::string, std::string, std::string, std::string>>;
// Null
using op_type_01 = std::optional<bool>;
using op_type_02 = std::optional<int>;
using op_type_03 = std::optional<std::string>;
using op_type_04 = std::optional<std::vector<std::string>>;
using op_type_05 = std::optional<std::list<std::string>>;
using op_type_06 = std::optional<std::map<std::string, std::string>>;
using op_type_07 = std::optional<std::unordered_map<std::string, std::string>>;
using op_type_08 = std::optional<std::set<std::string>>;
using op_type_09 = std::optional<std::unordered_set<std::string>>;
using op_type_01 = result<std::optional<bool>>;
using op_type_02 = result<std::optional<int>>;
using op_type_03 = result<std::optional<std::string>>;
using op_type_04 = result<std::optional<std::vector<std::string>>>;
using op_type_05 = result<std::optional<std::list<std::string>>>;
using op_type_06 = result<std::optional<std::map<std::string, std::string>>>;
using op_type_07 = result<std::optional<std::unordered_map<std::string, std::string>>>;
using op_type_08 = result<std::optional<std::set<std::string>>>;
using op_type_09 = result<std::optional<std::unordered_set<std::string>>>;
//-------------------------------------------------------------------
@@ -79,12 +82,13 @@ struct expect {
std::string in;
Result expected;
boost::system::error_code ec{};
resp3::type error_type = resp3::type::invalid;
};
template <class Result>
auto make_expected(std::string in, Result expected, boost::system::error_code ec = {})
auto make_expected(std::string in, Result expected, boost::system::error_code ec = {}, resp3::type error_type = resp3::type::invalid)
{
return expect<Result>{in, expected, ec};
return expect<Result>{in, expected, ec, error_type};
}
template <class Result>
@@ -96,12 +100,21 @@ void test_sync(net::any_io_executor ex, expect<Result> e)
Result result;
boost::system::error_code ec;
resp3::read(ts, net::dynamic_buffer(rbuffer), adapt2(result), ec);
BOOST_CHECK_EQUAL(ec, e.ec);
if (e.ec)
if (e.ec) {
BOOST_CHECK_EQUAL(ec, e.ec);
return;
}
BOOST_TEST(!ec);
BOOST_TEST(rbuffer.empty());
auto const res = result == e.expected;
BOOST_TEST(res);
if (result.has_value()) {
auto const res = result == e.expected;
BOOST_TEST(res);
} else {
BOOST_TEST(result.has_error());
BOOST_CHECK_EQUAL(result.error().data_type, e.error_type);
}
}
template <class Result>
@@ -125,12 +138,21 @@ public:
auto self = this->shared_from_this();
auto f = [self](auto ec, auto)
{
BOOST_CHECK_EQUAL(ec, self->data_.ec);
if (self->data_.ec)
if (self->data_.ec) {
BOOST_CHECK_EQUAL(ec, self->data_.ec);
return;
}
BOOST_TEST(!ec);
BOOST_TEST(self->rbuffer_.empty());
auto const res = self->result_ == self->data_.expected;
BOOST_TEST(res);
if (self->result_.has_value()) {
auto const res = self->result_ == self->data_.expected;
BOOST_TEST(res);
} else {
BOOST_TEST(self->result_.has_error());
BOOST_CHECK_EQUAL(self->result_.error().data_type, self->data_.error_type);
}
};
resp3::async_read(
@@ -171,65 +193,66 @@ auto make_blob_string(std::string const& b)
return wire;
}
std::optional<int> op_int_ok = 11;
std::optional<bool> op_bool_ok = true;
result<std::optional<int>> op_int_ok = 11;
result<std::optional<bool>> op_bool_ok = true;
// TODO: Test a streamed string that is not finished with a string of
// size 0 but other command comes in.
std::vector<node_type> streamed_string_e1
{ {boost::redis::resp3::type::streamed_string, 0, 1, ""}
vec_node_type streamed_string_e1
{{ {boost::redis::resp3::type::streamed_string, 0, 1, ""}
, {boost::redis::resp3::type::streamed_string_part, 1, 1, "Hell"}
, {boost::redis::resp3::type::streamed_string_part, 1, 1, "o wor"}
, {boost::redis::resp3::type::streamed_string_part, 1, 1, "d"}
, {boost::redis::resp3::type::streamed_string_part, 1, 1, ""}
};
}};
std::vector<node_type> streamed_string_e2 { {resp3::type::streamed_string, 0UL, 1UL, {}}, {resp3::type::streamed_string_part, 1UL, 1UL, {}} };
vec_node_type streamed_string_e2
{{{resp3::type::streamed_string, 0UL, 1UL, {}}, {resp3::type::streamed_string_part, 1UL, 1UL, {}} }};
std::vector<node_type> const push_e1a
{ {resp3::type::push, 4UL, 0UL, {}}
vec_node_type const push_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> const push_e1b
{ {resp3::type::push, 0UL, 0UL, {}} };
vec_node_type const push_e1b
{{{resp3::type::push, 0UL, 0UL, {}}}};
std::vector<node_type> const set_expected1a
{ {resp3::type::set, 6UL, 0UL, {}}
vec_node_type const set_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 set_e1f{"apple", "one", "orange", "orange", "three", "two"};
uset_type const set_e1c{"apple", "one", "orange", "three", "two"};
muset_type const set_e1g{"apple", "one", "orange", "orange", "three", "two"};
vec_type const set_e1d = {"orange", "apple", "one", "two", "three", "orange"};
mset_type const set_e1f{{"apple", "one", "orange", "orange", "three", "two"}};
uset_type const set_e1c{{"apple", "one", "orange", "three", "two"}};
muset_type const set_e1g{{"apple", "one", "orange", "orange", "three", "two"}};
vec_type const set_e1d = {{"orange", "apple", "one", "two", "three", "orange"}};
op_vec_type const set_expected_1e = set_e1d;
std::vector<node_type> const array_e1a
{ {resp3::type::array, 3UL, 0UL, {}}
vec_node_type const array_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 array_e1b{11, 22, 3};
std::vector<std::string> const array_e1c{"11", "22", "3"};
std::vector<std::string> const array_e1d{};
std::vector<node_type> const array_e1e{{resp3::type::array, 0UL, 0UL, {}}};
array_type const array_e1f{11, 22, 3};
std::list<int> const array_e1g{11, 22, 3};
std::deque<int> const array_e1h{11, 22, 3};
result<std::vector<int>> const array_e1b{{11, 22, 3}};
result<std::vector<std::string>> const array_e1c{{"11", "22", "3"}};
result<std::vector<std::string>> const array_e1d{};
vec_node_type const array_e1e{{{resp3::type::array, 0UL, 0UL, {}}}};
array_type const array_e1f{{11, 22, 3}};
result<std::list<int>> const array_e1g{{11, 22, 3}};
result<std::deque<int>> const array_e1h{{11, 22, 3}};
std::vector<node_type> const map_expected_1a
{ {resp3::type::map, 4UL, 0UL, {}}
vec_node_type const map_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"}}
@@ -238,40 +261,40 @@ std::vector<node_type> const map_expected_1a
, {resp3::type::blob_string, 1UL, 1UL, {"value3"}}
, {resp3::type::blob_string, 1UL, 1UL, {"key3"}}
, {resp3::type::blob_string, 1UL, 1UL, {"value3"}}
};
}};
map_type const map_expected_1b
{ {"key1", "value1"}
{{ {"key1", "value1"}
, {"key2", "value2"}
, {"key3", "value3"}
};
}};
umap_type const map_e1g
{ {"key1", "value1"}
{{ {"key1", "value1"}
, {"key2", "value2"}
, {"key3", "value3"}
};
}};
mmap_type const map_e1k
{ {"key1", "value1"}
{{ {"key1", "value1"}
, {"key2", "value2"}
, {"key3", "value3"}
, {"key3", "value3"}
};
}};
mumap_type const map_e1l
{ {"key1", "value1"}
{{ {"key1", "value1"}
, {"key2", "value2"}
, {"key3", "value3"}
, {"key3", "value3"}
};
}};
std::vector<std::string> const map_expected_1c
{ "key1", "value1"
result<std::vector<std::string>> const map_expected_1c
{{ "key1", "value1"
, "key2", "value2"
, "key3", "value3"
, "key3", "value3"
};
}};
op_map_type const map_expected_1d = map_expected_1b;
@@ -284,18 +307,18 @@ tuple8_type const map_e1f
, std::string{"key3"}, std::string{"value3"}
};
std::vector<node_type> const attr_e1a
{ {resp3::type::attribute, 1UL, 0UL, {}}
vec_node_type const attr_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> const attr_e1b
{ {resp3::type::attribute, 0UL, 0UL, {}} };
vec_node_type const attr_e1b
{{{resp3::type::attribute, 0UL, 0UL, {}} }};
#define S01a "#11\r\n"
#define S01b "#f\r\n"
@@ -370,38 +393,34 @@ std::vector<node_type> const attr_e1b
#define S18d "$0\r\n\r\n"
#define NUMBER_TEST_CONDITIONS(test) \
test(ex, make_expected(S01a, std::optional<bool>{}, boost::redis::error::unexpected_bool_value)); \
test(ex, make_expected(S01b, bool{false})); \
test(ex, make_expected(S01b, node_type{resp3::type::boolean, 1UL, 0UL, {"f"}})); \
test(ex, make_expected(S01c, bool{true})); \
test(ex, make_expected(S01c, node_type{resp3::type::boolean, 1UL, 0UL, {"t"}})); \
test(ex, make_expected(S01a, result<std::optional<bool>>{}, boost::redis::error::unexpected_bool_value)); \
test(ex, make_expected(S01b, result<bool>{{false}})); \
test(ex, make_expected(S01b, node_type{{resp3::type::boolean, 1UL, 0UL, {"f"}}})); \
test(ex, make_expected(S01c, result<bool>{{true}})); \
test(ex, make_expected(S01c, node_type{{resp3::type::boolean, 1UL, 0UL, {"t"}}})); \
test(ex, make_expected(S01c, op_bool_ok)); \
test(ex, make_expected(S01c, std::map<int, int>{}, boost::redis::error::expects_resp3_map)); \
test(ex, make_expected(S01c, std::set<int>{}, boost::redis::error::expects_resp3_set)); \
test(ex, make_expected(S01c, std::unordered_map<int, int>{}, boost::redis::error::expects_resp3_map)); \
test(ex, make_expected(S01c, std::unordered_set<int>{}, boost::redis::error::expects_resp3_set)); \
test(ex, make_expected(S01c, result<std::map<int, int>>{}, boost::redis::error::expects_resp3_map)); \
test(ex, make_expected(S01c, result<std::set<int>>{}, boost::redis::error::expects_resp3_set)); \
test(ex, make_expected(S01c, result<std::unordered_map<int, int>>{}, boost::redis::error::expects_resp3_map)); \
test(ex, make_expected(S01c, result<std::unordered_set<int>>{}, boost::redis::error::expects_resp3_set)); \
test(ex, make_expected(S02a, streamed_string_e2)); \
test(ex, make_expected(S03a, int{}, boost::redis::error::expects_resp3_simple_type));\
test(ex, make_expected(S03a, std::optional<int>{}, boost::redis::error::expects_resp3_simple_type));; \
test(ex, make_expected(S02b, int{}, boost::redis::error::not_a_number)); \
test(ex, make_expected(S02b, std::string{"Hello word"})); \
test(ex, make_expected(S03a, result<int>{}, boost::redis::error::expects_resp3_simple_type));\
test(ex, make_expected(S03a, result<std::optional<int>>{}, boost::redis::error::expects_resp3_simple_type));; \
test(ex, make_expected(S02b, result<int>{}, boost::redis::error::not_a_number)); \
test(ex, make_expected(S02b, result<std::string>{std::string{"Hello word"}})); \
test(ex, make_expected(S02b, streamed_string_e1)); \
test(ex, make_expected(S02c, std::string{}, boost::redis::error::not_a_number)); \
test(ex, make_expected(S04a, response<int>{11})); \
test(ex, make_expected(S05a, node_type{resp3::type::number, 1UL, 0UL, {"-3"}})); \
test(ex, make_expected(S05b, int{11})); \
test(ex, make_expected(S02c, result<std::string>{}, boost::redis::error::not_a_number)); \
test(ex, make_expected(S05a, node_type{{resp3::type::number, 1UL, 0UL, {"-3"}}})); \
test(ex, make_expected(S05b, result<int>{11})); \
test(ex, make_expected(S05b, op_int_ok)); \
test(ex, make_expected(S05b, std::list<std::string>{}, boost::redis::error::expects_resp3_aggregate)); \
test(ex, make_expected(S05b, std::map<std::string, std::string>{}, boost::redis::error::expects_resp3_map)); \
test(ex, make_expected(S05b, std::set<std::string>{}, boost::redis::error::expects_resp3_set)); \
test(ex, make_expected(S05b, std::unordered_map<std::string, std::string>{}, boost::redis::error::expects_resp3_map)); \
test(ex, make_expected(S05b, std::unordered_set<std::string>{}, boost::redis::error::expects_resp3_set)); \
test(ex, make_expected(S05b, result<std::list<std::string>>{}, boost::redis::error::expects_resp3_aggregate)); \
test(ex, make_expected(S05b, result<std::map<std::string, std::string>>{}, boost::redis::error::expects_resp3_map)); \
test(ex, make_expected(S05b, result<std::set<std::string>>{}, boost::redis::error::expects_resp3_set)); \
test(ex, make_expected(S05b, result<std::unordered_map<std::string, std::string>>{}, boost::redis::error::expects_resp3_map)); \
test(ex, make_expected(S05b, result<std::unordered_set<std::string>>{}, boost::redis::error::expects_resp3_set)); \
test(ex, make_expected(s05c, array_type2{}, boost::redis::error::expects_resp3_aggregate));\
test(ex, make_expected(s05c, node_type{resp3::type::number, 1UL, 0UL, {"3"}})); \
test(ex, make_expected(S06a, array_type{}, boost::redis::error::resp3_null));\
test(ex, make_expected(S06a, int{0}, boost::redis::error::resp3_null)); \
test(ex, make_expected(S06a, map_type{}, boost::redis::error::resp3_null));\
test(ex, make_expected(S06a, op_type_01{}));\
test(ex, make_expected(s05c, node_type{{resp3::type::number, 1UL, 0UL, {"3"}}}));\
test(ex, make_expected(S06a, op_type_01{})); \
test(ex, make_expected(S06a, op_type_02{}));\
test(ex, make_expected(S06a, op_type_03{}));\
test(ex, make_expected(S06a, op_type_04{}));\
@@ -410,21 +429,17 @@ std::vector<node_type> const attr_e1b
test(ex, make_expected(S06a, op_type_07{}));\
test(ex, make_expected(S06a, op_type_08{}));\
test(ex, make_expected(S06a, op_type_09{}));\
test(ex, make_expected(S06a, std::list<int>{}, boost::redis::error::resp3_null));\
test(ex, make_expected(S06a, std::vector<int>{}, boost::redis::error::resp3_null));\
test(ex, make_expected(S07a, push_e1a)); \
test(ex, make_expected(S07b, push_e1b)); \
test(ex, make_expected(S04b, map_type{}, boost::redis::error::expects_resp3_map));\
test(ex, make_expected(S03b, map_e1f));\
test(ex, make_expected(S03b, map_e1g));\
test(ex, make_expected(S03b, map_e1k));\
test(ex, make_expected(S03b, map_e1l));\
test(ex, make_expected(S03b, map_expected_1a));\
test(ex, make_expected(S03b, map_expected_1b));\
test(ex, make_expected(S03b, map_expected_1c));\
test(ex, make_expected(S03b, map_expected_1d));\
test(ex, make_expected(S03b, map_expected_1e));\
test(ex, make_expected(S04c, response<op_map_type>{map_expected_1d}));\
test(ex, make_expected(S08a, attr_e1a)); \
test(ex, make_expected(S08b, attr_e1b)); \
test(ex, make_expected(S04e, array_e1a));\
@@ -446,41 +461,49 @@ std::vector<node_type> const attr_e1b
test(ex, make_expected(S09a, set_e1g)); \
test(ex, make_expected(S09a, set_expected1a)); \
test(ex, make_expected(S09a, set_expected_1e)); \
test(ex, make_expected(S09a, set_type{"apple", "one", "orange", "three", "two"})); \
test(ex, make_expected(S04d, response<uset_type>{set_e1c})); \
test(ex, make_expected(S09b, std::vector<node_type>{ {resp3::type::set, 0UL, 0UL, {}} })); \
test(ex, make_expected(S10a, boost::redis::ignore, boost::redis::error::resp3_simple_error)); \
test(ex, make_expected(S10a, node_type{resp3::type::simple_error, 1UL, 0UL, {"Error"}}, boost::redis::error::resp3_simple_error)); \
test(ex, make_expected(S10b, node_type{resp3::type::simple_error, 1UL, 0UL, {""}}, boost::redis::error::resp3_simple_error)); \
test(ex, make_expected(S09a, set_type{{"apple", "one", "orange", "three", "two"}})); \
test(ex, make_expected(S09b, vec_node_type{{{resp3::type::set, 0UL, 0UL, {}}}})); \
test(ex, make_expected(S03c, map_type{}));\
test(ex, make_expected(S11a, node_type{resp3::type::doublean, 1UL, 0UL, {"1.23"}}));\
test(ex, make_expected(S11b, node_type{resp3::type::doublean, 1UL, 0UL, {"inf"}}));\
test(ex, make_expected(S11c, node_type{resp3::type::doublean, 1UL, 0UL, {"-inf"}}));\
test(ex, make_expected(S11d, double{1.23}));\
test(ex, make_expected(S11e, double{0}, boost::redis::error::not_a_double));\
test(ex, make_expected(S12a, node_type{resp3::type::blob_error, 1UL, 0UL, {"SYNTAX invalid syntax"}}, boost::redis::error::resp3_blob_error));\
test(ex, make_expected(S12b, node_type{resp3::type::blob_error, 1UL, 0UL, {}}, boost::redis::error::resp3_blob_error));\
test(ex, make_expected(S12c, boost::redis::ignore, boost::redis::error::resp3_blob_error));\
test(ex, make_expected(S13a, node_type{resp3::type::verbatim_string, 1UL, 0UL, {"txt:Some string"}}));\
test(ex, make_expected(S13b, node_type{resp3::type::verbatim_string, 1UL, 0UL, {}}));\
test(ex, make_expected(S14a, node_type{resp3::type::big_number, 1UL, 0UL, {"3492890328409238509324850943850943825024385"}}));\
test(ex, make_expected(S14b, int{}, boost::redis::error::empty_field));\
test(ex, make_expected(S15a, std::optional<std::string>{"OK"}));\
test(ex, make_expected(S15a, std::string{"OK"}));\
test(ex, make_expected(S15b, std::optional<std::string>{""}));\
test(ex, make_expected(S15b, std::string{""}));\
test(ex, make_expected(S16a, int{}, boost::redis::error::invalid_data_type));\
test(ex, make_expected(S05d, int{11}, boost::redis::error::not_a_number));\
test(ex, make_expected(S11a, node_type{{resp3::type::doublean, 1UL, 0UL, {"1.23"}}}));\
test(ex, make_expected(S11b, node_type{{resp3::type::doublean, 1UL, 0UL, {"inf"}}}));\
test(ex, make_expected(S11c, node_type{{resp3::type::doublean, 1UL, 0UL, {"-inf"}}}));\
test(ex, make_expected(S11d, result<double>{{1.23}}));\
test(ex, make_expected(S11e, result<double>{{0}}, boost::redis::error::not_a_double));\
test(ex, make_expected(S13a, node_type{{resp3::type::verbatim_string, 1UL, 0UL, {"txt:Some string"}}}));\
test(ex, make_expected(S13b, node_type{{resp3::type::verbatim_string, 1UL, 0UL, {}}}));\
test(ex, make_expected(S14a, node_type{{resp3::type::big_number, 1UL, 0UL, {"3492890328409238509324850943850943825024385"}}}));\
test(ex, make_expected(S14b, result<int>{}, boost::redis::error::empty_field));\
test(ex, make_expected(S15a, result<std::optional<std::string>>{{"OK"}}));\
test(ex, make_expected(S15a, result<std::string>{{"OK"}}));\
test(ex, make_expected(S15b, result<std::optional<std::string>>{""}));\
test(ex, make_expected(S15b, result<std::string>{{""}}));\
test(ex, make_expected(S16a, result<int>{}, boost::redis::error::invalid_data_type));\
test(ex, make_expected(S05d, result<int>{11}, boost::redis::error::not_a_number));\
test(ex, make_expected(S03d, map_type{}, boost::redis::error::not_a_number));\
test(ex, make_expected(S02d, std::string{}, boost::redis::error::not_a_number));\
test(ex, make_expected(S17a, std::string{}, boost::redis::error::not_a_number));\
test(ex, make_expected(S05e, int{}, boost::redis::error::empty_field));\
test(ex, make_expected(S01d, std::optional<bool>{}, boost::redis::error::empty_field));\
test(ex, make_expected(S11f, std::string{}, boost::redis::error::empty_field));\
test(ex, make_expected(S17b, node_type{resp3::type::blob_string, 1UL, 0UL, {"hh"}}));\
test(ex, make_expected(S18c, node_type{resp3::type::blob_string, 1UL, 0UL, {"hhaa\aaaa\raaaaa\r\naaaaaaaaaa"}}));\
test(ex, make_expected(S18d, node_type{resp3::type::blob_string, 1UL, 0UL, {}}));\
test(ex, make_expected(make_blob_string(blob), node_type{resp3::type::blob_string, 1UL, 0UL, {blob}}));\
test(ex, make_expected(S02d, result<std::string>{}, boost::redis::error::not_a_number));\
test(ex, make_expected(S17a, result<std::string>{}, boost::redis::error::not_a_number));\
test(ex, make_expected(S05e, result<int>{}, boost::redis::error::empty_field));\
test(ex, make_expected(S01d, result<std::optional<bool>>{}, boost::redis::error::empty_field));\
test(ex, make_expected(S11f, result<std::string>{}, boost::redis::error::empty_field));\
test(ex, make_expected(S17b, node_type{{resp3::type::blob_string, 1UL, 0UL, {"hh"}}}));\
test(ex, make_expected(S18c, node_type{{resp3::type::blob_string, 1UL, 0UL, {"hhaa\aaaa\raaaaa\r\naaaaaaaaaa"}}}));\
test(ex, make_expected(S18d, node_type{{resp3::type::blob_string, 1UL, 0UL, {}}}));\
test(ex, make_expected(make_blob_string(blob), node_type{{resp3::type::blob_string, 1UL, 0UL, {blob}}}));\
test(ex, make_expected(S04a, result<std::vector<int>>{{11}})); \
test(ex, make_expected(S04d, result<response<std::unordered_set<std::string>>>{response<std::unordered_set<std::string>>{{set_e1c}}})); \
test(ex, make_expected(S04c, result<response<std::map<std::string, std::string>>>{response<std::map<std::string, std::string>>{{map_expected_1b}}}));\
test(ex, make_expected(S03b, map_e1l));\
test(ex, make_expected(S06a, result<int>{0}, {}, resp3::type::null)); \
test(ex, make_expected(S06a, map_type{}, {}, resp3::type::null));\
test(ex, make_expected(S06a, array_type{}, {}, resp3::type::null));\
test(ex, make_expected(S06a, result<std::list<int>>{}, {}, resp3::type::null));\
test(ex, make_expected(S06a, result<std::vector<int>>{}, {}, resp3::type::null));\
test(ex, make_expected(S10a, result<ignore_t>{}, boost::redis::error::resp3_simple_error)); \
test(ex, make_expected(S10a, node_type{{resp3::type::simple_error, 1UL, 0UL, {"Error"}}}, {}, resp3::type::simple_error)); \
test(ex, make_expected(S10b, node_type{{resp3::type::simple_error, 1UL, 0UL, {""}}}, {}, resp3::type::simple_error)); \
test(ex, make_expected(S12a, node_type{{resp3::type::blob_error, 1UL, 0UL, {"SYNTAX invalid syntax"}}}, {}, resp3::type::blob_error));\
test(ex, make_expected(S12b, node_type{{resp3::type::blob_error, 1UL, 0UL, {}}}, {}, resp3::type::blob_error));\
test(ex, make_expected(S12c, result<ignore_t>{}, boost::redis::error::resp3_blob_error));\
BOOST_AUTO_TEST_CASE(parser)
{
@@ -508,7 +531,7 @@ BOOST_AUTO_TEST_CASE(ignore_adapter_simple_error)
test_stream ts {ioc};
ts.append(S10a);
resp3::read(ts, net::dynamic_buffer(rbuffer), adapt2(), ec);
resp3::read(ts, net::dynamic_buffer(rbuffer), adapt2(ignore), ec);
BOOST_CHECK_EQUAL(ec, boost::redis::error::resp3_simple_error);
BOOST_TEST(!rbuffer.empty());
}
@@ -521,7 +544,7 @@ BOOST_AUTO_TEST_CASE(ignore_adapter_blob_error)
test_stream ts {ioc};
ts.append(S12a);
resp3::read(ts, net::dynamic_buffer(rbuffer), adapt2(), ec);
resp3::read(ts, net::dynamic_buffer(rbuffer), adapt2(ignore), ec);
BOOST_CHECK_EQUAL(ec, boost::redis::error::resp3_blob_error);
BOOST_TEST(!rbuffer.empty());
}
@@ -534,7 +557,7 @@ BOOST_AUTO_TEST_CASE(ignore_adapter_no_error)
test_stream ts {ioc};
ts.append(S05b);
resp3::read(ts, net::dynamic_buffer(rbuffer), adapt2(), ec);
resp3::read(ts, net::dynamic_buffer(rbuffer), adapt2(ignore), ec);
BOOST_TEST(!ec);
BOOST_TEST(rbuffer.empty());
}
@@ -635,18 +658,16 @@ BOOST_AUTO_TEST_CASE(adapter)
boost::system::error_code ec;
std::string a;
int b;
auto resp = std::tie(a, b, std::ignore);
response<std::string, int, ignore_t> resp;
auto f = boost_redis_adapt(resp);
f(0, resp3::node<std::string_view>{type::simple_string, 1, 0, "Hello"}, ec);
f(1, resp3::node<std::string_view>{type::number, 1, 0, "42"}, ec);
BOOST_CHECK_EQUAL(a, "Hello");
BOOST_CHECK_EQUAL(std::get<0>(resp).value(), "Hello");
BOOST_TEST(!ec);
BOOST_CHECK_EQUAL(b, 42);
BOOST_CHECK_EQUAL(std::get<1>(resp).value(), 42);
BOOST_TEST(!ec);
}