Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -684,6 +684,9 @@ https://lists.boost.org/Archives/boost/2023/01/253944.php.
also changed to return EOF to the user when that error is received
from the server. That is a breaking change.

* ([Issue 210](https://github.com/boostorg/redis/issues/210))
Fixes the adapter of empty nested reposponses.

### Boost 1.85

* ([Issue 170](https://github.com/boostorg/redis/issues/170))
Expand Down
33 changes: 18 additions & 15 deletions include/boost/redis/adapter/detail/result_traits.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,12 @@ class static_aggregate_adapter<result<Tuple>> {
std::variant>,
std::tuple_size<Tuple>::value>;

// Tuple element we are currently on.
std::size_t i_ = 0;

// Nested aggregate element counter.
std::size_t aggregate_size_ = 0;

adapters_array_type adapters_;
result<Tuple>* res_ = nullptr;

Expand All @@ -117,36 +121,35 @@ class static_aggregate_adapter<result<Tuple>> {
}

template <class String>
void count(resp3::basic_node<String> const& nd)
void count(resp3::basic_node<String> const& elem)
{
if (nd.depth == 1) {
if (is_aggregate(nd.data_type))
aggregate_size_ = element_multiplicity(nd.data_type) * nd.aggregate_size;
else
++i_;

return;
if (elem.depth == 1 && is_aggregate(elem.data_type)) {
aggregate_size_ = element_multiplicity(elem.data_type) * elem.aggregate_size;
}

if (--aggregate_size_ == 0)
++i_;
if (aggregate_size_ == 0) {
i_ += 1;
} else {
aggregate_size_ -= 1;
}
}

template <class String>
void operator()(resp3::basic_node<String> const& nd, system::error_code& ec)
void operator()(resp3::basic_node<String> const& elem, 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 (elem.depth == 0) {
auto const multiplicity = element_multiplicity(elem.data_type);
auto const real_aggr_size = elem.aggregate_size * multiplicity;
if (real_aggr_size != std::tuple_size<Tuple>::value)
ec = redis::error::incompatible_size;

return;
}

visit([&](auto& arg){arg(nd, ec);}, adapters_[i_]);
count(nd);
visit([&](auto& arg){arg(elem, ec);}, adapters_[i_]);
count(elem);
}
};

Expand Down
112 changes: 112 additions & 0 deletions test/test_low_level_sync_sans_io.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@
using boost::redis::request;
using boost::redis::config;
using boost::redis::detail::push_hello;
using boost::redis::response;
using boost::redis::adapter::adapt2;
using boost::redis::adapter::result;
using boost::redis::resp3::detail::deserialize;
using boost::redis::ignore_t;

BOOST_AUTO_TEST_CASE(low_level_sync_sans_io)
{
Expand Down Expand Up @@ -88,3 +90,113 @@ BOOST_AUTO_TEST_CASE(config_to_hello_cmd_auth)
std::string_view const expected = "*5\r\n$5\r\nHELLO\r\n$1\r\n3\r\n$4\r\nAUTH\r\n$3\r\nfoo\r\n$3\r\nbar\r\n";
BOOST_CHECK_EQUAL(req.payload(), expected);
}

BOOST_AUTO_TEST_CASE(issue_210_empty_set)
{
try {
result<
std::tuple<
result<int>,
result<std::vector<std::string>>,
result<std::string>,
result<int>
>
> resp;

char const* wire = "*4\r\n:1\r\n~0\r\n$25\r\nthis_should_not_be_in_set\r\n:2\r\n";

deserialize(wire, adapt2(resp));

BOOST_CHECK_EQUAL(std::get<0>(resp.value()).value(), 1);
BOOST_CHECK(std::get<1>(resp.value()).value().empty());
BOOST_CHECK_EQUAL(std::get<2>(resp.value()).value(), "this_should_not_be_in_set");
BOOST_CHECK_EQUAL(std::get<3>(resp.value()).value(), 2);

} catch (std::exception const& e) {
std::cerr << e.what() << std::endl;
exit(EXIT_FAILURE);
}
}

BOOST_AUTO_TEST_CASE(issue_210_non_empty_set_size_one)
{
try {
result<
std::tuple<
result<int>,
result<std::vector<std::string>>,
result<std::string>,
result<int>
>
> resp;

char const* wire = "*4\r\n:1\r\n~1\r\n$3\r\nfoo\r\n$25\r\nthis_should_not_be_in_set\r\n:2\r\n";

deserialize(wire, adapt2(resp));

BOOST_CHECK_EQUAL(std::get<0>(resp.value()).value(), 1);
BOOST_CHECK_EQUAL(std::get<1>(resp.value()).value().size(), 1);
BOOST_CHECK_EQUAL(std::get<1>(resp.value()).value().at(0), std::string{"foo"});
BOOST_CHECK_EQUAL(std::get<2>(resp.value()).value(), "this_should_not_be_in_set");
BOOST_CHECK_EQUAL(std::get<3>(resp.value()).value(), 2);

} catch (std::exception const& e) {
std::cerr << e.what() << std::endl;
exit(EXIT_FAILURE);
}
}

BOOST_AUTO_TEST_CASE(issue_210_non_empty_set_size_two)
{
try {
result<
std::tuple<
result<int>,
result<std::vector<std::string>>,
result<std::string>,
result<int>
>
> resp;

char const* wire = "*4\r\n:1\r\n~2\r\n$3\r\nfoo\r\n$3\r\nbar\r\n$25\r\nthis_should_not_be_in_set\r\n:2\r\n";

deserialize(wire, adapt2(resp));

BOOST_CHECK_EQUAL(std::get<0>(resp.value()).value(), 1);
BOOST_CHECK_EQUAL(std::get<1>(resp.value()).value().at(0), std::string{"foo"});
BOOST_CHECK_EQUAL(std::get<1>(resp.value()).value().at(1), std::string{"bar"});
BOOST_CHECK_EQUAL(std::get<2>(resp.value()).value(), "this_should_not_be_in_set");

} catch (std::exception const& e) {
std::cerr << e.what() << std::endl;
exit(EXIT_FAILURE);
}
}

BOOST_AUTO_TEST_CASE(issue_210_no_nested)
{
try {
result<
std::tuple<
result<int>,
result<std::string>,
result<std::string>,
result<std::string>
>
> resp;

char const* wire = "*4\r\n:1\r\n$3\r\nfoo\r\n$3\r\nbar\r\n$25\r\nthis_should_not_be_in_set\r\n";

deserialize(wire, adapt2(resp));

BOOST_CHECK_EQUAL(std::get<0>(resp.value()).value(), 1);
BOOST_CHECK_EQUAL(std::get<1>(resp.value()).value(), std::string{"foo"});
BOOST_CHECK_EQUAL(std::get<2>(resp.value()).value(), std::string{"bar"});
BOOST_CHECK_EQUAL(std::get<3>(resp.value()).value(), "this_should_not_be_in_set");

} catch (std::exception const& e) {
std::cerr << e.what() << std::endl;
exit(EXIT_FAILURE);
}
}