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}