1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
use super::Scribe;
use crate::http::header::{HeaderValue, LOCATION};
use crate::http::uri::Uri;
use crate::http::{Response, StatusCode};
use crate::Error;

/// Response that redirects the request to another location.
#[derive(Clone, Debug)]
pub struct Redirect {
    status_code: StatusCode,
    location: HeaderValue,
}

impl Redirect {
    /// Create a new [`Redirect`] that uses a [`303 See Other`][mdn] status code.
    ///
    /// This redirect instructs the client to change the method to GET for the subsequent request
    /// to the given `uri`, which is useful after successful form submission, file upload or when
    /// you generally don't want the redirected-to page to observe the original request method and
    /// body (if non-empty).
    ///
    /// # Panics
    ///
    /// If `uri` isn't a valid [`Uri`].
    ///
    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/303
    pub fn other(uri: impl TryInto<Uri>) -> Self {
        Self::with_status_code(StatusCode::SEE_OTHER, uri).expect("invalid uri")
    }

    /// Create a new [`Redirect`] that uses a [`307 Temporary Redirect`][mdn] status code.
    ///
    /// # Panics
    ///
    /// If `uri` isn't a valid [`Uri`].
    ///
    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/307
    pub fn temporary(uri: impl TryInto<Uri>) -> Self {
        Self::with_status_code(StatusCode::TEMPORARY_REDIRECT, uri).expect("invalid uri")
    }

    /// Create a new [`Redirect`] that uses a [`308 Permanent Redirect`][mdn] status code.
    ///
    /// # Panics
    ///
    /// If `uri` isn't a valid [`Uri`].
    ///
    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/308
    pub fn permanent(uri: impl TryInto<Uri>) -> Self {
        Self::with_status_code(StatusCode::PERMANENT_REDIRECT, uri).expect("invalid uri")
    }

    /// Create a new [`Redirect`] that uses a [`302 Found`][mdn] status code.
    ///
    /// This is the same as [`Redirect::temporary`], except the status code is older and thus
    /// supported by some legacy applications that doesn't understand the newer one, but some of
    /// those applications wrongly apply [`Redirect::other`] (`303 See Other`) semantics for this
    /// status code. It should be avoided where possible.
    ///
    /// # Panics
    ///
    /// If `uri` isn't a valid [`Uri`].
    ///
    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/302
    pub fn found(uri: impl TryInto<Uri>) -> Self {
        Self::with_status_code(StatusCode::FOUND, uri).expect("invalid uri")
    }

    /// Create a new [`Redirect`] that uses a status code.
    pub fn with_status_code(status_code: StatusCode, uri: impl TryInto<Uri>) -> Result<Self, Error> {
        if !status_code.is_redirection() {
            return Err(Error::other("not a redirection status code"));
        }

        Ok(Self {
            status_code,
            location: uri
                .try_into()
                .map_err(|_| Error::other("It isn't a valid URI"))
                .and_then(|uri| {
                    HeaderValue::try_from(uri.to_string()).map_err(|_| Error::other("URI isn't a valid header value"))
                })?,
        })
    }
}

impl Scribe for Redirect {
    #[inline]
    fn render(self, res: &mut Response) {
        let Self { status_code, location } = self;
        res.status_code(status_code);
        res.headers_mut().insert(LOCATION, location);
    }
}