Skip to main content

trillium_redirect/
lib.rs

1//! Trillium handler for redirection
2#![forbid(unsafe_code)]
3#![deny(
4    missing_copy_implementations,
5    rustdoc::missing_crate_level_docs,
6    missing_debug_implementations,
7    missing_docs,
8    nonstandard_style,
9    unused_qualifications
10)]
11
12#[cfg(test)]
13#[doc = include_str!("../README.md")]
14mod readme {}
15
16use std::borrow::Cow;
17use trillium::{Conn, Handler, KnownHeaderName::Location, Status};
18
19/// The subset of http statuses that indicate redirection
20///
21/// The default is [`RedirectStatus::Found`]
22#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
23pub enum RedirectStatus {
24    /// [300 Multiple Choices](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/300)
25    MultipleChoices,
26    /// [301 Moved Permanently](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/301)
27    MovedPermanently,
28    /// [302 Found](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/302)
29    #[default]
30    Found,
31    /// [303 See Other](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/303)
32    SeeOther,
33    /// [307 Temporary Redirect](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/307)
34    TemporaryRedirect,
35    /// [308 Permanent Redirect](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/308)
36    PermanentRedirect,
37}
38
39impl From<RedirectStatus> for Status {
40    fn from(value: RedirectStatus) -> Self {
41        match value {
42            RedirectStatus::MultipleChoices => Status::MultipleChoice,
43            RedirectStatus::MovedPermanently => Status::MovedPermanently,
44            RedirectStatus::Found => Status::Found,
45            RedirectStatus::SeeOther => Status::SeeOther,
46            RedirectStatus::TemporaryRedirect => Status::TemporaryRedirect,
47            RedirectStatus::PermanentRedirect => Status::PermanentRedirect,
48        }
49    }
50}
51
52/// A simple handler for redirection
53#[derive(Clone, Debug)]
54pub struct Redirect {
55    to: Cow<'static, str>,
56    status: RedirectStatus,
57}
58
59impl Redirect {
60    /// Redirect to the provided path or url with the default redirect status
61    pub fn to(to: impl Into<Cow<'static, str>>) -> Self {
62        Self {
63            to: to.into(),
64            status: RedirectStatus::default(),
65        }
66    }
67
68    /// Provide a [`RedirectStatus`] for this redirect handler
69    pub fn with_redirect_status(mut self, status: RedirectStatus) -> Self {
70        self.status = status;
71        self
72    }
73}
74
75/// Redirect to the provided path or url with the default redirect status
76pub fn redirect(to: impl Into<Cow<'static, str>>) -> Redirect {
77    Redirect::to(to)
78}
79
80impl Handler for Redirect {
81    async fn run(&self, conn: Conn) -> Conn {
82        conn.redirect_as(self.to.clone(), self.status)
83    }
84}
85
86/// An extension trait for [`trillium::Conn`] for redirection
87pub trait RedirectConnExt {
88    /// redirect this conn with the default redirect status
89    fn redirect(self, to: impl Into<Cow<'static, str>>) -> Self;
90    /// redirect this conn with the provided redirect status
91    fn redirect_as(self, to: impl Into<Cow<'static, str>>, status: RedirectStatus) -> Self;
92}
93
94impl RedirectConnExt for Conn {
95    fn redirect(self, to: impl Into<Cow<'static, str>>) -> Self {
96        self.redirect_as(to, RedirectStatus::default())
97    }
98
99    fn redirect_as(self, to: impl Into<Cow<'static, str>>, status: RedirectStatus) -> Self {
100        self.with_status(status)
101            .with_response_header(Location, to.into())
102            .halt()
103    }
104}