axum_extra/response/
erased_json.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
use std::sync::Arc;

use axum::{
    http::{header, HeaderValue, StatusCode},
    response::{IntoResponse, Response},
};
use bytes::{BufMut, Bytes, BytesMut};
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.
///
/// 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
/// # use axum::{response::IntoResponse};
/// # use axum_extra::response::ErasedJson;
/// async fn handler() -> ErasedJson {
///     # 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")))]
#[derive(Clone, Debug)]
#[must_use]
pub struct ErasedJson(Result<Bytes, Arc<serde_json::Error>>);

impl ErasedJson {
    /// Create an `ErasedJson` by serializing a value with the compact formatter.
    pub fn new<T: Serialize>(val: T) -> Self {
        let mut bytes = BytesMut::with_capacity(128);
        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<T: Serialize>(val: T) -> Self {
        let mut bytes = BytesMut::with_capacity(128);
        let result = match serde_json::to_writer_pretty((&mut bytes).writer(), &val) {
            Ok(()) => Ok(bytes.freeze()),
            Err(e) => Err(Arc::new(e)),
        };
        Self(result)
    }
}

impl IntoResponse for ErasedJson {
    fn into_response(self) -> Response {
        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(),
        }
    }
}

/// 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;
}