rama_http/service/
redirect.rs

1//! Service that redirects all requests.
2
3use crate::Request;
4use crate::{header, HeaderValue, Response, StatusCode, Uri};
5use rama_core::{Context, Service};
6use std::{
7    convert::{Infallible, TryFrom},
8    fmt,
9    marker::PhantomData,
10};
11
12/// Service that redirects all requests.
13pub struct Redirect<ResBody> {
14    status_code: StatusCode,
15    location: HeaderValue,
16    // Covariant over ResBody, no dropping of ResBody
17    _marker: PhantomData<fn() -> ResBody>,
18}
19
20impl<ResBody> Redirect<ResBody> {
21    /// Create a new [`Redirect`] that uses a [`307 Temporary Redirect`][mdn] status code.
22    ///
23    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/307
24    pub fn temporary(uri: Uri) -> Self {
25        Self::with_status_code(StatusCode::TEMPORARY_REDIRECT, uri)
26    }
27
28    /// Create a new [`Redirect`] that uses a [`308 Permanent Redirect`][mdn] status code.
29    ///
30    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/308
31    pub fn permanent(uri: Uri) -> Self {
32        Self::with_status_code(StatusCode::PERMANENT_REDIRECT, uri)
33    }
34
35    /// Create a new [`Redirect`] that uses the given status code.
36    ///
37    /// # Panics
38    ///
39    /// - If `status_code` isn't a [redirection status code][mdn] (3xx).
40    /// - If `uri` isn't a valid [`HeaderValue`].
41    ///
42    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#redirection_messages
43    pub fn with_status_code(status_code: StatusCode, uri: Uri) -> Self {
44        assert!(
45            status_code.is_redirection(),
46            "not a redirection status code"
47        );
48
49        Self {
50            status_code,
51            location: HeaderValue::try_from(uri.to_string())
52                .expect("URI isn't a valid header value"),
53            _marker: PhantomData,
54        }
55    }
56}
57
58impl<State, Body, ResBody> Service<State, Request<Body>> for Redirect<ResBody>
59where
60    State: Clone + Send + Sync + 'static,
61    Body: Send + 'static,
62    ResBody: Default + Send + 'static,
63{
64    type Response = Response<ResBody>;
65    type Error = Infallible;
66
67    async fn serve(
68        &self,
69        _ctx: Context<State>,
70        _req: Request<Body>,
71    ) -> Result<Self::Response, Self::Error> {
72        let mut res = Response::default();
73        *res.status_mut() = self.status_code;
74        res.headers_mut()
75            .insert(header::LOCATION, self.location.clone());
76        Ok(res)
77    }
78}
79
80impl<ResBody> fmt::Debug for Redirect<ResBody> {
81    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82        f.debug_struct("Redirect")
83            .field("status_code", &self.status_code)
84            .field("location", &self.location)
85            .finish()
86    }
87}
88
89impl<ResBody> Clone for Redirect<ResBody> {
90    fn clone(&self) -> Self {
91        Self {
92            status_code: self.status_code,
93            location: self.location.clone(),
94            _marker: PhantomData,
95        }
96    }
97}