Add axum_extra::json! (#2962)

This commit is contained in:
Sabrina Jewson 2024-10-06 20:01:10 +01:00 committed by Jonas Platte
parent 5b6d1caaa7
commit b71d4fa557
No known key found for this signature in database
GPG key ID: 7D261D771D915378
5 changed files with 91 additions and 2 deletions

View file

@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog],
and this project adheres to [Semantic Versioning].
# Unreleased
- **added:** Add `json!` for easy construction of JSON responses ([#2962])
[#2962]: https://github.com/tokio-rs/axum/pull/2962
# 0.9.4
- **added:** The `response::Attachment` type ([#2789])

View file

@ -20,7 +20,7 @@ cookie = ["dep:cookie"]
cookie-private = ["cookie", "cookie?/private"]
cookie-signed = ["cookie", "cookie?/signed"]
cookie-key-expansion = ["cookie", "cookie?/key-expansion"]
erased-json = ["dep:serde_json"]
erased-json = ["dep:serde_json", "dep:typed-json"]
form = ["dep:serde_html_form"]
json-deserializer = ["dep:serde_json", "dep:serde_path_to_error"]
json-lines = [
@ -69,9 +69,11 @@ tokio = { version = "1.19", optional = true }
tokio-stream = { version = "0.1.9", optional = true }
tokio-util = { version = "0.7", optional = true }
tracing = { version = "0.1.37", default-features = false, optional = true }
typed-json = { version = "0.1.1", optional = true }
[dev-dependencies]
axum = { path = "../axum", version = "0.7.2" }
axum = { path = "../axum", features = ["macros"] }
axum-macros = { path = "../axum-macros", features = ["__private"] }
hyper = "1.0.0"
reqwest = { version = "0.12", default-features = false, features = ["json", "stream", "multipart"] }
serde = { version = "1.0", features = ["derive"] }

View file

@ -12,6 +12,15 @@ use serde::Serialize;
/// This allows returning a borrowing type from a handler, or returning different response
/// types as JSON from different branches inside a handler.
///
/// Like [`axum::Json`],
/// if the [`Serialize`] implementation fails
/// or if a map with non-string keys is used,
/// a 500 response will be issued
/// whose body is the error message in UTF-8.
///
/// This can be constructed using [`new`](ErasedJson::new)
/// or the [`json!`](crate::json) macro.
///
/// # Example
///
/// ```rust
@ -72,3 +81,65 @@ impl IntoResponse for ErasedJson {
}
}
}
/// Construct an [`ErasedJson`] response from a JSON literal.
///
/// A `Content-Type: application/json` header is automatically added.
/// Any variable or expression implementing [`Serialize`]
/// can be interpolated as a value in the literal.
/// If the [`Serialize`] implementation fails,
/// or if a map with non-string keys is used,
/// a 500 response will be issued
/// whose body is the error message in UTF-8.
///
/// Internally,
/// this function uses the [`typed_json::json!`] macro,
/// allowing it to perform far fewer allocations
/// than a dynamic macro like [`serde_json::json!`] would
/// it's equivalent to if you had just written
/// `derive(Serialize)` on a struct.
///
/// # Examples
///
/// ```
/// use axum::{
/// Router,
/// extract::Path,
/// response::Response,
/// routing::get,
/// };
/// use axum_extra::response::ErasedJson;
///
/// async fn get_user(Path(user_id) : Path<u64>) -> ErasedJson {
/// let user_name = find_user_name(user_id).await;
/// axum_extra::json!({ "name": user_name })
/// }
///
/// async fn find_user_name(user_id: u64) -> String {
/// // ...
/// # unimplemented!()
/// }
///
/// let app = Router::new().route("/users/{id}", get(get_user));
/// # let _: Router = app;
/// ```
///
/// Trailing commas are allowed in both arrays and objects.
///
/// ```
/// let response = axum_extra::json!(["trailing",]);
/// ```
#[macro_export]
macro_rules! json {
($($t:tt)*) => {
$crate::response::ErasedJson::new(
$crate::response::__private_erased_json::typed_json::json!($($t)*)
)
}
}
/// Not public API. Re-exported as `crate::response::__private_erased_json`.
#[doc(hidden)]
pub mod private {
pub use typed_json;
}

View file

@ -12,6 +12,11 @@ pub mod multiple;
#[cfg(feature = "erased-json")]
pub use erased_json::ErasedJson;
/// _not_ public API
#[cfg(feature = "erased-json")]
#[doc(hidden)]
pub use erased_json::private as __private_erased_json;
#[cfg(feature = "json-lines")]
#[doc(no_inline)]
pub use crate::json_lines::JsonLines;

View file

@ -55,6 +55,11 @@ use serde::{de::DeserializeOwned, Serialize};
/// When used as a response, it can serialize any type that implements [`serde::Serialize`] to
/// `JSON`, and will automatically set `Content-Type: application/json` header.
///
/// If the [`Serialize`] implementation decides to fail
/// or if a map with non-string keys is used,
/// a 500 response will be issued
/// whose body is the error message in UTF-8.
///
/// # Response example
///
/// ```