#ifndef CPR_ASYNC_WRAPPER_H #define CPR_ASYNC_WRAPPER_H #include #include #include #include "cpr/response.h" namespace cpr { enum class [[nodiscard]] CancellationResult { failure, success, invalid_operation }; /** * A class template intended to wrap results of async operations (instances of std::future) * and also provide extended capablilities relaed to these requests, for example cancellation. * * The RAII semantics are the same as std::future - moveable, not copyable. */ template class AsyncWrapper { private: std::future future; std::shared_ptr is_cancelled; public: // Constructors explicit AsyncWrapper(std::future&& f) : future{std::move(f)} {} AsyncWrapper(std::future&& f, std::shared_ptr&& cancelledState) : future{std::move(f)}, is_cancelled{std::move(cancelledState)} {} // Copy Semantics AsyncWrapper(const AsyncWrapper&) = delete; AsyncWrapper& operator=(const AsyncWrapper&) = delete; // Move Semantics AsyncWrapper(AsyncWrapper&&) noexcept = default; AsyncWrapper& operator=(AsyncWrapper&&) noexcept = default; // Destructor ~AsyncWrapper() { if constexpr (isCancellable) { if(is_cancelled) { is_cancelled->store(true); } } } // These methods replicate the behaviour of std::future [[nodiscard]] T get() { if constexpr (isCancellable) { if (IsCancelled()) { throw std::logic_error{"Calling AsyncWrapper::get on a cancelled request!"}; } } if (!future.valid()) { throw std::logic_error{"Calling AsyncWrapper::get when the associated future instance is invalid!"}; } return future.get(); } [[nodiscard]] bool valid() const noexcept { if constexpr (isCancellable) { return !is_cancelled->load() && future.valid(); } else { return future.valid(); } } void wait() const { if constexpr (isCancellable) { if (is_cancelled->load()) { throw std::logic_error{"Calling AsyncWrapper::wait when the associated future is invalid or cancelled!"}; } } if (!future.valid()) { throw std::logic_error{"Calling AsyncWrapper::wait_until when the associated future is invalid!"}; } future.wait(); } template std::future_status wait_for(const std::chrono::duration& timeout_duration) const { if constexpr (isCancellable) { if (IsCancelled()) { throw std::logic_error{"Calling AsyncWrapper::wait_for when the associated future is cancelled!"}; } } if (!future.valid()) { throw std::logic_error{"Calling AsyncWrapper::wait_until when the associated future is invalid!"}; } return future.wait_for(timeout_duration); } template std::future_status wait_until(const std::chrono::time_point& timeout_time) const { if constexpr (isCancellable) { if (IsCancelled()) { throw std::logic_error{"Calling AsyncWrapper::wait_until when the associated future is cancelled!"}; } } if (!future.valid()) { throw std::logic_error{"Calling AsyncWrapper::wait_until when the associated future is invalid!"}; } return future.wait_until(timeout_time); } std::shared_future share() noexcept { return future.share(); } // Cancellation-related methods CancellationResult Cancel() { if constexpr (!isCancellable) { return CancellationResult::invalid_operation; } if (!future.valid() || is_cancelled->load()) { return CancellationResult::invalid_operation; } is_cancelled->store(true); return CancellationResult::success; } [[nodiscard]] bool IsCancelled() const { if constexpr (isCancellable) { return is_cancelled->load(); } else { return false; } } }; // Deduction guides template AsyncWrapper(std::future&&) -> AsyncWrapper; template AsyncWrapper(std::future&&, std::shared_ptr&&) -> AsyncWrapper; } // namespace cpr #endif