diff --git a/axum-extra/Cargo.toml b/axum-extra/Cargo.toml index 7efd9e02..432bb293 100644 --- a/axum-extra/Cargo.toml +++ b/axum-extra/Cargo.toml @@ -10,5 +10,10 @@ readme = "README.md" repository = "https://github.com/tokio-rs/axum" version = "0.1.0" +[features] +erased-json = ["serde", "serde_json"] + [dependencies] axum = { path = "../axum", version = "0.3" } +serde = { version = "1.0.130", optional = true } +serde_json = { version = "1.0.71", optional = true } diff --git a/axum-extra/src/lib.rs b/axum-extra/src/lib.rs index 4d623774..945d9318 100644 --- a/axum-extra/src/lib.rs +++ b/axum-extra/src/lib.rs @@ -41,3 +41,5 @@ #![forbid(unsafe_code)] #![cfg_attr(docsrs, feature(doc_cfg))] #![cfg_attr(test, allow(clippy::float_cmp))] + +pub mod response; diff --git a/axum-extra/src/response/erased_json.rs b/axum-extra/src/response/erased_json.rs new file mode 100644 index 00000000..6ade7119 --- /dev/null +++ b/axum-extra/src/response/erased_json.rs @@ -0,0 +1,67 @@ +use std::convert::Infallible; + +use axum::{ + body::{Bytes, Full}, + http::{header, HeaderValue, Response, StatusCode}, + response::IntoResponse, +}; +use serde::Serialize; + +/// A response type that holds a JSON in serialized form. +/// +/// This allows returning a borrowing type from a handler, or returning different response +/// types as JSON from different branches inside a handler. +/// +/// # Example +/// +/// ```rust +/// # use axum::{response::IntoResponse}; +/// # use axum_extra::response::ErasedJson; +/// async fn handler() -> impl IntoResponse { +/// # let condition = true; +/// # let foo = (); +/// # let bar = vec![()]; +/// // ... +/// +/// if condition { +/// ErasedJson::new(&foo) +/// } else { +/// ErasedJson::new(&bar) +/// } +/// } +/// ``` +#[derive(Debug)] +pub struct ErasedJson(serde_json::Result>); + +impl ErasedJson { + /// Create an `ErasedJson` by serializing a value. + pub fn new(val: T) -> Self { + Self(serde_json::to_vec(&val)) + } +} + +impl IntoResponse for ErasedJson { + type Body = Full; + type BodyError = Infallible; + + fn into_response(self) -> Response { + #[allow(clippy::declare_interior_mutable_const)] + const APPLICATION_JSON: HeaderValue = HeaderValue::from_static("application/json"); + + let bytes = match self.0 { + Ok(res) => res, + Err(err) => { + return Response::builder() + .status(StatusCode::INTERNAL_SERVER_ERROR) + .header(header::CONTENT_TYPE, "text/plain") + .body(Full::from(err.to_string())) + .unwrap(); + } + }; + + let mut res = Response::new(Full::from(bytes)); + res.headers_mut() + .insert(header::CONTENT_TYPE, APPLICATION_JSON); + res + } +} diff --git a/axum-extra/src/response/mod.rs b/axum-extra/src/response/mod.rs new file mode 100644 index 00000000..9324c805 --- /dev/null +++ b/axum-extra/src/response/mod.rs @@ -0,0 +1,7 @@ +//! Additional types for generating responses. + +#[cfg(feature = "erased-json")] +mod erased_json; + +#[cfg(feature = "erased-json")] +pub use erased_json::ErasedJson; diff --git a/axum/Cargo.toml b/axum/Cargo.toml index aac4ee36..e3ac7948 100644 --- a/axum/Cargo.toml +++ b/axum/Cargo.toml @@ -45,7 +45,7 @@ base64 = { optional = true, version = "0.13" } headers = { optional = true, version = "0.3" } mime = { optional = true, version = "0.3" } multer = { optional = true, version = "2.0.0" } -serde_json = { version = "1.0", optional = true } +serde_json = { version = "1.0", optional = true, features = ["raw_value"] } sha-1 = { optional = true, version = "0.9.6" } tokio-tungstenite = { optional = true, version = "0.16" }