zon_util/response/
redirect.rs

1use http::{header::LOCATION, HeaderValue, StatusCode};
2use zon_core::{Body, IntoResponse, Response};
3
4/// Response that redirects the request to another location.
5#[must_use = "needs to be returned from a handler or otherwise turned into a Response to be useful"]
6#[derive(Debug, Clone)]
7pub struct Redirect {
8    status_code: StatusCode,
9    location: HeaderValue,
10}
11
12impl Redirect {
13    /// Create a new [`Redirect`] that uses a [`303 See Other`][mdn] status
14    /// code.
15    ///
16    /// This redirect instructs the client to change the method to GET for the
17    /// subsequent request to the given `uri`, which is useful after
18    /// successful form submission, file upload or when you generally don't
19    /// want the redirected-to page to observe the original request method and
20    /// body (if non-empty). If you want to preserve the request method and
21    /// body, [`Redirect::temporary`] should be used instead.
22    ///
23    /// # Panics
24    ///
25    /// If `uri` isn't a valid [`HeaderValue`].
26    ///
27    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/303
28    pub fn to(uri: &str) -> Self {
29        Self::with_status_code(StatusCode::SEE_OTHER, uri)
30    }
31
32    /// Create a new [`Redirect`] that uses a [`307 Temporary Redirect`][mdn]
33    /// status code.
34    ///
35    /// This has the same behavior as [`Redirect::to`], except it will preserve
36    /// the original HTTP method and body.
37    ///
38    /// # Panics
39    ///
40    /// If `uri` isn't a valid [`HeaderValue`].
41    ///
42    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/307
43    pub fn temporary(uri: &str) -> Self {
44        Self::with_status_code(StatusCode::TEMPORARY_REDIRECT, uri)
45    }
46
47    /// Create a new [`Redirect`] that uses a [`308 Permanent Redirect`][mdn]
48    /// status code.
49    ///
50    /// # Panics
51    ///
52    /// If `uri` isn't a valid [`HeaderValue`].
53    ///
54    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/308
55    pub fn permanent(uri: &str) -> Self {
56        Self::with_status_code(StatusCode::PERMANENT_REDIRECT, uri)
57    }
58
59    // This is intentionally not public since other kinds of redirects might not
60    // use the `Location` header, namely `304 Not Modified`.
61    //
62    // We're open to adding more constructors upon request, if they make sense :)
63    fn with_status_code(status_code: StatusCode, uri: &str) -> Self {
64        assert!(status_code.is_redirection(), "not a redirection status code");
65
66        Self {
67            status_code,
68            location: HeaderValue::try_from(uri).expect("URI isn't a valid header value"),
69        }
70    }
71}
72
73impl IntoResponse for Redirect {
74    fn into_response(self) -> Result<Response, Response> {
75        let mut res = Response::new(Body::empty());
76        *res.status_mut() = self.status_code;
77        res.headers_mut().insert(LOCATION, self.location);
78        Ok(res)
79    }
80}