diff --git a/axum-core/CHANGELOG.md b/axum-core/CHANGELOG.md index 01c31d065f..4a5c22e49d 100644 --- a/axum-core/CHANGELOG.md +++ b/axum-core/CHANGELOG.md @@ -7,7 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 # Unreleased -- None. +- **added:** Add `AppendHeaders` for appending headers to a response rather than overriding them ([#927]) + +[#927]: https://github.com/tokio-rs/axum/pull/927 # 0.2.1 (03. April, 2022) diff --git a/axum-core/src/response/append_headers.rs b/axum-core/src/response/append_headers.rs new file mode 100644 index 0000000000..0eb7c14b40 --- /dev/null +++ b/axum-core/src/response/append_headers.rs @@ -0,0 +1,65 @@ +use super::{IntoResponse, IntoResponseParts, Response, ResponseParts, TryIntoHeaderError}; +use http::header::{HeaderName, HeaderValue}; +use std::{convert::TryInto, fmt}; + +/// Append headers to a response. +/// +/// Returning something like `[("content-type", "foo=bar")]` from a handler will override any +/// existing `content-type` headers. If instead you want to append headers, use `AppendHeaders`: +/// +/// ```rust +/// use axum::{ +/// response::{AppendHeaders, IntoResponse}, +/// http::header::SET_COOKIE, +/// }; +/// +/// async fn handler() -> impl IntoResponse { +/// // something that sets the `set-cookie` header +/// let set_some_cookies = /* ... */ +/// # axum::http::HeaderMap::new(); +/// +/// ( +/// set_some_cookies, +/// // append two `set-cookie` headers to the response +/// // without overriding the ones added by `set_some_cookies` +/// AppendHeaders([ +/// (SET_COOKIE, "foo=bar"), +/// (SET_COOKIE, "baz=qux"), +/// ]) +/// ) +/// } +/// ``` +#[derive(Debug)] +pub struct AppendHeaders(pub [(K, V); N]); + +impl IntoResponse for AppendHeaders +where + K: TryInto, + K::Error: fmt::Display, + V: TryInto, + V::Error: fmt::Display, +{ + fn into_response(self) -> Response { + (self, ()).into_response() + } +} + +impl IntoResponseParts for AppendHeaders +where + K: TryInto, + K::Error: fmt::Display, + V: TryInto, + V::Error: fmt::Display, +{ + type Error = TryIntoHeaderError; + + fn into_response_parts(self, mut res: ResponseParts) -> Result { + for (key, value) in self.0 { + let key = key.try_into().map_err(TryIntoHeaderError::key)?; + let value = value.try_into().map_err(TryIntoHeaderError::value)?; + res.headers_mut().append(key, value); + } + + Ok(res) + } +} diff --git a/axum-core/src/response/into_response.rs b/axum-core/src/response/into_response.rs index 560fc58537..c2cdcf81bc 100644 --- a/axum-core/src/response/into_response.rs +++ b/axum-core/src/response/into_response.rs @@ -392,27 +392,7 @@ where V::Error: fmt::Display, { fn into_response(self) -> Response { - let mut res = ().into_response(); - - for (key, value) in self { - let key = match key.try_into() { - Ok(key) => key, - Err(err) => { - return (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()).into_response() - } - }; - - let value = match value.try_into() { - Ok(value) => value, - Err(err) => { - return (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()).into_response() - } - }; - - res.headers_mut().insert(key, value); - } - - res + (self, ()).into_response() } } diff --git a/axum-core/src/response/into_response_parts.rs b/axum-core/src/response/into_response_parts.rs index e85855b18a..3018b5e81d 100644 --- a/axum-core/src/response/into_response_parts.rs +++ b/axum-core/src/response/into_response_parts.rs @@ -161,13 +161,13 @@ pub struct TryIntoHeaderError { } impl TryIntoHeaderError { - fn key(err: K) -> Self { + pub(super) fn key(err: K) -> Self { Self { kind: TryIntoHeaderErrorKind::Key(err), } } - fn value(err: V) -> Self { + pub(super) fn value(err: V) -> Self { Self { kind: TryIntoHeaderErrorKind::Value(err), } diff --git a/axum-core/src/response/mod.rs b/axum-core/src/response/mod.rs index 687743fe82..6a15ba7344 100644 --- a/axum-core/src/response/mod.rs +++ b/axum-core/src/response/mod.rs @@ -6,10 +6,12 @@ use crate::body::BoxBody; +mod append_headers; mod into_response; mod into_response_parts; pub use self::{ + append_headers::AppendHeaders, into_response::IntoResponse, into_response_parts::{IntoResponseParts, ResponseParts, TryIntoHeaderError}, }; diff --git a/axum/CHANGELOG.md b/axum/CHANGELOG.md index 533f7ffe19..34ca6805ba 100644 --- a/axum/CHANGELOG.md +++ b/axum/CHANGELOG.md @@ -7,11 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 # Unreleased +- **added:** Add `AppendHeaders` for appending headers to a response rather than overriding them ([#927]) - **added:** Add `axum::extract::multipart::Field::chunk` method for streaming a single chunk from the field ([#901]) - **fixed:** Fix trailing slash redirection with query parameters ([#936]) [#901]: https://github.com/tokio-rs/axum/pull/901 +[#927]: https://github.com/tokio-rs/axum/pull/927 [#936]: https://github.com/tokio-rs/axum/pull/936 # 0.5.1 (03. April, 2022) diff --git a/axum/src/response/mod.rs b/axum/src/response/mod.rs index e5bb465b33..d8fade41f1 100644 --- a/axum/src/response/mod.rs +++ b/axum/src/response/mod.rs @@ -19,7 +19,9 @@ pub use crate::TypedHeader; pub use crate::Extension; #[doc(inline)] -pub use axum_core::response::{IntoResponse, IntoResponseParts, Response, ResponseParts}; +pub use axum_core::response::{ + AppendHeaders, IntoResponse, IntoResponseParts, Response, ResponseParts, +}; #[doc(inline)] pub use self::{redirect::Redirect, sse::Sse};