Repository: seanmonstar/reqwest (language: rust)

Issue:
over eager loop detection
If an HTTP server redirects back to itself, the reqwest client appears to detect a loop immediately on the first redirect. It is a somewhat common pattern for a page to do something like set a cookie and then redirect back to itself to ensure that the cookie has, in fact, been set. 

To fix this, the loop detection code could remain simple but defer detecting the loop until the same URL is redirected to twice in a row (this would assume that a server does not repeatedly redirect back to itself while changing state, but that probably occurs a lot less often). 

Alternatively, the loop detection code could operate on the full set of headers being sent in the new request rather than just on the URL (i.e. it would consider what cookies have been set etc). This would be a more general solution and could possibly be easier to implement as you only need to consider the current request and the next request that is being generated by the redirect handler. 


Discussion / hints:
Hm, yea, I tried looking at other libraries, but can't seem to remember where I've seen loop detection before. Maybe it should go?
I think curl just has a max redirects default that catches a loop eventually. I thought some browsers had loop detection, but it looks like Chrome hits `ERR_TOO_MANY_REDIRECTS` before any loop detection comes into play (at least for me). Firefox says "The page isn't redirecting properly", and looking through the source it looks like that comes from an error constant called `NS_ERROR_REDIRECT_LOOP` -- the source of those errors in the C++ code appear to also be based on counting and exceeding a maximum rather than actually tracking a loop. 

The current relevant RFC (https://tools.ietf.org/html/rfc7231#section-6.4) does say a "client SHOULD detect and intervene in cyclical redirections (i.e.,
   "infinite" redirection loops)" but goes on to note than the earlier RFC 2068 had simply suggested that clients not automatically redirect a request more than 5 times (https://tools.ietf.org/html/rfc2068#section-10.3). 

I do think reqwest having the ability to implement a custom RedirectPolicy is really great, and ideally I'd be able to implement custom loop detection myself using that facility. However, if I'm understanding the documentation correctly for `RedirectAttempts`, the only relevant data available is the `&[Url]` slice in `previous` - so we only have the list of urls and not the rest of the requests (such as the cookies or other headers). Am I missing some way to store custom context from the previous requests to use in a custom redirect policy implementation? I guess I could store my own record (of request hashes or something) in an external data structure, but I think that would probably have to live in a lazy_static or something and would probably come with undesirable concurrency management issues.

Actually, it probably would not be hard to just make the whole `Request` history available custom redirect policies so that any arbitrary logic can be added to inspect that. Perhaps `RedirectAttempt` could have something like this added:
```
pub fn request(&self) -> &Request
pub fn previous_requests(&self) -> &[Request]
```

In addition to the existing:
```
pub fn url(&self) -> &Url
pub fn previous(&self) -> &[Url]
```

This would probably work, although if I were implementing this without having to conform to this interface, I'd probably want to just hash the request as each one is processed and store the hash in the `previous` slice (or perhaps a Set would be more efficient). On the other hand, with the addition of `previous_requests` a custom policy could be implemented that would work just like the existing one and be primarily based on detecting potential loops using the Url, but then have additional code added to check the relevant entry in `previous_requests` to see if they actually are identical to the current one. 
For anyone coming to this issue, I can confirm that the default loop detection policy can be overridden with a custom RedirectPolicy such as this:
```
    let custom = RedirectPolicy::custom(|attempt| {
        if attempt.previous().len() > 50 {
            attempt.too_many_redirects()
        } else {
            attempt.follow()
        }
    });
    let client = match Client::builder().cookie_store(true).redirect(custom).build()?;
```


Fix patch:
diff --git a/src/async_impl/client.rs b/src/async_impl/client.rs
index 5b0356481..c305d231b 100644
--- a/src/async_impl/client.rs
+++ b/src/async_impl/client.rs
@@ -29,7 +29,7 @@ use crate::connect::Connector;
 #[cfg(feature = "cookies")]
 use crate::cookie;
 use crate::into_url::{expect_uri, try_uri};
-use crate::redirect::{self, remove_sensitive_headers, RedirectPolicy};
+use crate::redirect::{self, remove_sensitive_headers};
 #[cfg(feature = "tls")]
 use crate::tls::TlsBackend;
 #[cfg(feature = "tls")]
@@ -70,7 +70,7 @@ struct Config {
     identity: Option<Identity>,
     proxies: Vec<Proxy>,
     auto_sys_proxy: bool,
-    redirect_policy: RedirectPolicy,
+    redirect_policy: redirect::Policy,
     referer: bool,
     timeout: Option<Duration>,
     #[cfg(feature = "tls")]
@@ -114,7 +114,7 @@ impl ClientBuilder {
                 max_idle_per_host: std::usize::MAX,
                 proxies: Vec::new(),
                 auto_sys_proxy: true,
-                redirect_policy: RedirectPolicy::default(),
+                redirect_policy: redirect::Policy::default(),
                 referer: true,
                 timeout: None,
                 #[cfg(feature = "tls")]
@@ -372,7 +372,7 @@ impl ClientBuilder {
     /// Set a `RedirectPolicy` for this client.
     ///
     /// Default will follow redirects up to a maximum of 10.
-    pub fn redirect(mut self, policy: RedirectPolicy) -> ClientBuilder {
+    pub fn redirect(mut self, policy: redirect::Policy) -> ClientBuilder {
         self.config.redirect_policy = policy;
         self
     }
@@ -915,7 +915,7 @@ struct ClientRef {
     gzip: bool,
     headers: HeaderMap,
     hyper: HyperClient,
-    redirect_policy: RedirectPolicy,
+    redirect_policy: redirect::Policy,
     referer: bool,
     request_timeout: Option<Duration>,
     proxies: Arc<Vec<Proxy>>,
@@ -1124,7 +1124,7 @@ impl Future for PendingRequest {
                         .check(res.status(), &loc, &self.urls);
 
                     match action {
-                        redirect::Action::Follow => {
+                        redirect::ActionKind::Follow => {
                             self.url = loc;
 
                             let mut headers =
@@ -1159,14 +1159,12 @@ impl Future for PendingRequest {
                             *self.as_mut().in_flight().get_mut() = self.client.hyper.request(req);
                             continue;
                         }
-                        redirect::Action::Stop => {
+                        redirect::ActionKind::Stop => {
                             debug!("redirect_policy disallowed redirection to '{}'", loc);
                         }
-                        redirect::Action::LoopDetected => {
-                            return Poll::Ready(Err(crate::error::loop_detected(self.url.clone())));
-                        }
-                        redirect::Action::TooManyRedirects => {
-                            return Poll::Ready(Err(crate::error::too_many_redirects(
+                        redirect::ActionKind::Error(err) => {
+                            return Poll::Ready(Err(crate::error::redirect(
+                                err,
                                 self.url.clone(),
                             )));
                         }
diff --git a/src/blocking/client.rs b/src/blocking/client.rs
index 8c13d6a5e..1d80c8b3e 100644
--- a/src/blocking/client.rs
+++ b/src/blocking/client.rs
@@ -13,7 +13,7 @@ use log::{error, trace};
 use super::request::{Request, RequestBuilder};
 use super::response::Response;
 use super::wait;
-use crate::{async_impl, header, IntoUrl, Method, Proxy, RedirectPolicy};
+use crate::{async_impl, header, IntoUrl, Method, Proxy, redirect};
 #[cfg(feature = "tls")]
 use crate::{Certificate, Identity};
 
@@ -176,10 +176,10 @@ impl ClientBuilder {
 
     // Redirect options
 
-    /// Set a `RedirectPolicy` for this client.
+    /// Set a `redirect::Policy` for this client.
     ///
     /// Default will follow redirects up to a maximum of 10.
-    pub fn redirect(self, policy: RedirectPolicy) -> ClientBuilder {
+    pub fn redirect(self, policy: redirect::Policy) -> ClientBuilder {
         self.with_inner(move |inner| inner.redirect(policy))
     }
 
@@ -541,7 +541,7 @@ impl Client {
     /// # Errors
     ///
     /// This method fails if there was an error while sending request,
-    /// redirect loop was detected or redirect limit was exhausted.
+    /// or redirect limit was exhausted.
     pub fn execute(&self, request: Request) -> crate::Result<Response> {
         self.inner.execute_request(request)
     }
diff --git a/src/error.rs b/src/error.rs
index a0ee86628..03e792ada 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -212,12 +212,8 @@ pub(crate) fn request<E: Into<BoxError>>(e: E) -> Error {
     Error::new(Kind::Request, Some(e))
 }
 
-pub(crate) fn loop_detected(url: Url) -> Error {
-    Error::new(Kind::Redirect, Some("infinite redirect loop detected")).with_url(url)
-}
-
-pub(crate) fn too_many_redirects(url: Url) -> Error {
-    Error::new(Kind::Redirect, Some("too many redirects")).with_url(url)
+pub(crate) fn redirect<E: Into<BoxError>>(e: E, url: Url) -> Error {
+    Error::new(Kind::Redirect, Some(e)).with_url(url)
 }
 
 pub(crate) fn status_code(url: Url, status: StatusCode) -> Error {
diff --git a/src/lib.rs b/src/lib.rs
index c43550b40..43f295262 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -119,9 +119,9 @@
 //!
 //! ## Redirect Policies
 //!
-//! By default, a `Client` will automatically handle HTTP redirects, detecting
-//! loops, and having a maximum redirect chain of 10 hops. To customize this
-//! behavior, a [`RedirectPolicy`][redirect] can used with a `ClientBuilder`.
+//! By default, a `Client` will automatically handle HTTP redirects, having a
+//! maximum redirect chain of 10 hops. To customize this behavior, a
+//! [`redirect::Policy`][redirect] can be used with a `ClientBuilder`.
 //!
 //! ## Cookies
 //!
@@ -175,7 +175,7 @@
 //! [get]: ./fn.get.html
 //! [builder]: ./struct.RequestBuilder.html
 //! [serde]: http://serde.rs
-//! [redirect]: ./struct.RedirectPolicy.html
+//! [redirect]: crate::redirect
 //! [Proxy]: ./struct.Proxy.html
 //! [cargo-features]: https://doc.rust-lang.org/stable/cargo/reference/manifest.html#the-features-section
 
@@ -237,7 +237,6 @@ pub use self::into_url::IntoUrl;
 /// - native TLS backend cannot be initialized
 /// - supplied `Url` cannot be parsed
 /// - there was an error while sending request
-/// - redirect loop was detected
 /// - redirect limit was exhausted
 pub async fn get<T: IntoUrl>(url: T) -> crate::Result<Response> {
     Client::builder().build()?.get(url).send().await
@@ -279,7 +278,6 @@ if_hyper! {
         multipart, Body, Client, ClientBuilder, Request, RequestBuilder, Response, ResponseBuilderExt,
     };
     pub use self::proxy::Proxy;
-    pub use self::redirect::{RedirectAction, RedirectAttempt, RedirectPolicy};
     #[cfg(feature = "tls")]
     pub use self::tls::{Certificate, Identity};
 
@@ -293,17 +291,9 @@ if_hyper! {
     //#[cfg(feature = "trust-dns")]
     //mod dns;
     mod proxy;
-    mod redirect;
+    pub mod redirect;
     #[cfg(feature = "tls")]
     mod tls;
-
-    #[doc(hidden)]
-    #[deprecated(note = "types moved to top of crate")]
-    pub mod r#async {
-        pub use crate::async_impl::{
-            multipart, Body, Client, ClientBuilder, Request, RequestBuilder, Response,
-        };
-    }
 }
 
 if_wasm! {
diff --git a/src/redirect.rs b/src/redirect.rs
index b0f25bce1..6d377e25a 100644
--- a/src/redirect.rs
+++ b/src/redirect.rs
@@ -1,3 +1,10 @@
+//! Redirect Handling
+//!
+//! By default, a `Client` will automatically handle HTTP redirects, having a
+//! maximum redirect chain of 10 hops. To customize this behavior, a
+//! `redirect::Policy` can be used with a `ClientBuilder`.
+
+use std::error::Error as StdError;
 use std::fmt;
 
 use crate::header::{HeaderMap, AUTHORIZATION, COOKIE, PROXY_AUTHORIZATION, WWW_AUTHENTICATE};
@@ -14,14 +21,14 @@ use crate::Url;
 ///   the allowed maximum redirect hops in a chain.
 /// - `none` can be used to disable all redirect behavior.
 /// - `custom` can be used to create a customized policy.
-pub struct RedirectPolicy {
-    inner: Policy,
+pub struct Policy {
+    inner: PolicyKind,
 }
 
 /// A type that holds information on the next request and previous requests
 /// in redirect chain.
 #[derive(Debug)]
-pub struct RedirectAttempt<'a> {
+pub struct Attempt<'a> {
     status: StatusCode,
     next: &'a Url,
     previous: &'a [Url],
@@ -29,50 +36,50 @@ pub struct RedirectAttempt<'a> {
 
 /// An action to perform when a redirect status code is found.
 #[derive(Debug)]
-pub struct RedirectAction {
-    inner: Action,
+pub struct Action {
+    inner: ActionKind,
 }
 
-impl RedirectPolicy {
-    /// Create a RedirectPolicy with a maximum number of redirects.
+impl Policy {
+    /// Create a `Policy` with a maximum number of redirects.
     ///
     /// An `Error` will be returned if the max is reached.
-    pub fn limited(max: usize) -> RedirectPolicy {
-        RedirectPolicy {
-            inner: Policy::Limit(max),
+    pub fn limited(max: usize) -> Self {
+        Self {
+            inner: PolicyKind::Limit(max),
         }
     }
 
-    /// Create a RedirectPolicy that does not follow any redirect.
-    pub fn none() -> RedirectPolicy {
-        RedirectPolicy {
-            inner: Policy::None,
+    /// Create a `Policy` that does not follow any redirect.
+    pub fn none() -> Self {
+        Self {
+            inner: PolicyKind::None,
         }
     }
 
-    /// Create a custom RedirectPolicy using the passed function.
+    /// Create a custom `Policy` using the passed function.
     ///
     /// # Note
     ///
-    /// The default RedirectPolicy handles redirect loops and a maximum loop
+    /// The default `Policy` handles a maximum loop
     /// chain, but the custom variant does not do that for you automatically.
     /// The custom policy should have some way of handling those.
     ///
     /// Information on the next request and previous requests can be found
-    /// on the [`RedirectAttempt`] argument passed to the closure.
+    /// on the [`Attempt`] argument passed to the closure.
     ///
     /// Actions can be conveniently created from methods on the
-    /// [`RedirectAttempt`].
+    /// [`Attempt`].
     ///
     /// # Example
     ///
     /// ```rust
-    /// # use reqwest::{Error, RedirectPolicy};
+    /// # use reqwest::{Error, redirect};
     /// #
     /// # fn run() -> Result<(), Error> {
-    /// let custom = RedirectPolicy::custom(|attempt| {
+    /// let custom = redirect::Policy::custom(|attempt| {
     ///     if attempt.previous().len() > 5 {
-    ///         attempt.too_many_redirects()
+    ///         attempt.error("too many redirects")
     ///     } else if attempt.url().host_str() == Some("example.domain") {
     ///         // prevent redirects to 'example.domain'
     ///         attempt.stop()
@@ -87,54 +94,52 @@ impl RedirectPolicy {
     /// # }
     /// ```
     ///
-    /// [`RedirectAttempt`]: struct.RedirectAttempt.html
-    pub fn custom<T>(policy: T) -> RedirectPolicy
+    /// [`Attempt`]: struct.Attempt.html
+    pub fn custom<T>(policy: T) -> Self
     where
-        T: Fn(RedirectAttempt) -> RedirectAction + Send + Sync + 'static,
+        T: Fn(Attempt) -> Action + Send + Sync + 'static,
     {
-        RedirectPolicy {
-            inner: Policy::Custom(Box::new(policy)),
+        Self {
+            inner: PolicyKind::Custom(Box::new(policy)),
         }
     }
 
-    /// Apply this policy to a given [`RedirectAttempt`] to produce a [`RedirectAction`].
+    /// Apply this policy to a given [`Attempt`] to produce a [`Action`].
     ///
     /// # Note
     ///
-    /// This method can be used together with RedirectPolicy::custom()
-    /// to construct one RedirectPolicy that wraps another.
+    /// This method can be used together with `Policy::custom()`
+    /// to construct one `Policy` that wraps another.
     ///
     /// # Example
     ///
     /// ```rust
-    /// # use reqwest::{Error, RedirectPolicy};
+    /// # use reqwest::{Error, redirect};
     /// #
     /// # fn run() -> Result<(), Error> {
-    /// let custom = RedirectPolicy::custom(|attempt| {
+    /// let custom = redirect::Policy::custom(|attempt| {
     ///     eprintln!("{}, Location: {:?}", attempt.status(), attempt.url());
-    ///     RedirectPolicy::default().redirect(attempt)
+    ///     redirect::Policy::default().redirect(attempt)
     /// });
     /// # Ok(())
     /// # }
     /// ```
-    pub fn redirect(&self, attempt: RedirectAttempt) -> RedirectAction {
+    pub fn redirect(&self, attempt: Attempt) -> Action {
         match self.inner {
-            Policy::Custom(ref custom) => custom(attempt),
-            Policy::Limit(max) => {
+            PolicyKind::Custom(ref custom) => custom(attempt),
+            PolicyKind::Limit(max) => {
                 if attempt.previous.len() == max {
-                    attempt.too_many_redirects()
-                } else if attempt.previous.contains(attempt.next) {
-                    attempt.loop_detected()
+                    attempt.error(TooManyRedirects)
                 } else {
                     attempt.follow()
                 }
             }
-            Policy::None => attempt.stop(),
+            PolicyKind::None => attempt.stop(),
         }
     }
 
-    pub(crate) fn check(&self, status: StatusCode, next: &Url, previous: &[Url]) -> Action {
-        self.redirect(RedirectAttempt {
+    pub(crate) fn check(&self, status: StatusCode, next: &Url, previous: &[Url]) -> ActionKind {
+        self.redirect(Attempt {
             status,
             next,
             previous,
@@ -144,20 +149,20 @@ impl RedirectPolicy {
 
     pub(crate) fn is_default(&self) -> bool {
         match self.inner {
-            Policy::Limit(10) => true,
+            PolicyKind::Limit(10) => true,
             _ => false,
         }
     }
 }
 
-impl Default for RedirectPolicy {
-    fn default() -> RedirectPolicy {
+impl Default for Policy {
+    fn default() -> Policy {
         // Keep `is_default` in sync
-        RedirectPolicy::limited(10)
+        Policy::limited(10)
     }
 }
 
-impl<'a> RedirectAttempt<'a> {
+impl<'a> Attempt<'a> {
     /// Get the type of redirect.
     pub fn status(&self) -> StatusCode {
         self.status
@@ -173,70 +178,60 @@ impl<'a> RedirectAttempt<'a> {
         self.previous
     }
     /// Returns an action meaning reqwest should follow the next URL.
-    pub fn follow(self) -> RedirectAction {
-        RedirectAction {
-            inner: Action::Follow,
+    pub fn follow(self) -> Action {
+        Action {
+            inner: ActionKind::Follow,
         }
     }
 
     /// Returns an action meaning reqwest should not follow the next URL.
     ///
     /// The 30x response will be returned as the `Ok` result.
-    pub fn stop(self) -> RedirectAction {
-        RedirectAction {
-            inner: Action::Stop,
-        }
-    }
-
-    /// Returns an action meaning there was a loop of redirects found.
-    ///
-    /// An `Error` will be returned for the result of the sent request.
-    pub fn loop_detected(self) -> RedirectAction {
-        RedirectAction {
-            inner: Action::LoopDetected,
+    pub fn stop(self) -> Action {
+        Action {
+            inner: ActionKind::Stop,
         }
     }
 
-    /// Returns an action meaning there was a loop of redirects found.
+    /// Returns an action failing the redirect with an error.
     ///
-    /// An `Error` will be returned for the result of the sent request.
-    pub fn too_many_redirects(self) -> RedirectAction {
-        RedirectAction {
-            inner: Action::TooManyRedirects,
+    /// The `Error` will be returned for the result of the sent request.
+    pub fn error<E: Into<Box<dyn StdError + Send + Sync>>>(self, error: E) -> Action {
+        Action {
+            inner: ActionKind::Error(error.into()),
         }
     }
 }
 
-enum Policy {
-    Custom(Box<dyn Fn(RedirectAttempt) -> RedirectAction + Send + Sync + 'static>),
+enum PolicyKind {
+    Custom(Box<dyn Fn(Attempt) -> Action + Send + Sync + 'static>),
     Limit(usize),
     None,
 }
 
-impl fmt::Debug for RedirectPolicy {
+impl fmt::Debug for Policy {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        f.debug_tuple("RedirectPolicy").field(&self.inner).finish()
+        f.debug_tuple("Policy").field(&self.inner).finish()
     }
 }
 
-impl fmt::Debug for Policy {
+impl fmt::Debug for PolicyKind {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         match *self {
-            Policy::Custom(..) => f.pad("Custom"),
-            Policy::Limit(max) => f.debug_tuple("Limit").field(&max).finish(),
-            Policy::None => f.pad("None"),
+            PolicyKind::Custom(..) => f.pad("Custom"),
+            PolicyKind::Limit(max) => f.debug_tuple("Limit").field(&max).finish(),
+            PolicyKind::None => f.pad("None"),
         }
     }
 }
 
 // pub(crate)
 
-#[derive(Debug, PartialEq)]
-pub(crate) enum Action {
+#[derive(Debug)]
+pub(crate) enum ActionKind {
     Follow,
     Stop,
-    LoopDetected,
-    TooManyRedirects,
+    Error(Box<dyn StdError + Send + Sync>),
 }
 
 pub(crate) fn remove_sensitive_headers(headers: &mut HeaderMap, next: &Url, previous: &[Url]) {
@@ -253,48 +248,42 @@ pub(crate) fn remove_sensitive_headers(headers: &mut HeaderMap, next: &Url, prev
     }
 }
 
-/*
-This was the desired way of doing it, but ran in to inference issues when
-using closures, since the arguments received are references (&Url and &[Url]),
-and the compiler could not infer the lifetimes of those references. That means
-people would need to annotate the closure's argument types, which is garbase.
-
-pub trait Redirect {
-    fn redirect(&self, next: &Url, previous: &[Url]) -> Result<bool>;
-}
+#[derive(Debug)]
+struct TooManyRedirects;
 
-impl<F> Redirect for F
-where F: Fn(&Url, &[Url]) -> Result<bool> {
-    fn redirect(&self, next: &Url, previous: &[Url]) -> Result<bool> {
-        self(next, previous)
+impl fmt::Display for TooManyRedirects {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.write_str("too many redirects")
     }
 }
-*/
+
+impl StdError for TooManyRedirects {}
 
 #[test]
 fn test_redirect_policy_limit() {
-    let policy = RedirectPolicy::default();
+    let policy = Policy::default();
     let next = Url::parse("http://x.y/z").unwrap();
     let mut previous = (0..9)
         .map(|i| Url::parse(&format!("http://a.b/c/{}", i)).unwrap())
         .collect::<Vec<_>>();
 
-    assert_eq!(
-        policy.check(StatusCode::FOUND, &next, &previous),
-        Action::Follow
-    );
+
+    match policy.check(StatusCode::FOUND, &next, &previous) {
+        ActionKind::Follow => (),
+        other => panic!("unexpected {:?}", other),
+    }
 
     previous.push(Url::parse("http://a.b.d/e/33").unwrap());
 
-    assert_eq!(
-        policy.check(StatusCode::FOUND, &next, &previous),
-        Action::TooManyRedirects
-    );
+    match policy.check(StatusCode::FOUND, &next, &previous) {
+        ActionKind::Error(err) if err.is::<TooManyRedirects>() => (),
+        other => panic!("unexpected {:?}", other),
+    }
 }
 
 #[test]
 fn test_redirect_policy_custom() {
-    let policy = RedirectPolicy::custom(|attempt| {
+    let policy = Policy::custom(|attempt| {
         if attempt.url().host_str() == Some("foo") {
             attempt.stop()
         } else {
@@ -303,10 +292,16 @@ fn test_redirect_policy_custom() {
     });
 
     let next = Url::parse("http://bar/baz").unwrap();
-    assert_eq!(policy.check(StatusCode::FOUND, &next, &[]), Action::Follow);
+    match policy.check(StatusCode::FOUND, &next, &[]) {
+        ActionKind::Follow => (),
+        other => panic!("unexpected {:?}", other),
+    }
 
     let next = Url::parse("http://foo/baz").unwrap();
-    assert_eq!(policy.check(StatusCode::FOUND, &next, &[]), Action::Stop);
+     match policy.check(StatusCode::FOUND, &next, &[]) {
+        ActionKind::Stop => (),
+        other => panic!("unexpected {:?}", other),
+    }
 }
 
 #[test]


Test patch:
diff --git a/tests/cookie.rs b/tests/cookie.rs
index b163f1e02..2684e32c7 100644
--- a/tests/cookie.rs
+++ b/tests/cookie.rs
@@ -173,7 +173,7 @@ async fn cookie_store_expires() {
         }
     });
 
-    let client = reqwest::r#async::Client::builder()
+    let client = reqwest::Client::builder()
         .cookie_store(true)
         .build()
         .unwrap();
@@ -201,7 +201,7 @@ async fn cookie_store_path() {
         }
     });
 
-    let client = reqwest::r#async::Client::builder()
+    let client = reqwest::Client::builder()
         .cookie_store(true)
         .build()
         .unwrap();
diff --git a/tests/redirect.rs b/tests/redirect.rs
index 66ba54b98..e21b32071 100644
--- a/tests/redirect.rs
+++ b/tests/redirect.rs
@@ -240,7 +240,7 @@ async fn test_redirect_policy_can_stop_redirects_without_an_error() {
     let url = format!("http://{}/no-redirect", server.addr());
 
     let res = reqwest::Client::builder()
-        .redirect(reqwest::RedirectPolicy::none())
+        .redirect(reqwest::redirect::Policy::none())
         .build()
         .unwrap()
         .get(&url)