salvo_core/writing/
redirect.rs

1use super::Scribe;
2use crate::Error;
3use crate::http::header::{HeaderValue, LOCATION};
4use crate::http::uri::Uri;
5use crate::http::{Response, StatusCode};
6
7/// Response that redirects the request to another location.
8///
9/// # Example
10///
11/// ```
12/// use salvo_core::prelude::*;
13///
14/// #[handler]
15/// async fn hello(res: &mut Response) {
16///     res.render(Redirect::found("https://www.rust-lang.org/"))
17/// }
18/// ```
19#[derive(Clone, Debug)]
20pub struct Redirect {
21    status_code: StatusCode,
22    location: HeaderValue,
23}
24
25impl Redirect {
26    /// Create a new [`Redirect`] that uses a [`303 See Other`][mdn] status code.
27    ///
28    /// This redirect instructs the client to change the method to GET for the subsequent request
29    /// to the given `uri`, which is useful after successful form submission, file upload or when
30    /// you generally don't want the redirected-to page to observe the original request method and
31    /// body (if non-empty).
32    ///
33    /// # Panics
34    ///
35    /// If `uri` isn't a valid [`Uri`].
36    ///
37    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/303
38    pub fn other(uri: impl TryInto<Uri>) -> Self {
39        Self::with_status_code(StatusCode::SEE_OTHER, uri).expect("invalid uri")
40    }
41
42    /// Create a new [`Redirect`] that uses a [`307 Temporary Redirect`][mdn] status code.
43    ///
44    /// # Panics
45    ///
46    /// If `uri` isn't a valid [`Uri`].
47    ///
48    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/307
49    pub fn temporary(uri: impl TryInto<Uri>) -> Self {
50        Self::with_status_code(StatusCode::TEMPORARY_REDIRECT, uri).expect("invalid uri")
51    }
52
53    /// Create a new [`Redirect`] that uses a [`308 Permanent Redirect`][mdn] status code.
54    ///
55    /// # Panics
56    ///
57    /// If `uri` isn't a valid [`Uri`].
58    ///
59    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/308
60    pub fn permanent(uri: impl TryInto<Uri>) -> Self {
61        Self::with_status_code(StatusCode::PERMANENT_REDIRECT, uri).expect("invalid uri")
62    }
63
64    /// Create a new [`Redirect`] that uses a [`302 Found`][mdn] status code.
65    ///
66    /// This is the same as [`Redirect::temporary`], except the status code is older and thus
67    /// supported by some legacy applications that doesn't understand the newer one, but some of
68    /// those applications wrongly apply [`Redirect::other`] (`303 See Other`) semantics for this
69    /// status code. It should be avoided where possible.
70    ///
71    /// # Panics
72    ///
73    /// If `uri` isn't a valid [`Uri`].
74    ///
75    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/302
76    pub fn found(uri: impl TryInto<Uri>) -> Self {
77        Self::with_status_code(StatusCode::FOUND, uri).expect("invalid uri")
78    }
79
80    /// Create a new [`Redirect`] that uses a status code.
81    pub fn with_status_code(
82        status_code: StatusCode,
83        uri: impl TryInto<Uri>,
84    ) -> Result<Self, Error> {
85        if !status_code.is_redirection() {
86            return Err(Error::other("not a redirection status code"));
87        }
88
89        Ok(Self {
90            status_code,
91            location: uri
92                .try_into()
93                .map_err(|_| Error::other("It isn't a valid URI"))
94                .and_then(|uri| {
95                    HeaderValue::try_from(uri.to_string())
96                        .map_err(|_| Error::other("URI isn't a valid header value"))
97                })?,
98        })
99    }
100}
101
102impl Scribe for Redirect {
103    #[inline]
104    fn render(self, res: &mut Response) {
105        let Self {
106            status_code,
107            location,
108        } = self;
109        res.status_code(status_code);
110        res.headers_mut().insert(LOCATION, location);
111    }
112}