mirror of
https://github.com/tokio-rs/axum.git
synced 2026-08-15 00:00:20 +02:00
feat: impl IntoResponseParts for Redirect (#3721)
This commit is contained in:
@@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
# Unreleased
|
||||
|
||||
- **added:** `ResponseParts::status` and `ResponseParts::status_mut` accessors,
|
||||
allowing `IntoResponseParts` implementations to set the response status ([#3721])
|
||||
|
||||
[#3721]: https://github.com/tokio-rs/axum/pull/3721
|
||||
|
||||
# 0.5.6
|
||||
|
||||
Improve error messages with `#[diagnostic::do_not_recommend]`.
|
||||
|
||||
@@ -107,6 +107,18 @@ pub struct ResponseParts {
|
||||
}
|
||||
|
||||
impl ResponseParts {
|
||||
/// Gets the response status code.
|
||||
#[must_use]
|
||||
pub fn status(&self) -> StatusCode {
|
||||
self.res.status()
|
||||
}
|
||||
|
||||
/// Gets a mutable reference to the response status code.
|
||||
#[must_use]
|
||||
pub fn status_mut(&mut self) -> &mut StatusCode {
|
||||
self.res.status_mut()
|
||||
}
|
||||
|
||||
/// Gets a reference to the response headers.
|
||||
#[must_use]
|
||||
pub fn headers(&self) -> &HeaderMap {
|
||||
|
||||
@@ -18,6 +18,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- **added:** `MethodRouter::method_filter` ([#3586])
|
||||
- **added:** `serve::Executor` trait and `Serve::with_executor` for customizing how connection
|
||||
tasks are spawned, enabling use cases like tracing and telemetry instrumentation ([#3704])
|
||||
- **added:** `IntoResponseParts` impl for `Redirect`, allowing it to be combined
|
||||
with a body in a response tuple ([#3721])
|
||||
- **changed:** `serve` has an additional generic argument and can now work with any response body
|
||||
type, not just `axum::body::Body` ([#3205])
|
||||
- **changed:** `Redirect` constructors now accept any `impl Into<String>` ([#3635])
|
||||
@@ -32,6 +34,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
[#3586]: https://github.com/tokio-rs/axum/pull/3586
|
||||
[#3635]: https://github.com/tokio-rs/axum/pull/3635
|
||||
[#3702]: https://github.com/tokio-rs/axum/pull/3702
|
||||
[#3721]: https://github.com/tokio-rs/axum/pull/3721
|
||||
|
||||
# 0.8.9
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use axum_core::response::{IntoResponse, Response};
|
||||
use axum_core::response::{IntoResponse, IntoResponseParts, Response, ResponseParts};
|
||||
use http::{header::LOCATION, HeaderValue, StatusCode};
|
||||
|
||||
/// Response that redirects the request to another location.
|
||||
@@ -93,11 +93,65 @@ impl IntoResponse for Redirect {
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponseParts for Redirect {
|
||||
type Error = (StatusCode, String);
|
||||
|
||||
/// Sets the redirect status code and `Location` header on the response.
|
||||
///
|
||||
/// This allows `Redirect` to be used as part of a response tuple, for example
|
||||
/// to include a body alongside a redirect as recommended by
|
||||
/// [RFC 9110 §15.4.4](https://datatracker.ietf.org/doc/html/rfc9110#name-303-see-other).
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use axum::response::{Html, Redirect};
|
||||
///
|
||||
/// let url = "https://example.com";
|
||||
///
|
||||
/// // Return a redirect with a body
|
||||
/// let response = (
|
||||
/// Redirect::to(url),
|
||||
/// Html(format!(
|
||||
/// r#"<p>Redirecting to <a href="{url}">{url}</a></p>"#,
|
||||
/// )),
|
||||
/// );
|
||||
/// ```
|
||||
///
|
||||
/// Note that when used alongside an explicit [`StatusCode`] in a tuple, the
|
||||
/// `StatusCode` takes precedence:
|
||||
///
|
||||
/// ```rust
|
||||
/// use axum::response::Redirect;
|
||||
/// use axum::http::StatusCode;
|
||||
///
|
||||
/// // The status will be 307, not 303
|
||||
/// let response = (
|
||||
/// StatusCode::TEMPORARY_REDIRECT,
|
||||
/// Redirect::to("/new"),
|
||||
/// "redirecting...",
|
||||
/// );
|
||||
/// ```
|
||||
fn into_response_parts(self, mut res: ResponseParts) -> Result<ResponseParts, Self::Error> {
|
||||
let location = HeaderValue::try_from(self.location).map_err(|err| {
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
format!("invalid redirect location: {err}"),
|
||||
)
|
||||
})?;
|
||||
|
||||
*res.status_mut() = self.status_code;
|
||||
res.headers_mut().insert(LOCATION, location);
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Redirect;
|
||||
use axum_core::response::IntoResponse;
|
||||
use http::StatusCode;
|
||||
use http::{header::LOCATION, StatusCode};
|
||||
|
||||
const EXAMPLE_URL: &str = "https://example.com";
|
||||
|
||||
@@ -135,4 +189,49 @@ mod tests {
|
||||
|
||||
assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn into_response_parts_sets_status_and_location() {
|
||||
let response = (Redirect::to(EXAMPLE_URL), "body").into_response();
|
||||
|
||||
assert_eq!(response.status(), StatusCode::SEE_OTHER);
|
||||
assert_eq!(response.headers().get(LOCATION).unwrap(), EXAMPLE_URL);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn into_response_parts_with_permanent_redirect() {
|
||||
let response = (Redirect::permanent(EXAMPLE_URL), "body").into_response();
|
||||
|
||||
assert_eq!(response.status(), StatusCode::PERMANENT_REDIRECT);
|
||||
assert_eq!(response.headers().get(LOCATION).unwrap(), EXAMPLE_URL);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn into_response_parts_with_temporary_redirect() {
|
||||
let response = (Redirect::temporary(EXAMPLE_URL), "body").into_response();
|
||||
|
||||
assert_eq!(response.status(), StatusCode::TEMPORARY_REDIRECT);
|
||||
assert_eq!(response.headers().get(LOCATION).unwrap(), EXAMPLE_URL);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn into_response_parts_explicit_status_overrides() {
|
||||
// Explicit StatusCode in a tuple takes precedence over the Redirect status
|
||||
let response = (
|
||||
StatusCode::TEMPORARY_REDIRECT,
|
||||
Redirect::to(EXAMPLE_URL),
|
||||
"body",
|
||||
)
|
||||
.into_response();
|
||||
|
||||
assert_eq!(response.status(), StatusCode::TEMPORARY_REDIRECT);
|
||||
assert_eq!(response.headers().get(LOCATION).unwrap(), EXAMPLE_URL);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn into_response_parts_invalid_location() {
|
||||
let response = (Redirect::permanent("invalid\nlocation"), "body").into_response();
|
||||
|
||||
assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user