/* Copyright (c) 2018-2024 Marcelo Zimbres Silva (mzimbres@gmail.com) * * Distributed under the Boost Software License, Version 1.0. (See * accompanying file LICENSE.txt) */ #ifndef BOOST_REDIS_ADAPTER_ADAPTERS_HPP #define BOOST_REDIS_ADAPTER_ADAPTERS_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // See https://stackoverflow.com/a/31658120/1077832 #ifdef _LIBCPP_VERSION #else #include #endif namespace boost::redis::adapter::detail { // Exclude bools, char and charXY_t types template struct is_integral_number : std::is_integral { }; template <> struct is_integral_number : std::false_type { }; template <> struct is_integral_number : std::false_type { }; template <> struct is_integral_number : std::false_type { }; template <> struct is_integral_number : std::false_type { }; template <> struct is_integral_number : std::false_type { }; #ifdef __cpp_char8_t template <> struct is_integral_number : std::false_type { }; #endif template ::value> struct converter; template struct converter { template static void apply(T& i, resp3::basic_node const& node, system::error_code& ec) { auto const res = std::from_chars(node.value.data(), node.value.data() + node.value.size(), i); if (res.ec != std::errc()) ec = redis::error::not_a_number; } }; template <> struct converter { template static void apply(bool& t, resp3::basic_node const& node, system::error_code&) { t = *node.value.data() == 't'; } }; template <> struct converter { template static void apply(double& d, resp3::basic_node const& node, system::error_code& ec) { #ifdef _LIBCPP_VERSION // The string in node.value is not null terminated and we also // don't know if there is enough space at the end for a null // char. The easiest thing to do is to create a temporary. std::string const tmp{node.value.data(), node.value.data() + node.value.size()}; char* end{}; d = std::strtod(tmp.data(), &end); if (d == HUGE_VAL || d == 0) ec = redis::error::not_a_double; #else auto const res = std::from_chars(node.value.data(), node.value.data() + node.value.size(), d); if (res.ec != std::errc()) ec = redis::error::not_a_double; #endif // _LIBCPP_VERSION } }; template struct converter, false> { template static void apply( std::basic_string& s, resp3::basic_node const& node, system::error_code&) { s.append(node.value.data(), node.value.size()); } }; template struct from_bulk_impl { template static void apply(T& t, resp3::basic_node const& node, system::error_code& ec) { converter::apply(t, node, ec); } }; template struct from_bulk_impl> { template static void apply( std::optional& op, resp3::basic_node const& node, system::error_code& ec) { if (node.data_type != resp3::type::null) { op.emplace(T{}); converter::apply(op.value(), node, ec); } } }; template void boost_redis_from_bulk(T& t, resp3::basic_node const& node, system::error_code& ec) { from_bulk_impl::apply(t, node, ec); } //================================================ template class general_aggregate { private: Result* result_; public: explicit general_aggregate(Result* c = nullptr) : result_(c) { } void on_init() { } void on_done() { } template void on_node(resp3::basic_node const& nd, system::error_code&) { BOOST_ASSERT_MSG(!!result_, "Unexpected null pointer"); switch (nd.data_type) { case resp3::type::blob_error: case resp3::type::simple_error: *result_ = error{ nd.data_type, std::string{std::cbegin(nd.value), std::cend(nd.value)} }; break; default: if (result_->has_value()) { (**result_).push_back({ nd.data_type, nd.aggregate_size, nd.depth, std::string{std::cbegin(nd.value), std::cend(nd.value)} }); } } } }; template class general_simple { private: Node* result_; public: explicit general_simple(Node* t = nullptr) : result_(t) { } void on_init() { } void on_done() { } template void on_node(resp3::basic_node const& nd, system::error_code&) { BOOST_ASSERT_MSG(!!result_, "Unexpected null pointer"); switch (nd.data_type) { case resp3::type::blob_error: case resp3::type::simple_error: *result_ = error{ nd.data_type, std::string{std::cbegin(nd.value), std::cend(nd.value)} }; break; default: result_->value().data_type = nd.data_type; result_->value().aggregate_size = nd.aggregate_size; result_->value().depth = nd.depth; result_->value().value.assign(nd.value.data(), nd.value.size()); } } }; template class simple_impl { public: void on_value_available(Result&) { } void on_init() { } void on_done() { } template void on_node(Result& result, resp3::basic_node const& node, system::error_code& ec) { if (is_aggregate(node.data_type)) { ec = redis::error::expects_resp3_simple_type; return; } boost_redis_from_bulk(result, node, ec); } }; template class set_impl { private: typename Result::iterator hint_; public: void on_value_available(Result& result) { hint_ = std::end(result); } void on_init() { } void on_done() { } template void on_node(Result& result, resp3::basic_node const& nd, system::error_code& ec) { if (is_aggregate(nd.data_type)) { if (nd.data_type != resp3::type::set) ec = redis::error::expects_resp3_set; return; } BOOST_ASSERT(nd.aggregate_size == 1); if (nd.depth < 1) { ec = redis::error::expects_resp3_set; return; } typename Result::key_type obj; boost_redis_from_bulk(obj, nd, ec); hint_ = result.insert(hint_, std::move(obj)); } }; template class map_impl { private: typename Result::iterator current_; bool on_key_ = true; public: void on_value_available(Result& result) { current_ = std::end(result); } void on_init() { } void on_done() { } template void on_node(Result& result, resp3::basic_node const& nd, system::error_code& ec) { if (is_aggregate(nd.data_type)) { if (element_multiplicity(nd.data_type) != 2) ec = redis::error::expects_resp3_map; return; } BOOST_ASSERT(nd.aggregate_size == 1); if (nd.depth < 1) { ec = redis::error::expects_resp3_map; return; } if (on_key_) { typename Result::key_type obj; boost_redis_from_bulk(obj, nd, ec); current_ = result.insert(current_, {std::move(obj), {}}); } else { typename Result::mapped_type obj; boost_redis_from_bulk(obj, nd, ec); current_->second = std::move(obj); } on_key_ = !on_key_; } }; template class vector_impl { public: void on_value_available(Result&) { } void on_init() { } void on_done() { } template void on_node(Result& result, resp3::basic_node const& nd, system::error_code& ec) { if (is_aggregate(nd.data_type)) { auto const m = element_multiplicity(nd.data_type); result.reserve(result.size() + m * nd.aggregate_size); } else { result.push_back({}); boost_redis_from_bulk(result.back(), nd, ec); } } }; template class array_impl { private: int i_ = -1; public: void on_value_available(Result&) { } void on_init() { } void on_done() { } template void on_node(Result& result, resp3::basic_node const& nd, system::error_code& ec) { if (is_aggregate(nd.data_type)) { if (i_ != -1) { ec = redis::error::nested_aggregate_not_supported; return; } if (result.size() != nd.aggregate_size * element_multiplicity(nd.data_type)) { ec = redis::error::incompatible_size; return; } } else { if (i_ == -1) { ec = redis::error::expects_resp3_aggregate; return; } BOOST_ASSERT(nd.aggregate_size == 1); boost_redis_from_bulk(result.at(i_), nd, ec); } ++i_; } }; template struct list_impl { void on_value_available(Result&) { } void on_init() { } void on_done() { } template void on_node(Result& result, resp3::basic_node const& nd, system::error_code& ec) { if (!is_aggregate(nd.data_type)) { BOOST_ASSERT(nd.aggregate_size == 1); if (nd.depth < 1) { ec = redis::error::expects_resp3_aggregate; return; } result.push_back({}); boost_redis_from_bulk(result.back(), nd, ec); } } }; //--------------------------------------------------- template struct impl_map { using type = simple_impl; }; template struct impl_map> { using type = set_impl>; }; template struct impl_map> { using type = set_impl>; }; template struct impl_map> { using type = set_impl>; }; template struct impl_map> { using type = set_impl>; }; template struct impl_map> { using type = map_impl>; }; template struct impl_map> { using type = map_impl>; }; template struct impl_map> { using type = map_impl>; }; template struct impl_map> { using type = map_impl>; }; template struct impl_map> { using type = vector_impl>; }; template struct impl_map> { using type = array_impl>; }; template struct impl_map> { using type = list_impl>; }; template struct impl_map> { using type = list_impl>; }; //--------------------------------------------------- template class wrapper; template class wrapper> { public: using response_type = result; private: response_type* result_; typename impl_map::type impl_; bool called_once_ = false; template bool set_if_resp3_error(resp3::basic_node const& nd) noexcept { switch (nd.data_type) { case resp3::type::null: case resp3::type::simple_error: case resp3::type::blob_error: *result_ = error{ nd.data_type, {std::cbegin(nd.value), std::cend(nd.value)} }; return true; default: return false; } } public: explicit wrapper(response_type* t = nullptr) : result_(t) { if (result_) { result_->value() = T{}; impl_.on_value_available(result_->value()); } } void on_init() { impl_.on_init(); } void on_done() { impl_.on_done(); } template void on_node(resp3::basic_node const& nd, system::error_code& ec) { BOOST_ASSERT_MSG(!!result_, "Unexpected null pointer"); if (result_->has_error()) return; if (!std::exchange(called_once_, true) && set_if_resp3_error(nd)) return; BOOST_ASSERT(result_); impl_.on_node(result_->value(), nd, ec); } }; template class wrapper>> { public: using response_type = result>; private: response_type* result_; typename impl_map::type impl_{}; bool called_once_ = false; template bool set_if_resp3_error(resp3::basic_node const& nd) noexcept { switch (nd.data_type) { case resp3::type::blob_error: case resp3::type::simple_error: *result_ = error{ nd.data_type, {std::cbegin(nd.value), std::cend(nd.value)} }; return true; default: return false; } } public: explicit wrapper(response_type* o = nullptr) : result_(o) { } void on_init() { impl_.on_init(); } void on_done() { impl_.on_done(); } template void on_node(resp3::basic_node const& nd, system::error_code& ec) { BOOST_ASSERT_MSG(!!result_, "Unexpected null pointer"); if (result_->has_error()) return; if (set_if_resp3_error(nd)) return; if (!std::exchange(called_once_, true) && nd.data_type == resp3::type::null) return; if (!result_->value().has_value()) { result_->value() = T{}; impl_.on_value_available(result_->value().value()); } impl_.on_node(result_->value().value(), nd, ec); } }; } // namespace boost::redis::adapter::detail #endif // BOOST_REDIS_ADAPTER_ADAPTERS_HPP