mirror of
https://github.com/tokio-rs/axum.git
synced 2026-09-01 00:00:14 +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/),
|
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).
|
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
|
# 0.5.6
|
||||||
|
|
||||||
Improve error messages with `#[diagnostic::do_not_recommend]`.
|
Improve error messages with `#[diagnostic::do_not_recommend]`.
|
||||||
|
|||||||
@@ -107,6 +107,18 @@ pub struct ResponseParts {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl 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.
|
/// Gets a reference to the response headers.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn headers(&self) -> &HeaderMap {
|
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:** `MethodRouter::method_filter` ([#3586])
|
||||||
- **added:** `serve::Executor` trait and `Serve::with_executor` for customizing how connection
|
- **added:** `serve::Executor` trait and `Serve::with_executor` for customizing how connection
|
||||||
tasks are spawned, enabling use cases like tracing and telemetry instrumentation ([#3704])
|
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
|
- **changed:** `serve` has an additional generic argument and can now work with any response body
|
||||||
type, not just `axum::body::Body` ([#3205])
|
type, not just `axum::body::Body` ([#3205])
|
||||||
- **changed:** `Redirect` constructors now accept any `impl Into<String>` ([#3635])
|
- **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
|
[#3586]: https://github.com/tokio-rs/axum/pull/3586
|
||||||
[#3635]: https://github.com/tokio-rs/axum/pull/3635
|
[#3635]: https://github.com/tokio-rs/axum/pull/3635
|
||||||
[#3702]: https://github.com/tokio-rs/axum/pull/3702
|
[#3702]: https://github.com/tokio-rs/axum/pull/3702
|
||||||
|
[#3721]: https://github.com/tokio-rs/axum/pull/3721
|
||||||
|
|
||||||
# 0.8.9
|
# 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};
|
use http::{header::LOCATION, HeaderValue, StatusCode};
|
||||||
|
|
||||||
/// Response that redirects the request to another location.
|
/// 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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Redirect;
|
use super::Redirect;
|
||||||
use axum_core::response::IntoResponse;
|
use axum_core::response::IntoResponse;
|
||||||
use http::StatusCode;
|
use http::{header::LOCATION, StatusCode};
|
||||||
|
|
||||||
const EXAMPLE_URL: &str = "https://example.com";
|
const EXAMPLE_URL: &str = "https://example.com";
|
||||||
|
|
||||||
@@ -135,4 +189,49 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
|
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