Files
axum/axum-extra/src/response/erased_json.rs
T

148 lines
4.3 KiB
Rust
Raw Normal View History

2023-08-02 21:35:04 +02:00
use std::sync::Arc;
use axum_core::response::{IntoResponse, Response};
2021-12-28 10:41:21 +00:00
use bytes::{BufMut, Bytes, BytesMut};
use http::{header, HeaderValue, StatusCode};
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)
/// }
/// }
/// ```
#[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 {
/// 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
}
/// 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) {
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-11-17 22:54:02 +01:00
}
impl IntoResponse for ErasedJson {
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",]);
/// ```
#[cfg_attr(docsrs, doc(cfg(feature = "erased-json")))]
2024-10-06 20:01:10 +01:00
#[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;
}