diff --git a/axum-extra/CHANGELOG.md b/axum-extra/CHANGELOG.md index b8cb0ce9f0..187f4954ee 100644 --- a/axum-extra/CHANGELOG.md +++ b/axum-extra/CHANGELOG.md @@ -7,7 +7,9 @@ and this project adheres to [Semantic Versioning]. # Unreleased -- None. +- **added:** `Clone` implementation for `ErasedJson` ([#2142]) + +[#2142]: https://github.com/tokio-rs/axum/pull/2142 # 0.7.6 (02. August, 2023) diff --git a/axum-extra/src/response/erased_json.rs b/axum-extra/src/response/erased_json.rs index df7257c58e..6e94a267e0 100644 --- a/axum-extra/src/response/erased_json.rs +++ b/axum-extra/src/response/erased_json.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use axum::{ http::{header, HeaderValue, StatusCode}, response::{IntoResponse, Response}, @@ -29,21 +31,29 @@ use serde::Serialize; /// } /// ``` #[cfg_attr(docsrs, doc(cfg(feature = "erased-json")))] -#[derive(Debug)] +#[derive(Clone, Debug)] #[must_use] -pub struct ErasedJson(serde_json::Result); +pub struct ErasedJson(Result>); impl ErasedJson { /// Create an `ErasedJson` by serializing a value with the compact formatter. pub fn new(val: T) -> Self { let mut bytes = BytesMut::with_capacity(128); - Self(serde_json::to_writer((&mut bytes).writer(), &val).map(|_| bytes.freeze())) + let result = match serde_json::to_writer((&mut bytes).writer(), &val) { + Ok(()) => Ok(bytes.freeze()), + Err(e) => Err(Arc::new(e)), + }; + Self(result) } /// Create an `ErasedJson` by serializing a value with the pretty formatter. pub fn pretty(val: T) -> Self { let mut bytes = BytesMut::with_capacity(128); - Self(serde_json::to_writer_pretty((&mut bytes).writer(), &val).map(|_| bytes.freeze())) + let result = match serde_json::to_writer_pretty((&mut bytes).writer(), &val) { + Ok(()) => Ok(bytes.freeze()), + Err(e) => Err(Arc::new(e)), + }; + Self(result) } } diff --git a/axum/CHANGELOG.md b/axum/CHANGELOG.md index 860c645fd9..a617691b47 100644 --- a/axum/CHANGELOG.md +++ b/axum/CHANGELOG.md @@ -10,6 +10,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **added:** `WebSocketUpgrade::write_buffer_size` and `WebSocketUpgrade::max_write_buffer_size` - **changed:** Deprecate `WebSocketUpgrade::max_send_queue` - **change:** Update tokio-tungstenite to 0.20 +- **added:** Implement `Handler` for `T: IntoResponse` ([#2140]) + +[#2140]: https://github.com/tokio-rs/axum/pull/2140 # 0.6.19 (17. July, 2023) diff --git a/axum/src/handler/mod.rs b/axum/src/handler/mod.rs index 9aa88b0896..8cfade7b3a 100644 --- a/axum/src/handler/mod.rs +++ b/axum/src/handler/mod.rs @@ -103,6 +103,31 @@ pub use self::service::HandlerService; /// {} /// ``` #[doc = include_str!("../docs/debugging_handler_type_errors.md")] +/// +/// # Handlers that aren't functions +/// +/// The `Handler` trait is also implemented for `T: IntoResponse`. That allows easily returning +/// fixed data for routes: +/// +/// ``` +/// use axum::{ +/// Router, +/// routing::{get, post}, +/// Json, +/// http::StatusCode, +/// }; +/// use serde_json::json; +/// +/// let app = Router::new() +/// // respond with a fixed string +/// .route("/", get("Hello, World!")) +/// // or return some mock data +/// .route("/users", post(( +/// StatusCode::CREATED, +/// Json(json!({ "id": 1, "username": "alice" })), +/// ))); +/// # let _: Router = app; +/// ``` #[cfg_attr( nightly_error_messages, rustc_on_unimplemented( @@ -231,6 +256,24 @@ macro_rules! impl_handler { all_the_tuples!(impl_handler); +mod private { + // Marker type for `impl Handler for T` + #[allow(missing_debug_implementations)] + pub enum IntoResponseHandler {} +} + +impl Handler for T +where + T: IntoResponse + Clone + Send + 'static, + B: Send + 'static, +{ + type Future = std::future::Ready; + + fn call(self, _req: Request, _state: S) -> Self::Future { + std::future::ready(self.into_response()) + } +} + /// A [`Service`] created from a [`Handler`] by applying a Tower middleware. /// /// Created with [`Handler::layer`]. See that method for more details. diff --git a/axum/src/routing/tests/mod.rs b/axum/src/routing/tests/mod.rs index ad7d5067f5..f1a459d645 100644 --- a/axum/src/routing/tests/mod.rs +++ b/axum/src/routing/tests/mod.rs @@ -1026,3 +1026,14 @@ async fn connect_going_to_default_fallback() { let body = hyper::body::to_bytes(res).await.unwrap(); assert!(body.is_empty()); } + +#[crate::test] +async fn impl_handler_for_into_response() { + let app = Router::new().route("/things", post((StatusCode::CREATED, "thing created"))); + + let client = TestClient::new(app); + + let res = client.post("/things").send().await; + assert_eq!(res.status(), StatusCode::CREATED); + assert_eq!(res.text().await, "thing created"); +}