boost_redis 1.4.2
A redis client library
adapters.hpp
1/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
2 *
3 * Distributed under the Boost Software License, Version 1.0. (See
4 * accompanying file LICENSE.txt)
5 */
6
7#ifndef BOOST_REDIS_ADAPTER_ADAPTERS_HPP
8#define BOOST_REDIS_ADAPTER_ADAPTERS_HPP
9
10#include <boost/redis/error.hpp>
11#include <boost/redis/resp3/type.hpp>
12#include <boost/redis/resp3/serialization.hpp>
13#include <boost/redis/resp3/node.hpp>
14#include <boost/redis/adapter/result.hpp>
15#include <boost/assert.hpp>
16
17#include <set>
18#include <optional>
19#include <unordered_set>
20#include <forward_list>
21#include <system_error>
22#include <map>
23#include <unordered_map>
24#include <list>
25#include <deque>
26#include <vector>
27#include <array>
28#include <string_view>
29#include <charconv>
30
31// See https://stackoverflow.com/a/31658120/1077832
32#include<ciso646>
33#ifdef _LIBCPP_VERSION
34#else
35#include <cstdlib>
36#endif
37
38namespace boost::redis::adapter::detail
39{
40
41// Serialization.
42
43template <class T>
44auto boost_redis_from_bulk(T& i, std::string_view sv, system::error_code& ec) -> typename std::enable_if<std::is_integral<T>::value, void>::type
45{
46 auto const res = std::from_chars(sv.data(), sv.data() + std::size(sv), i);
47 if (res.ec != std::errc())
49}
50
51inline
52void boost_redis_from_bulk(bool& t, std::string_view sv, system::error_code&)
53{
54 t = *sv.data() == 't';
55}
56
57inline
58void boost_redis_from_bulk(double& d, std::string_view sv, system::error_code& ec)
59{
60#ifdef _LIBCPP_VERSION
61 // The string in sv is not null terminated and we also don't know
62 // if there is enough space at the end for a null char. The easiest
63 // thing to do is to create a temporary.
64 std::string const tmp{sv.data(), sv.data() + std::size(sv)};
65 char* end{};
66 d = std::strtod(tmp.data(), &end);
67 if (d == HUGE_VAL || d == 0)
69#else
70 auto const res = std::from_chars(sv.data(), sv.data() + std::size(sv), d);
71 if (res.ec != std::errc())
73#endif // _LIBCPP_VERSION
74}
75
76template <class CharT, class Traits, class Allocator>
77void
78boost_redis_from_bulk(
79 std::basic_string<CharT, Traits, Allocator>& s,
80 std::string_view sv,
81 system::error_code&)
82{
83 s.append(sv.data(), sv.size());
84}
85
86//================================================
87
88template <class Result>
89class general_aggregate {
90private:
91 Result* result_;
92
93public:
94 explicit general_aggregate(Result* c = nullptr): result_(c) {}
95 void operator()(resp3::basic_node<std::string_view> const& nd, system::error_code&)
96 {
97 BOOST_ASSERT_MSG(!!result_, "Unexpected null pointer");
98 switch (nd.data_type) {
101 *result_ = error{nd.data_type, std::string{std::cbegin(nd.value), std::cend(nd.value)}};
102 break;
103 default:
104 result_->value().push_back({nd.data_type, nd.aggregate_size, nd.depth, std::string{std::cbegin(nd.value), std::cend(nd.value)}});
105 }
106 }
107};
108
109template <class Node>
110class general_simple {
111private:
112 Node* result_;
113
114public:
115 explicit general_simple(Node* t = nullptr) : result_(t) {}
116
117 void operator()(resp3::basic_node<std::string_view> const& nd, system::error_code&)
118 {
119 BOOST_ASSERT_MSG(!!result_, "Unexpected null pointer");
120 switch (nd.data_type) {
123 *result_ = error{nd.data_type, std::string{std::cbegin(nd.value), std::cend(nd.value)}};
124 break;
125 default:
126 result_->value().data_type = nd.data_type;
127 result_->value().aggregate_size = nd.aggregate_size;
128 result_->value().depth = nd.depth;
129 result_->value().value.assign(nd.value.data(), nd.value.size());
130 }
131 }
132};
133
134template <class Result>
135class simple_impl {
136public:
137 void on_value_available(Result&) {}
138
139 void
140 operator()(
141 Result& result,
142 resp3::basic_node<std::string_view> const& n,
143 system::error_code& ec)
144 {
145 if (is_aggregate(n.data_type)) {
147 return;
148 }
149
150 boost_redis_from_bulk(result, n.value, ec);
151 }
152};
153
154template <class Result>
155class set_impl {
156private:
157 typename Result::iterator hint_;
158
159public:
160 void on_value_available(Result& result)
161 { hint_ = std::end(result); }
162
163 void
164 operator()(
165 Result& result,
166 resp3::basic_node<std::string_view> const& nd,
167 system::error_code& ec)
168 {
169 if (is_aggregate(nd.data_type)) {
170 if (nd.data_type != resp3::type::set)
172 return;
173 }
174
175 BOOST_ASSERT(nd.aggregate_size == 1);
176
177 if (nd.depth < 1) {
179 return;
180 }
181
182 typename Result::key_type obj;
183 boost_redis_from_bulk(obj, nd.value, ec);
184 hint_ = result.insert(hint_, std::move(obj));
185 }
186};
187
188template <class Result>
189class map_impl {
190private:
191 typename Result::iterator current_;
192 bool on_key_ = true;
193
194public:
195 void on_value_available(Result& result)
196 { current_ = std::end(result); }
197
198 void
199 operator()(
200 Result& result,
201 resp3::basic_node<std::string_view> const& nd,
202 system::error_code& ec)
203 {
204 if (is_aggregate(nd.data_type)) {
205 if (element_multiplicity(nd.data_type) != 2)
207 return;
208 }
209
210 BOOST_ASSERT(nd.aggregate_size == 1);
211
212 if (nd.depth < 1) {
214 return;
215 }
216
217 if (on_key_) {
218 typename Result::key_type obj;
219 boost_redis_from_bulk(obj, nd.value, ec);
220 current_ = result.insert(current_, {std::move(obj), {}});
221 } else {
222 typename Result::mapped_type obj;
223 boost_redis_from_bulk(obj, nd.value, ec);
224 current_->second = std::move(obj);
225 }
226
227 on_key_ = !on_key_;
228 }
229};
230
231template <class Result>
232class vector_impl {
233public:
234 void on_value_available(Result& ) { }
235
236 void
237 operator()(
238 Result& result,
239 resp3::basic_node<std::string_view> const& nd,
240 system::error_code& ec)
241 {
242 if (is_aggregate(nd.data_type)) {
243 auto const m = element_multiplicity(nd.data_type);
244 result.reserve(result.size() + m * nd.aggregate_size);
245 } else {
246 result.push_back({});
247 boost_redis_from_bulk(result.back(), nd.value, ec);
248 }
249 }
250};
251
252template <class Result>
253class array_impl {
254private:
255 int i_ = -1;
256
257public:
258 void on_value_available(Result& ) { }
259
260 void
261 operator()(
262 Result& result,
263 resp3::basic_node<std::string_view> const& nd,
264 system::error_code& ec)
265 {
266 if (is_aggregate(nd.data_type)) {
267 if (i_ != -1) {
269 return;
270 }
271
272 if (result.size() != nd.aggregate_size * element_multiplicity(nd.data_type)) {
274 return;
275 }
276 } else {
277 if (i_ == -1) {
279 return;
280 }
281
282 BOOST_ASSERT(nd.aggregate_size == 1);
283 boost_redis_from_bulk(result.at(i_), nd.value, ec);
284 }
285
286 ++i_;
287 }
288};
289
290template <class Result>
291struct list_impl {
292
293 void on_value_available(Result& ) { }
294
295 void
296 operator()(
297 Result& result,
298 resp3::basic_node<std::string_view> const& nd,
299 system::error_code& ec)
300 {
301 if (!is_aggregate(nd.data_type)) {
302 BOOST_ASSERT(nd.aggregate_size == 1);
303 if (nd.depth < 1) {
305 return;
306 }
307
308 result.push_back({});
309 boost_redis_from_bulk(result.back(), nd.value, ec);
310 }
311 }
312};
313
314//---------------------------------------------------
315
316template <class T>
317struct impl_map { using type = simple_impl<T>; };
318
319template <class Key, class Compare, class Allocator>
320struct impl_map<std::set<Key, Compare, Allocator>> { using type = set_impl<std::set<Key, Compare, Allocator>>; };
321
322template <class Key, class Compare, class Allocator>
323struct impl_map<std::multiset<Key, Compare, Allocator>> { using type = set_impl<std::multiset<Key, Compare, Allocator>>; };
324
325template <class Key, class Hash, class KeyEqual, class Allocator>
326struct impl_map<std::unordered_set<Key, Hash, KeyEqual, Allocator>> { using type = set_impl<std::unordered_set<Key, Hash, KeyEqual, Allocator>>; };
327
328template <class Key, class Hash, class KeyEqual, class Allocator>
329struct impl_map<std::unordered_multiset<Key, Hash, KeyEqual, Allocator>> { using type = set_impl<std::unordered_multiset<Key, Hash, KeyEqual, Allocator>>; };
330
331template <class Key, class T, class Compare, class Allocator>
332struct impl_map<std::map<Key, T, Compare, Allocator>> { using type = map_impl<std::map<Key, T, Compare, Allocator>>; };
333
334template <class Key, class T, class Compare, class Allocator>
335struct impl_map<std::multimap<Key, T, Compare, Allocator>> { using type = map_impl<std::multimap<Key, T, Compare, Allocator>>; };
336
337template <class Key, class Hash, class KeyEqual, class Allocator>
338struct impl_map<std::unordered_map<Key, Hash, KeyEqual, Allocator>> { using type = map_impl<std::unordered_map<Key, Hash, KeyEqual, Allocator>>; };
339
340template <class Key, class Hash, class KeyEqual, class Allocator>
341struct impl_map<std::unordered_multimap<Key, Hash, KeyEqual, Allocator>> { using type = map_impl<std::unordered_multimap<Key, Hash, KeyEqual, Allocator>>; };
342
343template <class T, class Allocator>
344struct impl_map<std::vector<T, Allocator>> { using type = vector_impl<std::vector<T, Allocator>>; };
345
346template <class T, std::size_t N>
347struct impl_map<std::array<T, N>> { using type = array_impl<std::array<T, N>>; };
348
349template <class T, class Allocator>
350struct impl_map<std::list<T, Allocator>> { using type = list_impl<std::list<T, Allocator>>; };
351
352template <class T, class Allocator>
353struct impl_map<std::deque<T, Allocator>> { using type = list_impl<std::deque<T, Allocator>>; };
354
355//---------------------------------------------------
356
357template <class>
358class wrapper;
359
360template <class Result>
361class wrapper<result<Result>> {
362public:
363 using response_type = result<Result>;
364private:
365 response_type* result_;
366 typename impl_map<Result>::type impl_;
367
368 bool set_if_resp3_error(resp3::basic_node<std::string_view> const& nd) noexcept
369 {
370 switch (nd.data_type) {
374 *result_ = error{nd.data_type, {std::cbegin(nd.value), std::cend(nd.value)}};
375 return true;
376 default:
377 return false;
378 }
379 }
380
381public:
382 explicit wrapper(response_type* t = nullptr) : result_(t)
383 {
384 if (result_) {
385 result_->value() = Result{};
386 impl_.on_value_available(result_->value());
387 }
388 }
389
390 void
391 operator()(
392 resp3::basic_node<std::string_view> const& nd,
393 system::error_code& ec)
394 {
395 BOOST_ASSERT_MSG(!!result_, "Unexpected null pointer");
396
397 if (result_->has_error())
398 return;
399
400 if (set_if_resp3_error(nd))
401 return;
402
403 BOOST_ASSERT(result_);
404 impl_(result_->value(), nd, ec);
405 }
406};
407
408template <class T>
409class wrapper<result<std::optional<T>>> {
410public:
411 using response_type = result<std::optional<T>>;
412
413private:
414 response_type* result_;
415 typename impl_map<T>::type impl_{};
416
417 bool set_if_resp3_error(resp3::basic_node<std::string_view> const& nd) noexcept
418 {
419 switch (nd.data_type) {
422 *result_ = error{nd.data_type, {std::cbegin(nd.value), std::cend(nd.value)}};
423 return true;
424 default:
425 return false;
426 }
427 }
428
429public:
430 explicit wrapper(response_type* o = nullptr) : result_(o) {}
431
432 void
433 operator()(
434 resp3::basic_node<std::string_view> const& nd,
435 system::error_code& ec)
436 {
437 BOOST_ASSERT_MSG(!!result_, "Unexpected null pointer");
438
439 if (result_->has_error())
440 return;
441
442 if (set_if_resp3_error(nd))
443 return;
444
445 if (nd.data_type == resp3::type::null)
446 return;
447
448 if (!result_->value().has_value()) {
449 result_->value() = T{};
450 impl_.on_value_available(result_->value().value());
451 }
452
453 impl_(result_->value().value(), nd, ec);
454 }
455};
456
457} // boost::redis::adapter::detail
458
459#endif // BOOST_REDIS_ADAPTER_ADAPTERS_HPP
type
RESP3 data types.
Definition: type.hpp:23
system::result< Value, error > result
Stores response to individual Redis commands.
Definition: result.hpp:56
error
Generic errors.
Definition: error.hpp:18
@ expects_resp3_set
Expects a set aggregate but got something else.
@ incompatible_size
Aggregate container has incompatible size.
@ not_a_number
Can't parse the string as a number.
@ nested_aggregate_not_supported
Nested response not supported.
@ expects_resp3_map
Expects a map but got other aggregate.
@ not_a_double
Not a double.
@ expects_resp3_aggregate
Expects aggregate.
@ expects_resp3_simple_type
Expects a simple RESP3 type but got an aggregate.