Loading...
Searching...
No Matches
adapters.hpp
1/* Copyright (c) 2018-2024 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/adapter/result.hpp>
11#include <boost/redis/error.hpp>
12#include <boost/redis/resp3/node.hpp>
13#include <boost/redis/resp3/serialization.hpp>
14#include <boost/redis/resp3/type.hpp>
15
16#include <boost/assert.hpp>
17
18#include <array>
19#include <charconv>
20#include <deque>
21#include <forward_list>
22#include <list>
23#include <map>
24#include <optional>
25#include <set>
26#include <string_view>
27#include <system_error>
28#include <type_traits>
29#include <unordered_map>
30#include <unordered_set>
31#include <vector>
32
33// See https://stackoverflow.com/a/31658120/1077832
34#ifdef _LIBCPP_VERSION
35#else
36#include <cstdlib>
37#endif
38
39namespace boost::redis::adapter::detail {
40
41// Exclude bools, char and charXY_t types
42template <class T> struct is_integral_number : std::is_integral<T> { };
43template <> struct is_integral_number<bool> : std::false_type { };
44template <> struct is_integral_number<char> : std::false_type { };
45template <> struct is_integral_number<char16_t> : std::false_type { };
46template <> struct is_integral_number<char32_t> : std::false_type { };
47template <> struct is_integral_number<wchar_t> : std::false_type { };
48#ifdef __cpp_char8_t
49template <> struct is_integral_number<char8_t> : std::false_type { };
50#endif
51
52template <class T, bool = is_integral_number<T>::value>
53struct converter;
54
55template <class T>
56struct converter<T, true> {
57 template <class String>
58 static void apply(T& i, resp3::basic_node<String> const& node, system::error_code& ec)
59 {
60 auto const res = std::from_chars(node.value.data(), node.value.data() + node.value.size(), i);
61 if (res.ec != std::errc())
63 }
64};
65
66template <>
67struct converter<bool, false> {
68 template <class String>
69 static void apply(bool& t, resp3::basic_node<String> const& node, system::error_code&)
70 {
71 t = *node.value.data() == 't';
72 }
73};
74
75template <>
76struct converter<double, false> {
77 template <class String>
78 static void apply(double& d, resp3::basic_node<String> const& node, system::error_code& ec)
79 {
80#ifdef _LIBCPP_VERSION
81 // The string in node.value is not null terminated and we also
82 // don't know if there is enough space at the end for a null
83 // char. The easiest thing to do is to create a temporary.
84 std::string const tmp{node.value.data(), node.value.data() + node.value.size()};
85 char* end{};
86 d = std::strtod(tmp.data(), &end);
87 if (d == HUGE_VAL || d == 0)
89#else
90 auto const res = std::from_chars(node.value.data(), node.value.data() + node.value.size(), d);
91 if (res.ec != std::errc())
93#endif // _LIBCPP_VERSION
94 }
95};
96
97template <class CharT, class Traits, class Allocator>
98struct converter<std::basic_string<CharT, Traits, Allocator>, false> {
99 template <class String>
100 static void apply(
101 std::basic_string<CharT, Traits, Allocator>& s,
102 resp3::basic_node<String> const& node,
103 system::error_code&)
104 {
105 s.append(node.value.data(), node.value.size());
106 }
107};
108
109template <class T>
110struct from_bulk_impl {
111 template <class String>
112 static void apply(T& t, resp3::basic_node<String> const& node, system::error_code& ec)
113 {
114 converter<T>::apply(t, node, ec);
115 }
116};
117
118template <class T>
119struct from_bulk_impl<std::optional<T>> {
120 template <class String>
121 static void apply(
122 std::optional<T>& op,
123 resp3::basic_node<String> const& node,
124 system::error_code& ec)
125 {
126 if (node.data_type != resp3::type::null) {
127 op.emplace(T{});
128 converter<T>::apply(op.value(), node, ec);
129 }
130 }
131};
132
133template <class T, class String>
134void boost_redis_from_bulk(T& t, resp3::basic_node<String> const& node, system::error_code& ec)
135{
136 from_bulk_impl<T>::apply(t, node, ec);
137}
138
139//================================================
140
141template <class Result>
142class general_aggregate {
143private:
144 Result* result_;
145
146public:
147 explicit general_aggregate(Result* c = nullptr)
148 : result_(c)
149 { }
150 template <class String>
151 void operator()(resp3::basic_node<String> const& nd, system::error_code&)
152 {
153 BOOST_ASSERT_MSG(!!result_, "Unexpected null pointer");
154 switch (nd.data_type) {
157 *result_ = error{
158 nd.data_type,
159 std::string{std::cbegin(nd.value), std::cend(nd.value)}
160 };
161 break;
162 default:
163 result_->value().push_back({
164 nd.data_type,
165 nd.aggregate_size,
166 nd.depth,
167 std::string{std::cbegin(nd.value), std::cend(nd.value)}
168 });
169 }
170 }
171};
172
173template <class Node>
174class general_simple {
175private:
176 Node* result_;
177
178public:
179 explicit general_simple(Node* t = nullptr)
180 : result_(t)
181 { }
182
183 template <class String>
184 void operator()(resp3::basic_node<String> const& nd, system::error_code&)
185 {
186 BOOST_ASSERT_MSG(!!result_, "Unexpected null pointer");
187 switch (nd.data_type) {
190 *result_ = error{
191 nd.data_type,
192 std::string{std::cbegin(nd.value), std::cend(nd.value)}
193 };
194 break;
195 default:
196 result_->value().data_type = nd.data_type;
197 result_->value().aggregate_size = nd.aggregate_size;
198 result_->value().depth = nd.depth;
199 result_->value().value.assign(nd.value.data(), nd.value.size());
200 }
201 }
202};
203
204template <class Result>
205class simple_impl {
206public:
207 void on_value_available(Result&) { }
208
209 template <class String>
210 void operator()(Result& result, resp3::basic_node<String> const& node, system::error_code& ec)
211 {
212 if (is_aggregate(node.data_type)) {
214 return;
215 }
216
217 boost_redis_from_bulk(result, node, ec);
218 }
219};
220
221template <class Result>
222class set_impl {
223private:
224 typename Result::iterator hint_;
225
226public:
227 void on_value_available(Result& result) { hint_ = std::end(result); }
228
229 template <class String>
230 void operator()(Result& result, resp3::basic_node<String> const& nd, system::error_code& ec)
231 {
232 if (is_aggregate(nd.data_type)) {
233 if (nd.data_type != resp3::type::set)
235 return;
236 }
237
238 BOOST_ASSERT(nd.aggregate_size == 1);
239
240 if (nd.depth < 1) {
242 return;
243 }
244
245 typename Result::key_type obj;
246 boost_redis_from_bulk(obj, nd, ec);
247 hint_ = result.insert(hint_, std::move(obj));
248 }
249};
250
251template <class Result>
252class map_impl {
253private:
254 typename Result::iterator current_;
255 bool on_key_ = true;
256
257public:
258 void on_value_available(Result& result) { current_ = std::end(result); }
259
260 template <class String>
261 void operator()(Result& result, resp3::basic_node<String> const& nd, system::error_code& ec)
262 {
263 if (is_aggregate(nd.data_type)) {
264 if (element_multiplicity(nd.data_type) != 2)
266 return;
267 }
268
269 BOOST_ASSERT(nd.aggregate_size == 1);
270
271 if (nd.depth < 1) {
273 return;
274 }
275
276 if (on_key_) {
277 typename Result::key_type obj;
278 boost_redis_from_bulk(obj, nd, ec);
279 current_ = result.insert(current_, {std::move(obj), {}});
280 } else {
281 typename Result::mapped_type obj;
282 boost_redis_from_bulk(obj, nd, ec);
283 current_->second = std::move(obj);
284 }
285
286 on_key_ = !on_key_;
287 }
288};
289
290template <class Result>
291class vector_impl {
292public:
293 void on_value_available(Result&) { }
294
295 template <class String>
296 void operator()(Result& result, resp3::basic_node<String> const& nd, system::error_code& ec)
297 {
298 if (is_aggregate(nd.data_type)) {
299 auto const m = element_multiplicity(nd.data_type);
300 result.reserve(result.size() + m * nd.aggregate_size);
301 } else {
302 result.push_back({});
303 boost_redis_from_bulk(result.back(), nd, ec);
304 }
305 }
306};
307
308template <class Result>
309class array_impl {
310private:
311 int i_ = -1;
312
313public:
314 void on_value_available(Result&) { }
315
316 template <class String>
317 void operator()(Result& result, resp3::basic_node<String> const& nd, system::error_code& ec)
318 {
319 if (is_aggregate(nd.data_type)) {
320 if (i_ != -1) {
322 return;
323 }
324
325 if (result.size() != nd.aggregate_size * element_multiplicity(nd.data_type)) {
327 return;
328 }
329 } else {
330 if (i_ == -1) {
332 return;
333 }
334
335 BOOST_ASSERT(nd.aggregate_size == 1);
336 boost_redis_from_bulk(result.at(i_), nd, ec);
337 }
338
339 ++i_;
340 }
341};
342
343template <class Result>
344struct list_impl {
345 void on_value_available(Result&) { }
346
347 template <class String>
348 void operator()(Result& result, resp3::basic_node<String> const& nd, system::error_code& ec)
349 {
350 if (!is_aggregate(nd.data_type)) {
351 BOOST_ASSERT(nd.aggregate_size == 1);
352 if (nd.depth < 1) {
354 return;
355 }
356
357 result.push_back({});
358 boost_redis_from_bulk(result.back(), nd, ec);
359 }
360 }
361};
362
363//---------------------------------------------------
364
365template <class T>
366struct impl_map {
367 using type = simple_impl<T>;
368};
369
370template <class Key, class Compare, class Allocator>
371struct impl_map<std::set<Key, Compare, Allocator>> {
372 using type = set_impl<std::set<Key, Compare, Allocator>>;
373};
374
375template <class Key, class Compare, class Allocator>
376struct impl_map<std::multiset<Key, Compare, Allocator>> {
377 using type = set_impl<std::multiset<Key, Compare, Allocator>>;
378};
379
380template <class Key, class Hash, class KeyEqual, class Allocator>
381struct impl_map<std::unordered_set<Key, Hash, KeyEqual, Allocator>> {
382 using type = set_impl<std::unordered_set<Key, Hash, KeyEqual, Allocator>>;
383};
384
385template <class Key, class Hash, class KeyEqual, class Allocator>
386struct impl_map<std::unordered_multiset<Key, Hash, KeyEqual, Allocator>> {
387 using type = set_impl<std::unordered_multiset<Key, Hash, KeyEqual, Allocator>>;
388};
389
390template <class Key, class T, class Compare, class Allocator>
391struct impl_map<std::map<Key, T, Compare, Allocator>> {
392 using type = map_impl<std::map<Key, T, Compare, Allocator>>;
393};
394
395template <class Key, class T, class Compare, class Allocator>
396struct impl_map<std::multimap<Key, T, Compare, Allocator>> {
397 using type = map_impl<std::multimap<Key, T, Compare, Allocator>>;
398};
399
400template <class Key, class Hash, class KeyEqual, class Allocator>
401struct impl_map<std::unordered_map<Key, Hash, KeyEqual, Allocator>> {
402 using type = map_impl<std::unordered_map<Key, Hash, KeyEqual, Allocator>>;
403};
404
405template <class Key, class Hash, class KeyEqual, class Allocator>
406struct impl_map<std::unordered_multimap<Key, Hash, KeyEqual, Allocator>> {
407 using type = map_impl<std::unordered_multimap<Key, Hash, KeyEqual, Allocator>>;
408};
409
410template <class T, class Allocator>
411struct impl_map<std::vector<T, Allocator>> {
412 using type = vector_impl<std::vector<T, Allocator>>;
413};
414
415template <class T, std::size_t N>
416struct impl_map<std::array<T, N>> {
417 using type = array_impl<std::array<T, N>>;
418};
419
420template <class T, class Allocator>
421struct impl_map<std::list<T, Allocator>> {
422 using type = list_impl<std::list<T, Allocator>>;
423};
424
425template <class T, class Allocator>
426struct impl_map<std::deque<T, Allocator>> {
427 using type = list_impl<std::deque<T, Allocator>>;
428};
429
430//---------------------------------------------------
431
432template <class>
433class wrapper;
434
435template <class T>
436class wrapper<result<T>> {
437public:
438 using response_type = result<T>;
439
440private:
441 response_type* result_;
442 typename impl_map<T>::type impl_;
443 bool called_once_ = false;
444
445 template <class String>
446 bool set_if_resp3_error(resp3::basic_node<String> const& nd) noexcept
447 {
448 switch (nd.data_type) {
452 *result_ = error{
453 nd.data_type,
454 {std::cbegin(nd.value), std::cend(nd.value)}
455 };
456 return true;
457 default: return false;
458 }
459 }
460
461public:
462 explicit wrapper(response_type* t = nullptr)
463 : result_(t)
464 {
465 if (result_) {
466 result_->value() = T{};
467 impl_.on_value_available(result_->value());
468 }
469 }
470
471 template <class String>
472 void operator()(resp3::basic_node<String> const& nd, system::error_code& ec)
473 {
474 BOOST_ASSERT_MSG(!!result_, "Unexpected null pointer");
475
476 if (result_->has_error())
477 return;
478
479 if (!std::exchange(called_once_, true) && set_if_resp3_error(nd))
480 return;
481
482 BOOST_ASSERT(result_);
483 impl_(result_->value(), nd, ec);
484 }
485};
486
487template <class T>
488class wrapper<result<std::optional<T>>> {
489public:
490 using response_type = result<std::optional<T>>;
491
492private:
493 response_type* result_;
494 typename impl_map<T>::type impl_{};
495 bool called_once_ = false;
496
497 template <class String>
498 bool set_if_resp3_error(resp3::basic_node<String> const& nd) noexcept
499 {
500 switch (nd.data_type) {
503 *result_ = error{
504 nd.data_type,
505 {std::cbegin(nd.value), std::cend(nd.value)}
506 };
507 return true;
508 default: return false;
509 }
510 }
511
512public:
513 explicit wrapper(response_type* o = nullptr)
514 : result_(o)
515 { }
516
517 template <class String>
518 void operator()(resp3::basic_node<String> const& nd, system::error_code& ec)
519 {
520 BOOST_ASSERT_MSG(!!result_, "Unexpected null pointer");
521
522 if (result_->has_error())
523 return;
524
525 if (set_if_resp3_error(nd))
526 return;
527
528 if (!std::exchange(called_once_, true) && nd.data_type == resp3::type::null)
529 return;
530
531 if (!result_->value().has_value()) {
532 result_->value() = T{};
533 impl_.on_value_available(result_->value().value());
534 }
535
536 impl_(result_->value().value(), nd, ec);
537 }
538};
539
540} // namespace boost::redis::adapter::detail
541
542#endif // BOOST_REDIS_ADAPTER_ADAPTERS_HPP
error
Generic errors.
Definition error.hpp:18
system::result< Value, error > result
Stores response to individual Redis commands.
Definition result.hpp:54
basic_node< std::string > node
A node in the response tree that owns its data.
Definition node.hpp:62
@ 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.