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