/************************************************************************** Copyright (c) 2017 sewenew Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. *************************************************************************/ #ifndef SEWENEW_REDISPLUSPLUS_REPLY_H #define SEWENEW_REDISPLUSPLUS_REPLY_H #include #include #include #include #include #include #include "errors.h" #include "utils.h" namespace sw { namespace redis { struct ReplyDeleter { void operator()(redisReply *reply) const { if (reply != nullptr) { freeReplyObject(reply); } } }; using ReplyUPtr = std::unique_ptr; namespace reply { template struct ParseTag {}; template inline T parse(redisReply &reply) { return parse(ParseTag(), reply); } void parse(ParseTag, redisReply &reply); std::string parse(ParseTag, redisReply &reply); long long parse(ParseTag, redisReply &reply); double parse(ParseTag, redisReply &reply); bool parse(ParseTag, redisReply &reply); template Optional parse(ParseTag>, redisReply &reply); template std::pair parse(ParseTag>, redisReply &reply); template std::tuple parse(ParseTag>, redisReply &reply); template ::value, int>::type = 0> T parse(ParseTag, redisReply &reply); template ::value, int>::type = 0> T parse(ParseTag, redisReply &reply); template long long parse_scan_reply(redisReply &reply, Output output); inline bool is_error(redisReply &reply) { return reply.type == REDIS_REPLY_ERROR; } inline bool is_nil(redisReply &reply) { return reply.type == REDIS_REPLY_NIL; } inline bool is_string(redisReply &reply) { return reply.type == REDIS_REPLY_STRING; } inline bool is_status(redisReply &reply) { return reply.type == REDIS_REPLY_STATUS; } inline bool is_integer(redisReply &reply) { return reply.type == REDIS_REPLY_INTEGER; } inline bool is_array(redisReply &reply) { return reply.type == REDIS_REPLY_ARRAY; } std::string to_status(redisReply &reply); template void to_array(redisReply &reply, Output output); // Rewrite set reply to bool type void rewrite_set_reply(redisReply &reply); // Rewrite georadius reply to OptionalLongLong type void rewrite_georadius_reply(redisReply &reply); template auto parse_xpending_reply(redisReply &reply, Output output) -> std::tuple; } // Inline implementations. namespace reply { namespace detail { template void to_array(redisReply &reply, Output output) { if (!is_array(reply)) { throw ProtoError("Expect ARRAY reply"); } if (reply.element == nullptr) { // Empty array. return; } for (std::size_t idx = 0; idx != reply.elements; ++idx) { auto *sub_reply = reply.element[idx]; if (sub_reply == nullptr) { throw ProtoError("Null array element reply"); } *output = parse::type>(*sub_reply); ++output; } } bool is_flat_array(redisReply &reply); template void to_flat_array(redisReply &reply, Output output) { if (reply.element == nullptr) { // Empty array. return; } if (reply.elements % 2 != 0) { throw ProtoError("Not string pair array reply"); } for (std::size_t idx = 0; idx != reply.elements; idx += 2) { auto *key_reply = reply.element[idx]; auto *val_reply = reply.element[idx + 1]; if (key_reply == nullptr || val_reply == nullptr) { throw ProtoError("Null string array reply"); } using Pair = typename IterType::type; using FirstType = typename std::decay::type; using SecondType = typename std::decay::type; *output = std::make_pair(parse(*key_reply), parse(*val_reply)); ++output; } } template void to_array(std::true_type, redisReply &reply, Output output) { if (is_flat_array(reply)) { to_flat_array(reply, output); } else { to_array(reply, output); } } template void to_array(std::false_type, redisReply &reply, Output output) { to_array(reply, output); } template std::tuple parse_tuple(redisReply **reply, std::size_t idx) { assert(reply != nullptr); auto *sub_reply = reply[idx]; if (sub_reply == nullptr) { throw ProtoError("Null reply"); } return std::make_tuple(parse(*sub_reply)); } template auto parse_tuple(redisReply **reply, std::size_t idx) -> typename std::enable_if>::type { assert(reply != nullptr); return std::tuple_cat(parse_tuple(reply, idx), parse_tuple(reply, idx + 1)); } } template Optional parse(ParseTag>, redisReply &reply) { if (reply::is_nil(reply)) { return {}; } return Optional(parse(reply)); } template std::pair parse(ParseTag>, redisReply &reply) { if (!is_array(reply)) { throw ProtoError("Expect ARRAY reply"); } if (reply.elements != 2) { throw ProtoError("NOT key-value PAIR reply"); } if (reply.element == nullptr) { throw ProtoError("Null PAIR reply"); } auto *first = reply.element[0]; auto *second = reply.element[1]; if (first == nullptr || second == nullptr) { throw ProtoError("Null pair reply"); } return std::make_pair(parse::type>(*first), parse::type>(*second)); } template std::tuple parse(ParseTag>, redisReply &reply) { constexpr auto size = sizeof...(Args); static_assert(size > 0, "DO NOT support parsing tuple with 0 element"); if (!is_array(reply)) { throw ProtoError("Expect ARRAY reply"); } if (reply.elements != size) { throw ProtoError("Expect tuple reply with " + std::to_string(size) + "elements"); } if (reply.element == nullptr) { throw ProtoError("Null TUPLE reply"); } return detail::parse_tuple(reply.element, 0); } template ::value, int>::type> T parse(ParseTag, redisReply &reply) { if (!is_array(reply)) { throw ProtoError("Expect ARRAY reply"); } T container; to_array(reply, std::back_inserter(container)); return container; } template ::value, int>::type> T parse(ParseTag, redisReply &reply) { if (!is_array(reply)) { throw ProtoError("Expect ARRAY reply"); } T container; to_array(reply, std::inserter(container, container.end())); return container; } template long long parse_scan_reply(redisReply &reply, Output output) { if (reply.elements != 2 || reply.element == nullptr) { throw ProtoError("Invalid scan reply"); } auto *cursor_reply = reply.element[0]; auto *data_reply = reply.element[1]; if (cursor_reply == nullptr || data_reply == nullptr) { throw ProtoError("Invalid cursor reply or data reply"); } auto cursor_str = reply::parse(*cursor_reply); auto new_cursor = 0; try { new_cursor = std::stoll(cursor_str); } catch (const std::exception &e) { throw ProtoError("Invalid cursor reply: " + cursor_str); } reply::to_array(*data_reply, output); return new_cursor; } template void to_array(redisReply &reply, Output output) { if (!is_array(reply)) { throw ProtoError("Expect ARRAY reply"); } detail::to_array(typename IsKvPairIter::type(), reply, output); } template auto parse_xpending_reply(redisReply &reply, Output output) -> std::tuple { if (!is_array(reply) || reply.elements != 4) { throw ProtoError("expect array reply with 4 elements"); } for (std::size_t idx = 0; idx != reply.elements; ++idx) { if (reply.element[idx] == nullptr) { throw ProtoError("null array reply"); } } auto num = parse(*(reply.element[0])); auto start = parse(*(reply.element[1])); auto end = parse(*(reply.element[2])); auto &entry_reply = *(reply.element[3]); if (!is_nil(entry_reply)) { to_array(entry_reply, output); } return std::make_tuple(num, std::move(start), std::move(end)); } } } } #endif // end SEWENEW_REDISPLUSPLUS_REPLY_H