diff --git a/cot/src/response.rs b/cot/src/response.rs index e6233a738..d0ad70078 100644 --- a/cot/src/response.rs +++ b/cot/src/response.rs @@ -108,8 +108,8 @@ pub trait ResponseExt: Sized + private::Sealed { /// Create a new redirect response. /// /// This creates a new [`Response`] object with a status code of - /// [`StatusCode::SEE_OTHER`] and a location header set to the provided - /// location. + /// [`StatusCode::SEE_OTHER`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/303) + /// and a location header set to the provided location. /// /// # Examples /// @@ -125,6 +125,7 @@ pub trait ResponseExt: Sized + private::Sealed { /// * [`crate::reverse_redirect!`] – a more ergonomic way to create /// redirects to internal views #[must_use] + #[deprecated(since = "0.5.0", note = "Use Redirect::new() instead")] fn new_redirect>(location: T) -> Self; } @@ -144,6 +145,53 @@ impl ResponseExt for Response { } } +/// A redirect response. +/// +/// This type creates an HTTP redirect response with a status code of +/// [`StatusCode::SEE_OTHER`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/303) +/// (303) and a `Location` header set to the specified URL. +/// +/// # Examples +/// +/// ``` +/// use cot::response::{IntoResponse, Redirect}; +/// +/// let redirect = Redirect::new("https://example.com"); +/// let response = redirect.into_response().unwrap(); +/// +/// assert_eq!(response.status(), cot::StatusCode::SEE_OTHER); +/// ``` +/// +/// # See also +/// +/// * [`crate::reverse_redirect!`] – a more ergonomic way to create redirects to +/// internal views +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Redirect(String); + +impl Redirect { + /// Creates a new redirect response to the specified location. + /// + /// Creates an HTTP redirect response with a status code of + /// [`StatusCode::SEE_OTHER`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/303) + /// (303) and a `Location` header set to the specified URL. + /// + /// # Examples + /// + /// ``` + /// use cot::response::{IntoResponse, Redirect}; + /// + /// let redirect = Redirect::new("https://example.com"); + /// let response = redirect.into_response().unwrap(); + /// + /// assert_eq!(response.status(), cot::StatusCode::SEE_OTHER); + /// ``` + #[must_use] + pub fn new>(location: T) -> Self { + Self(location.into()) + } +} + #[cfg(test)] mod tests { use super::*; @@ -179,6 +227,7 @@ mod tests { } #[test] + #[expect(deprecated)] fn response_new_redirect() { let location = "http://example.com"; let response = Response::new_redirect(location); @@ -188,4 +237,15 @@ mod tests { location ); } + + #[test] + fn response_new_redirect_struct() { + let location = "http://example.com"; + let response = Redirect::new(location).into_response().unwrap(); + assert_eq!(response.status(), StatusCode::SEE_OTHER); + assert_eq!( + response.headers().get(http::header::LOCATION).unwrap(), + location + ); + } } diff --git a/cot/src/response/into_response.rs b/cot/src/response/into_response.rs index 1f1d1f909..256300f7c 100644 --- a/cot/src/response/into_response.rs +++ b/cot/src/response/into_response.rs @@ -1,13 +1,14 @@ use bytes::{Bytes, BytesMut}; use cot::error::error_impl::impl_into_cot_error; use cot::headers::{HTML_CONTENT_TYPE, OCTET_STREAM_CONTENT_TYPE, PLAIN_TEXT_CONTENT_TYPE}; -use cot::response::Response; +use cot::response::{RESPONSE_BUILD_FAILURE, Response}; use cot::{Body, Error, StatusCode}; use http; #[cfg(feature = "json")] use crate::headers::JSON_CONTENT_TYPE; use crate::html::Html; +use crate::response::Redirect; /// Trait for generating responses. /// Types that implement `IntoResponse` can be returned from handlers. @@ -385,6 +386,17 @@ impl IntoResponse for Body { } } +impl IntoResponse for Redirect { + fn into_response(self) -> cot::Result { + let response = http::Response::builder() + .status(StatusCode::SEE_OTHER) + .header(http::header::LOCATION, self.0) + .body(Body::empty()) + .expect(RESPONSE_BUILD_FAILURE); + Ok(response) + } +} + #[cfg(test)] mod tests { use bytes::{Bytes, BytesMut}; diff --git a/cot/src/router.rs b/cot/src/router.rs index 9c6eed37e..3c568af51 100644 --- a/cot/src/router.rs +++ b/cot/src/router.rs @@ -1022,7 +1022,10 @@ macro_rules! reverse_redirect { $request, $view_name, $( $($key = $value),* )? - ).map(|url| <$crate::response::Response as $crate::response::ResponseExt>::new_redirect(url)) + ).map(|url| + $crate::response::IntoResponse::into_response($crate::response::Redirect::new(url)) + .expect("Failed to build response") + ) }; }