Repository: seanmonstar/warp
Pull request #813 (2021-03-04 02:52:23)

Add 303 and 308 redirect helpers
The 303 is like the existing 307 except it forces the GET HTTP method. The 308 is like the existing 301 except it reuses the HTTP method and body for the new location.

JakeWharton: I hope this isn't too much for this module since you can obviously build these responses yourself, but 303 is useful for implementing POST endpoints and 308 works around clients sometimes changing HTTP POST to GET in response to a 301.

Patch:
diff --git a/src/redirect.rs b/src/redirect.rs
index 1459cef..7bc040d 100644
--- a/src/redirect.rs
+++ b/src/redirect.rs
@@ -8,7 +8,7 @@ use http::{header, StatusCode};
 use self::sealed::AsLocation;
 use crate::reply::{self, Reply};
 
-/// A simple `301` redirect to a different location.
+/// A simple `301` permanent redirect to a different location.
 ///
 /// # Example
 ///
@@ -28,8 +28,29 @@ pub fn redirect(uri: impl AsLocation) -> impl Reply {
     )
 }
 
+/// A simple `303` redirect to a different location.
+///
+/// The HTTP method of the request to the new location will always be `GET`.
+///
+/// # Example
+///
+/// ```
+/// use warp::{http::Uri, Filter};
+///
+/// let route = warp::path("v1")
+///     .map(|| {
+///         warp::redirect::see_other(Uri::from_static("/v2"))
+///     });
+/// ```
+pub fn see_other(uri: impl AsLocation) -> impl Reply {
+    reply::with_header(StatusCode::SEE_OTHER, header::LOCATION, uri.header_value())
+}
+
 /// A simple `307` temporary redirect to a different location.
 ///
+/// This is similar to [`see_other`](fn@see_other) but the HTTP method and the body of the request
+/// to the new location will be the same as the method and body of the current request.
+///
 /// # Example
 ///
 /// ```
@@ -48,6 +69,29 @@ pub fn temporary(uri: impl AsLocation) -> impl Reply {
     )
 }
 
+/// A simple `308` permanent redirect to a different location.
+///
+/// This is similar to [`redirect`](fn@redirect) but the HTTP method of the request to the new
+/// location will be the same as the method of the current request.
+///
+/// # Example
+///
+/// ```
+/// use warp::{http::Uri, Filter};
+///
+/// let route = warp::path("v1")
+///     .map(|| {
+///         warp::redirect::permanent(Uri::from_static("/v2"))
+///     });
+/// ```
+pub fn permanent(uri: impl AsLocation) -> impl Reply {
+    reply::with_header(
+        StatusCode::PERMANENT_REDIRECT,
+        header::LOCATION,
+        uri.header_value(),
+    )
+}
+
 mod sealed {
     use bytes::Bytes;
     use http::{header::HeaderValue, Uri};


Test patch:
diff --git a/tests/redirect.rs b/tests/redirect.rs
index 5754a9c..911a160 100644
--- a/tests/redirect.rs
+++ b/tests/redirect.rs
@@ -11,3 +11,36 @@ async fn redirect_uri() {
     assert_eq!(resp.status(), 301);
     assert_eq!(resp.headers()["location"], "/over-there");
 }
+
+#[tokio::test]
+async fn redirect_see_other_uri() {
+    let over_there = warp::any().map(|| warp::redirect::see_other(Uri::from_static("/over-there")));
+
+    let req = warp::test::request();
+    let resp = req.reply(&over_there).await;
+
+    assert_eq!(resp.status(), 303);
+    assert_eq!(resp.headers()["location"], "/over-there");
+}
+
+#[tokio::test]
+async fn redirect_temporary_uri() {
+    let over_there = warp::any().map(|| warp::redirect::temporary(Uri::from_static("/over-there")));
+
+    let req = warp::test::request();
+    let resp = req.reply(&over_there).await;
+
+    assert_eq!(resp.status(), 307);
+    assert_eq!(resp.headers()["location"], "/over-there");
+}
+
+#[tokio::test]
+async fn redirect_permanent_uri() {
+    let over_there = warp::any().map(|| warp::redirect::permanent(Uri::from_static("/over-there")));
+
+    let req = warp::test::request();
+    let resp = req.reply(&over_there).await;
+
+    assert_eq!(resp.status(), 308);
+    assert_eq!(resp.headers()["location"], "/over-there");
+}