2023-08-02 21:35:04 +02:00
|
|
|
|
use std::sync::Arc;
|
|
|
|
|
|
|
2025-09-21 05:50:56 +09:00
|
|
|
|
use axum_core::response::{IntoResponse, Response};
|
2021-12-28 10:41:21 +00:00
|
|
|
|
use bytes::{BufMut, Bytes, BytesMut};
|
2025-09-21 05:50:56 +09:00
|
|
|
|
use http::{header, HeaderValue, StatusCode};
|
2025-09-14 08:49:07 +02:00
|
|
|
|
use serde_core::Serialize;
|
2021-11-17 22:54:02 +01:00
|
|
|
|
|
|
|
|
|
|
/// 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.
|
|
|
|
|
|
///
|
2024-10-06 20:01:10 +01:00
|
|
|
|
/// 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.
|
|
|
|
|
|
///
|
2021-11-17 22:54:02 +01:00
|
|
|
|
/// # Example
|
|
|
|
|
|
///
|
|
|
|
|
|
/// ```rust
|
|
|
|
|
|
/// # use axum::{response::IntoResponse};
|
|
|
|
|
|
/// # use axum_extra::response::ErasedJson;
|
2022-05-22 13:41:29 +02:00
|
|
|
|
/// async fn handler() -> ErasedJson {
|
2021-11-17 22:54:02 +01:00
|
|
|
|
/// # let condition = true;
|
|
|
|
|
|
/// # let foo = ();
|
|
|
|
|
|
/// # let bar = vec![()];
|
|
|
|
|
|
/// // ...
|
|
|
|
|
|
///
|
|
|
|
|
|
/// if condition {
|
|
|
|
|
|
/// ErasedJson::new(&foo)
|
|
|
|
|
|
/// } else {
|
|
|
|
|
|
/// ErasedJson::new(&bar)
|
|
|
|
|
|
/// }
|
|
|
|
|
|
/// }
|
|
|
|
|
|
/// ```
|
2021-12-27 17:12:46 +00:00
|
|
|
|
#[cfg_attr(docsrs, doc(cfg(feature = "erased-json")))]
|
2023-08-02 21:35:04 +02:00
|
|
|
|
#[derive(Clone, Debug)]
|
2023-03-03 14:12:35 +01:00
|
|
|
|
#[must_use]
|
2023-08-02 21:35:04 +02:00
|
|
|
|
pub struct ErasedJson(Result<Bytes, Arc<serde_json::Error>>);
|
2021-11-17 22:54:02 +01:00
|
|
|
|
|
|
|
|
|
|
impl ErasedJson {
|
2021-12-28 11:19:24 +02:00
|
|
|
|
/// Create an `ErasedJson` by serializing a value with the compact formatter.
|
2021-11-17 22:54:02 +01:00
|
|
|
|
pub fn new<T: Serialize>(val: T) -> Self {
|
2021-12-28 10:41:21 +00:00
|
|
|
|
let mut bytes = BytesMut::with_capacity(128);
|
2023-08-02 21:35:04 +02:00
|
|
|
|
let result = match serde_json::to_writer((&mut bytes).writer(), &val) {
|
|
|
|
|
|
Ok(()) => Ok(bytes.freeze()),
|
|
|
|
|
|
Err(e) => Err(Arc::new(e)),
|
|
|
|
|
|
};
|
|
|
|
|
|
Self(result)
|
2021-11-17 22:54:02 +01:00
|
|
|
|
}
|
2021-12-28 11:19:24 +02:00
|
|
|
|
|
|
|
|
|
|
/// Create an `ErasedJson` by serializing a value with the pretty formatter.
|
|
|
|
|
|
pub fn pretty<T: Serialize>(val: T) -> Self {
|
2021-12-28 10:41:21 +00:00
|
|
|
|
let mut bytes = BytesMut::with_capacity(128);
|
2023-08-02 21:35:04 +02:00
|
|
|
|
let result = match serde_json::to_writer_pretty((&mut bytes).writer(), &val) {
|
2025-11-24 21:34:46 +01:00
|
|
|
|
Ok(()) => {
|
|
|
|
|
|
bytes.put_u8(b'\n');
|
|
|
|
|
|
Ok(bytes.freeze())
|
|
|
|
|
|
}
|
2023-08-02 21:35:04 +02:00
|
|
|
|
Err(e) => Err(Arc::new(e)),
|
|
|
|
|
|
};
|
|
|
|
|
|
Self(result)
|
2021-12-28 11:19:24 +02:00
|
|
|
|
}
|
2021-11-17 22:54:02 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl IntoResponse for ErasedJson {
|
2021-12-05 18:16:46 +00:00
|
|
|
|
fn into_response(self) -> Response {
|
2022-03-01 00:04:33 +01:00
|
|
|
|
match self.0 {
|
|
|
|
|
|
Ok(bytes) => (
|
|
|
|
|
|
[(
|
|
|
|
|
|
header::CONTENT_TYPE,
|
|
|
|
|
|
HeaderValue::from_static(mime::APPLICATION_JSON.as_ref()),
|
|
|
|
|
|
)],
|
|
|
|
|
|
bytes,
|
|
|
|
|
|
)
|
|
|
|
|
|
.into_response(),
|
|
|
|
|
|
Err(err) => (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()).into_response(),
|
|
|
|
|
|
}
|
2021-11-17 22:54:02 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2024-10-06 20:01:10 +01:00
|
|
|
|
|
|
|
|
|
|
/// 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;
|
|
|
|
|
|
}
|