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}