1 /**************************************************************************
2    Copyright (c) 2017 sewenew
3 
4    Licensed under the Apache License, Version 2.0 (the "License");
5    you may not use this file except in compliance with the License.
6    You may obtain a copy of the License at
7 
8        http://www.apache.org/licenses/LICENSE-2.0
9 
10    Unless required by applicable law or agreed to in writing, software
11    distributed under the License is distributed on an "AS IS" BASIS,
12    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13    See the License for the specific language governing permissions and
14    limitations under the License.
15  *************************************************************************/
16 
17 #ifndef SEWENEW_REDISPLUSPLUS_REPLY_H
18 #define SEWENEW_REDISPLUSPLUS_REPLY_H
19 
20 #include <cassert>
21 #include <string>
22 #include <memory>
23 #include <functional>
24 #include <tuple>
25 #include <hiredis/hiredis.h>
26 #include "errors.h"
27 #include "utils.h"
28 
29 namespace sw {
30 
31 namespace redis {
32 
33 struct ReplyDeleter {
operatorReplyDeleter34     void operator()(redisReply *reply) const {
35         if (reply != nullptr) {
36             freeReplyObject(reply);
37         }
38     }
39 };
40 
41 using ReplyUPtr = std::unique_ptr<redisReply, ReplyDeleter>;
42 
43 namespace reply {
44 
45 template <typename T>
46 struct ParseTag {};
47 
48 template <typename T>
parse(redisReply & reply)49 inline T parse(redisReply &reply) {
50     return parse(ParseTag<T>(), reply);
51 }
52 
53 void parse(ParseTag<void>, redisReply &reply);
54 
55 std::string parse(ParseTag<std::string>, redisReply &reply);
56 
57 long long parse(ParseTag<long long>, redisReply &reply);
58 
59 double parse(ParseTag<double>, redisReply &reply);
60 
61 bool parse(ParseTag<bool>, redisReply &reply);
62 
63 template <typename T>
64 Optional<T> parse(ParseTag<Optional<T>>, redisReply &reply);
65 
66 template <typename T, typename U>
67 std::pair<T, U> parse(ParseTag<std::pair<T, U>>, redisReply &reply);
68 
69 template <typename ...Args>
70 std::tuple<Args...> parse(ParseTag<std::tuple<Args...>>, redisReply &reply);
71 
72 template <typename T, typename std::enable_if<IsSequenceContainer<T>::value, int>::type = 0>
73 T parse(ParseTag<T>, redisReply &reply);
74 
75 template <typename T, typename std::enable_if<IsAssociativeContainer<T>::value, int>::type = 0>
76 T parse(ParseTag<T>, redisReply &reply);
77 
78 template <typename Output>
79 long long parse_scan_reply(redisReply &reply, Output output);
80 
is_error(redisReply & reply)81 inline bool is_error(redisReply &reply) {
82     return reply.type == REDIS_REPLY_ERROR;
83 }
84 
is_nil(redisReply & reply)85 inline bool is_nil(redisReply &reply) {
86     return reply.type == REDIS_REPLY_NIL;
87 }
88 
is_string(redisReply & reply)89 inline bool is_string(redisReply &reply) {
90     return reply.type == REDIS_REPLY_STRING;
91 }
92 
is_status(redisReply & reply)93 inline bool is_status(redisReply &reply) {
94     return reply.type == REDIS_REPLY_STATUS;
95 }
96 
is_integer(redisReply & reply)97 inline bool is_integer(redisReply &reply) {
98     return reply.type == REDIS_REPLY_INTEGER;
99 }
100 
is_array(redisReply & reply)101 inline bool is_array(redisReply &reply) {
102     return reply.type == REDIS_REPLY_ARRAY;
103 }
104 
105 std::string to_status(redisReply &reply);
106 
107 template <typename Output>
108 void to_array(redisReply &reply, Output output);
109 
110 // Rewrite set reply to bool type
111 void rewrite_set_reply(redisReply &reply);
112 
113 // Rewrite georadius reply to OptionalLongLong type
114 void rewrite_georadius_reply(redisReply &reply);
115 
116 template <typename Output>
117 auto parse_xpending_reply(redisReply &reply, Output output)
118     -> std::tuple<long long, OptionalString, OptionalString>;
119 
120 }
121 
122 // Inline implementations.
123 
124 namespace reply {
125 
126 namespace detail {
127 
128 template <typename Output>
to_array(redisReply & reply,Output output)129 void to_array(redisReply &reply, Output output) {
130     if (!is_array(reply)) {
131         throw ProtoError("Expect ARRAY reply");
132     }
133 
134     if (reply.element == nullptr) {
135         // Empty array.
136         return;
137     }
138 
139     for (std::size_t idx = 0; idx != reply.elements; ++idx) {
140         auto *sub_reply = reply.element[idx];
141         if (sub_reply == nullptr) {
142             throw ProtoError("Null array element reply");
143         }
144 
145         *output = parse<typename IterType<Output>::type>(*sub_reply);
146 
147         ++output;
148     }
149 }
150 
151 bool is_flat_array(redisReply &reply);
152 
153 template <typename Output>
to_flat_array(redisReply & reply,Output output)154 void to_flat_array(redisReply &reply, Output output) {
155     if (reply.element == nullptr) {
156         // Empty array.
157         return;
158     }
159 
160     if (reply.elements % 2 != 0) {
161         throw ProtoError("Not string pair array reply");
162     }
163 
164     for (std::size_t idx = 0; idx != reply.elements; idx += 2) {
165         auto *key_reply = reply.element[idx];
166         auto *val_reply = reply.element[idx + 1];
167         if (key_reply == nullptr || val_reply == nullptr) {
168             throw ProtoError("Null string array reply");
169         }
170 
171         using Pair = typename IterType<Output>::type;
172         using FirstType = typename std::decay<typename Pair::first_type>::type;
173         using SecondType = typename std::decay<typename Pair::second_type>::type;
174         *output = std::make_pair(parse<FirstType>(*key_reply),
175                                     parse<SecondType>(*val_reply));
176 
177         ++output;
178     }
179 }
180 
181 template <typename Output>
to_array(std::true_type,redisReply & reply,Output output)182 void to_array(std::true_type, redisReply &reply, Output output) {
183     if (is_flat_array(reply)) {
184         to_flat_array(reply, output);
185     } else {
186         to_array(reply, output);
187     }
188 }
189 
190 template <typename Output>
to_array(std::false_type,redisReply & reply,Output output)191 void to_array(std::false_type, redisReply &reply, Output output) {
192     to_array(reply, output);
193 }
194 
195 template <typename T>
parse_tuple(redisReply ** reply,std::size_t idx)196 std::tuple<T> parse_tuple(redisReply **reply, std::size_t idx) {
197     assert(reply != nullptr);
198 
199     auto *sub_reply = reply[idx];
200     if (sub_reply == nullptr) {
201         throw ProtoError("Null reply");
202     }
203 
204     return std::make_tuple(parse<T>(*sub_reply));
205 }
206 
207 template <typename T, typename ...Args>
208 auto parse_tuple(redisReply **reply, std::size_t idx) ->
209     typename std::enable_if<sizeof...(Args) != 0, std::tuple<T, Args...>>::type {
210     assert(reply != nullptr);
211 
212     return std::tuple_cat(parse_tuple<T>(reply, idx),
213                             parse_tuple<Args...>(reply, idx + 1));
214 }
215 
216 }
217 
218 template <typename T>
parse(ParseTag<Optional<T>>,redisReply & reply)219 Optional<T> parse(ParseTag<Optional<T>>, redisReply &reply) {
220     if (reply::is_nil(reply)) {
221         return {};
222     }
223 
224     return Optional<T>(parse<T>(reply));
225 }
226 
227 template <typename T, typename U>
parse(ParseTag<std::pair<T,U>>,redisReply & reply)228 std::pair<T, U> parse(ParseTag<std::pair<T, U>>, redisReply &reply) {
229     if (!is_array(reply)) {
230         throw ProtoError("Expect ARRAY reply");
231     }
232 
233     if (reply.elements != 2) {
234         throw ProtoError("NOT key-value PAIR reply");
235     }
236 
237     if (reply.element == nullptr) {
238         throw ProtoError("Null PAIR reply");
239     }
240 
241     auto *first = reply.element[0];
242     auto *second = reply.element[1];
243     if (first == nullptr || second == nullptr) {
244         throw ProtoError("Null pair reply");
245     }
246 
247     return std::make_pair(parse<typename std::decay<T>::type>(*first),
248                             parse<typename std::decay<U>::type>(*second));
249 }
250 
251 template <typename ...Args>
parse(ParseTag<std::tuple<Args...>>,redisReply & reply)252 std::tuple<Args...> parse(ParseTag<std::tuple<Args...>>, redisReply &reply) {
253     constexpr auto size = sizeof...(Args);
254 
255     static_assert(size > 0, "DO NOT support parsing tuple with 0 element");
256 
257     if (!is_array(reply)) {
258         throw ProtoError("Expect ARRAY reply");
259     }
260 
261     if (reply.elements != size) {
262         throw ProtoError("Expect tuple reply with " + std::to_string(size) + "elements");
263     }
264 
265     if (reply.element == nullptr) {
266         throw ProtoError("Null TUPLE reply");
267     }
268 
269     return detail::parse_tuple<Args...>(reply.element, 0);
270 }
271 
272 template <typename T, typename std::enable_if<IsSequenceContainer<T>::value, int>::type>
parse(ParseTag<T>,redisReply & reply)273 T parse(ParseTag<T>, redisReply &reply) {
274     if (!is_array(reply)) {
275         throw ProtoError("Expect ARRAY reply");
276     }
277 
278     T container;
279 
280     to_array(reply, std::back_inserter(container));
281 
282     return container;
283 }
284 
285 template <typename T, typename std::enable_if<IsAssociativeContainer<T>::value, int>::type>
parse(ParseTag<T>,redisReply & reply)286 T parse(ParseTag<T>, redisReply &reply) {
287     if (!is_array(reply)) {
288         throw ProtoError("Expect ARRAY reply");
289     }
290 
291     T container;
292 
293     to_array(reply, std::inserter(container, container.end()));
294 
295     return container;
296 }
297 
298 template <typename Output>
parse_scan_reply(redisReply & reply,Output output)299 long long parse_scan_reply(redisReply &reply, Output output) {
300     if (reply.elements != 2 || reply.element == nullptr) {
301         throw ProtoError("Invalid scan reply");
302     }
303 
304     auto *cursor_reply = reply.element[0];
305     auto *data_reply = reply.element[1];
306     if (cursor_reply == nullptr || data_reply == nullptr) {
307         throw ProtoError("Invalid cursor reply or data reply");
308     }
309 
310     auto cursor_str = reply::parse<std::string>(*cursor_reply);
311     auto new_cursor = 0;
312     try {
313         new_cursor = std::stoll(cursor_str);
314     } catch (const std::exception &e) {
315         throw ProtoError("Invalid cursor reply: " + cursor_str);
316     }
317 
318     reply::to_array(*data_reply, output);
319 
320     return new_cursor;
321 }
322 
323 template <typename Output>
to_array(redisReply & reply,Output output)324 void to_array(redisReply &reply, Output output) {
325     if (!is_array(reply)) {
326         throw ProtoError("Expect ARRAY reply");
327     }
328 
329     detail::to_array(typename IsKvPairIter<Output>::type(), reply, output);
330 }
331 
332 template <typename Output>
333 auto parse_xpending_reply(redisReply &reply, Output output)
334     -> std::tuple<long long, OptionalString, OptionalString> {
335     if (!is_array(reply) || reply.elements != 4) {
336         throw ProtoError("expect array reply with 4 elements");
337     }
338 
339     for (std::size_t idx = 0; idx != reply.elements; ++idx) {
340         if (reply.element[idx] == nullptr) {
341             throw ProtoError("null array reply");
342         }
343     }
344 
345     auto num = parse<long long>(*(reply.element[0]));
346     auto start = parse<OptionalString>(*(reply.element[1]));
347     auto end = parse<OptionalString>(*(reply.element[2]));
348 
349     auto &entry_reply = *(reply.element[3]);
350     if (!is_nil(entry_reply)) {
351         to_array(entry_reply, output);
352     }
353 
354     return std::make_tuple(num, std::move(start), std::move(end));
355 }
356 
357 }
358 
359 }
360 
361 }
362 
363 #endif // end SEWENEW_REDISPLUSPLUS_REPLY_H
364