Skip to content

Commit dfecf0b

Browse files
committed
Fixes the adapter of empty nested responses.
See #210.
1 parent ce4e5cb commit dfecf0b

File tree

3 files changed

+133
-15
lines changed

3 files changed

+133
-15
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -684,6 +684,9 @@ https://lists.boost.org/Archives/boost/2023/01/253944.php.
684684
also changed to return EOF to the user when that error is received
685685
from the server. That is a breaking change.
686686

687+
* ([Issue 210](https://github.com/boostorg/redis/issues/210))
688+
Fixes the adapter of empty nested reposponses.
689+
687690
### Boost 1.85
688691

689692
* ([Issue 170](https://github.com/boostorg/redis/issues/170))

include/boost/redis/adapter/detail/result_traits.hpp

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,12 @@ class static_aggregate_adapter<result<Tuple>> {
102102
std::variant>,
103103
std::tuple_size<Tuple>::value>;
104104

105+
// Tuple element we are currently on.
105106
std::size_t i_ = 0;
107+
108+
// Nested aggregate element counter.
106109
std::size_t aggregate_size_ = 0;
110+
107111
adapters_array_type adapters_;
108112
result<Tuple>* res_ = nullptr;
109113

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

119123
template <class String>
120-
void count(resp3::basic_node<String> const& nd)
124+
void count(resp3::basic_node<String> const& elem)
121125
{
122-
if (nd.depth == 1) {
123-
if (is_aggregate(nd.data_type))
124-
aggregate_size_ = element_multiplicity(nd.data_type) * nd.aggregate_size;
125-
else
126-
++i_;
127-
128-
return;
126+
if (elem.depth == 1 && is_aggregate(elem.data_type)) {
127+
aggregate_size_ = element_multiplicity(elem.data_type) * elem.aggregate_size;
129128
}
130129

131-
if (--aggregate_size_ == 0)
132-
++i_;
130+
if (aggregate_size_ == 0) {
131+
i_ += 1;
132+
} else {
133+
aggregate_size_ -= 1;
134+
}
133135
}
134136

135137
template <class String>
136-
void operator()(resp3::basic_node<String> const& nd, system::error_code& ec)
138+
void operator()(resp3::basic_node<String> const& elem, system::error_code& ec)
137139
{
138140
using std::visit;
139141

140-
if (nd.depth == 0) {
141-
auto const real_aggr_size = nd.aggregate_size * element_multiplicity(nd.data_type);
142+
if (elem.depth == 0) {
143+
auto const multiplicity = element_multiplicity(elem.data_type);
144+
auto const real_aggr_size = elem.aggregate_size * multiplicity;
142145
if (real_aggr_size != std::tuple_size<Tuple>::value)
143146
ec = redis::error::incompatible_size;
144147

145148
return;
146149
}
147150

148-
visit([&](auto& arg){arg(nd, ec);}, adapters_[i_]);
149-
count(nd);
151+
visit([&](auto& arg){arg(elem, ec);}, adapters_[i_]);
152+
count(elem);
150153
}
151154
};
152155

test/test_low_level_sync_sans_io.cpp

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@
1515
using boost::redis::request;
1616
using boost::redis::config;
1717
using boost::redis::detail::push_hello;
18+
using boost::redis::response;
1819
using boost::redis::adapter::adapt2;
1920
using boost::redis::adapter::result;
2021
using boost::redis::resp3::detail::deserialize;
22+
using boost::redis::ignore_t;
2123

2224
BOOST_AUTO_TEST_CASE(low_level_sync_sans_io)
2325
{
@@ -88,3 +90,113 @@ BOOST_AUTO_TEST_CASE(config_to_hello_cmd_auth)
8890
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";
8991
BOOST_CHECK_EQUAL(req.payload(), expected);
9092
}
93+
94+
BOOST_AUTO_TEST_CASE(issue_210_empty_set)
95+
{
96+
try {
97+
result<
98+
std::tuple<
99+
result<int>,
100+
result<std::vector<std::string>>,
101+
result<std::string>,
102+
result<int>
103+
>
104+
> resp;
105+
106+
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";
107+
108+
deserialize(wire, adapt2(resp));
109+
110+
BOOST_CHECK_EQUAL(std::get<0>(resp.value()).value(), 1);
111+
BOOST_CHECK(std::get<1>(resp.value()).value().empty());
112+
BOOST_CHECK_EQUAL(std::get<2>(resp.value()).value(), "this_should_not_be_in_set");
113+
BOOST_CHECK_EQUAL(std::get<3>(resp.value()).value(), 2);
114+
115+
} catch (std::exception const& e) {
116+
std::cerr << e.what() << std::endl;
117+
exit(EXIT_FAILURE);
118+
}
119+
}
120+
121+
BOOST_AUTO_TEST_CASE(issue_210_non_empty_set_size_one)
122+
{
123+
try {
124+
result<
125+
std::tuple<
126+
result<int>,
127+
result<std::vector<std::string>>,
128+
result<std::string>,
129+
result<int>
130+
>
131+
> resp;
132+
133+
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";
134+
135+
deserialize(wire, adapt2(resp));
136+
137+
BOOST_CHECK_EQUAL(std::get<0>(resp.value()).value(), 1);
138+
BOOST_CHECK_EQUAL(std::get<1>(resp.value()).value().size(), 1);
139+
BOOST_CHECK_EQUAL(std::get<1>(resp.value()).value().at(0), std::string{"foo"});
140+
BOOST_CHECK_EQUAL(std::get<2>(resp.value()).value(), "this_should_not_be_in_set");
141+
BOOST_CHECK_EQUAL(std::get<3>(resp.value()).value(), 2);
142+
143+
} catch (std::exception const& e) {
144+
std::cerr << e.what() << std::endl;
145+
exit(EXIT_FAILURE);
146+
}
147+
}
148+
149+
BOOST_AUTO_TEST_CASE(issue_210_non_empty_set_size_two)
150+
{
151+
try {
152+
result<
153+
std::tuple<
154+
result<int>,
155+
result<std::vector<std::string>>,
156+
result<std::string>,
157+
result<int>
158+
>
159+
> resp;
160+
161+
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";
162+
163+
deserialize(wire, adapt2(resp));
164+
165+
BOOST_CHECK_EQUAL(std::get<0>(resp.value()).value(), 1);
166+
BOOST_CHECK_EQUAL(std::get<1>(resp.value()).value().at(0), std::string{"foo"});
167+
BOOST_CHECK_EQUAL(std::get<1>(resp.value()).value().at(1), std::string{"bar"});
168+
BOOST_CHECK_EQUAL(std::get<2>(resp.value()).value(), "this_should_not_be_in_set");
169+
170+
} catch (std::exception const& e) {
171+
std::cerr << e.what() << std::endl;
172+
exit(EXIT_FAILURE);
173+
}
174+
}
175+
176+
BOOST_AUTO_TEST_CASE(issue_210_no_nested)
177+
{
178+
try {
179+
result<
180+
std::tuple<
181+
result<int>,
182+
result<std::string>,
183+
result<std::string>,
184+
result<std::string>
185+
>
186+
> resp;
187+
188+
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";
189+
190+
deserialize(wire, adapt2(resp));
191+
192+
BOOST_CHECK_EQUAL(std::get<0>(resp.value()).value(), 1);
193+
BOOST_CHECK_EQUAL(std::get<1>(resp.value()).value(), std::string{"foo"});
194+
BOOST_CHECK_EQUAL(std::get<2>(resp.value()).value(), std::string{"bar"});
195+
BOOST_CHECK_EQUAL(std::get<3>(resp.value()).value(), "this_should_not_be_in_set");
196+
197+
} catch (std::exception const& e) {
198+
std::cerr << e.what() << std::endl;
199+
exit(EXIT_FAILURE);
200+
}
201+
}
202+

0 commit comments

Comments
 (0)