axum/response/
redirect.rs

1use axum_core::response::{IntoResponse, Response};
2use http::{header::LOCATION, HeaderValue, StatusCode};
3
4/// Response that redirects the request to another location.
5///
6/// # Example
7///
8/// ```rust
9/// use axum::{
10///     routing::get,
11///     response::Redirect,
12///     Router,
13/// };
14///
15/// let app = Router::new()
16///     .route("/old", get(|| async { Redirect::permanent("/new") }))
17///     .route("/new", get(|| async { "Hello!" }));
18/// # let _: Router = app;
19/// ```
20#[must_use = "needs to be returned from a handler or otherwise turned into a Response to be useful"]
21#[derive(Debug, Clone)]
22pub struct Redirect {
23    status_code: StatusCode,
24    location: String,
25}
26
27impl Redirect {
28    /// Create a new [`Redirect`] that uses a [`303 See Other`][mdn] status code.
29    ///
30    /// This redirect instructs the client to change the method to GET for the subsequent request
31    /// to the given `uri`, which is useful after successful form submission, file upload or when
32    /// you generally don't want the redirected-to page to observe the original request method and
33    /// body (if non-empty). If you want to preserve the request method and body,
34    /// [`Redirect::temporary`] should be used instead.
35    ///
36    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/303
37    pub fn to(uri: &str) -> Self {
38        Self::with_status_code(StatusCode::SEE_OTHER, uri)
39    }
40
41    /// Create a new [`Redirect`] that uses a [`307 Temporary Redirect`][mdn] status code.
42    ///
43    /// This has the same behavior as [`Redirect::to`], except it will preserve the original HTTP
44    /// method and body.
45    ///
46    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/307
47    pub fn temporary(uri: &str) -> Self {
48        Self::with_status_code(StatusCode::TEMPORARY_REDIRECT, uri)
49    }
50
51    /// Create a new [`Redirect`] that uses a [`308 Permanent Redirect`][mdn] status code.
52    ///
53    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/308
54    pub fn permanent(uri: &str) -> Self {
55        Self::with_status_code(StatusCode::PERMANENT_REDIRECT, uri)
56    }
57
58    /// Returns the HTTP status code of the `Redirect`.
59    #[must_use]
60    pub fn status_code(&self) -> StatusCode {
61        self.status_code
62    }
63
64    /// Returns the `Redirect`'s URI.
65    #[must_use]
66    pub fn location(&self) -> &str {
67        &self.location
68    }
69
70    // This is intentionally not public since other kinds of redirects might not
71    // use the `Location` header, namely `304 Not Modified`.
72    //
73    // We're open to adding more constructors upon request, if they make sense :)
74    fn with_status_code(status_code: StatusCode, uri: &str) -> Self {
75        assert!(
76            status_code.is_redirection(),
77            "not a redirection status code"
78        );
79
80        Self {
81            status_code,
82            location: uri.to_owned(),
83        }
84    }
85}
86
87impl IntoResponse for Redirect {
88    fn into_response(self) -> Response {
89        match HeaderValue::try_from(self.location) {
90            Ok(location) => (self.status_code, [(LOCATION, location)]).into_response(),
91            Err(error) => (StatusCode::INTERNAL_SERVER_ERROR, error.to_string()).into_response(),
92        }
93    }
94}
95
96#[cfg(test)]
97mod tests {
98    use super::Redirect;
99    use axum_core::response::IntoResponse;
100    use http::StatusCode;
101
102    const EXAMPLE_URL: &str = "https://example.com";
103
104    // Tests to make sure Redirect has the correct status codes
105    // based on the way it was constructed.
106    #[test]
107    fn correct_status() {
108        assert_eq!(
109            StatusCode::SEE_OTHER,
110            Redirect::to(EXAMPLE_URL).status_code()
111        );
112
113        assert_eq!(
114            StatusCode::TEMPORARY_REDIRECT,
115            Redirect::temporary(EXAMPLE_URL).status_code()
116        );
117
118        assert_eq!(
119            StatusCode::PERMANENT_REDIRECT,
120            Redirect::permanent(EXAMPLE_URL).status_code()
121        );
122    }
123
124    #[test]
125    fn correct_location() {
126        assert_eq!(EXAMPLE_URL, Redirect::permanent(EXAMPLE_URL).location());
127
128        assert_eq!("/redirect", Redirect::permanent("/redirect").location())
129    }
130
131    #[test]
132    fn test_internal_error() {
133        let response = Redirect::permanent("Axum is awesome, \n but newlines aren't allowed :(")
134            .into_response();
135
136        assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
137    }
138}