xitca_web/handler/types/
json.rs

1//! type extractor and response generator for json
2
3use core::{
4    convert::Infallible,
5    fmt,
6    marker::PhantomData,
7    ops::{Deref, DerefMut},
8};
9
10use serde::{de::Deserialize, ser::Serialize};
11use xitca_http::util::service::router::{PathGen, RouteGen, RouterMapErr};
12
13use crate::{
14    body::BodyStream,
15    bytes::{BufMutWriter, Bytes, BytesMut},
16    context::WebContext,
17    error::{Error, error_from_service, forward_blank_bad_request},
18    handler::{FromRequest, Responder},
19    http::{WebResponse, const_header_value::JSON, header::CONTENT_TYPE},
20    service::Service,
21};
22
23use super::{
24    body::Limit,
25    header::{self, HeaderRef},
26};
27
28pub const DEFAULT_LIMIT: usize = 1024 * 1024;
29
30/// Extract type for Json object. const generic param LIMIT is for max size of the object in bytes.
31/// Object larger than limit would be treated as error.
32///
33/// Default limit is [DEFAULT_LIMIT] in bytes.
34#[derive(Clone)]
35pub struct Json<T, const LIMIT: usize = DEFAULT_LIMIT>(pub T);
36
37impl<T, const LIMIT: usize> fmt::Debug for Json<T, LIMIT>
38where
39    T: fmt::Debug,
40{
41    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42        f.debug_struct("Json")
43            .field("value", &self.0)
44            .field("limit", &LIMIT)
45            .finish()
46    }
47}
48
49impl<T, const LIMIT: usize> Deref for Json<T, LIMIT> {
50    type Target = T;
51
52    fn deref(&self) -> &Self::Target {
53        &self.0
54    }
55}
56
57impl<T, const LIMIT: usize> DerefMut for Json<T, LIMIT> {
58    fn deref_mut(&mut self) -> &mut Self::Target {
59        &mut self.0
60    }
61}
62
63impl<'a, 'r, C, B, T, const LIMIT: usize> FromRequest<'a, WebContext<'r, C, B>> for Json<T, LIMIT>
64where
65    B: BodyStream + Default,
66    T: for<'de> Deserialize<'de>,
67{
68    type Type<'b> = Json<T, LIMIT>;
69    type Error = Error;
70
71    async fn from_request(ctx: &'a WebContext<'r, C, B>) -> Result<Self, Self::Error> {
72        HeaderRef::<'a, { header::CONTENT_TYPE }>::from_request(ctx).await?;
73        let (bytes, _) = <(BytesMut, Limit<LIMIT>)>::from_request(ctx).await?;
74        serde_json::from_slice(&bytes).map(Json).map_err(Into::into)
75    }
76}
77
78/// lazy deserialize type that wrap around [Json]. It lowers the deserialization to handler
79/// function where zero copy deserialize can happen.
80///
81/// # Example
82/// ```rust
83/// # use serde::Deserialize;
84/// # use xitca_web::{
85/// #   error::Error,
86/// #   http::StatusCode,
87/// #   handler::{handler_service, json::LazyJson},
88/// #   App, WebContext
89/// # };
90/// // a json object with zero copy deserialization.
91/// #[derive(Deserialize)]
92/// struct Post<'a> {
93///     title: &'a str,
94///     content: &'a str    
95/// }
96///
97/// // handler function utilize Lazy type to lower the Json type into handler function.
98/// async fn handler(lazy: LazyJson<Post<'_>>) -> Result<String, Error> {
99///     // actual deserialize happens here.
100///     let Post { title, content } = lazy.deserialize()?;
101///     // the Post type and it's &str referencing LazyJson would live until the handler
102///     // function return.
103///     Ok(format!("Post {{ title: {title}, content: {content} }}"))
104/// }
105///
106/// App::new()
107///     .at("/post", handler_service(handler))
108///     # .at("/", handler_service(|_: &WebContext<'_>| async { "used for infer type" }));
109/// ```
110pub struct LazyJson<T, const LIMIT: usize = DEFAULT_LIMIT> {
111    bytes: Vec<u8>,
112    _json: PhantomData<T>,
113}
114
115impl<T, const LIMIT: usize> LazyJson<T, LIMIT> {
116    pub fn deserialize<'de>(&'de self) -> Result<T, Error>
117    where
118        T: Deserialize<'de>,
119    {
120        serde_json::from_slice(&self.bytes).map_err(Into::into)
121    }
122}
123
124impl<'a, 'r, C, B, T, const LIMIT: usize> FromRequest<'a, WebContext<'r, C, B>> for LazyJson<T, LIMIT>
125where
126    B: BodyStream + Default,
127    T: Deserialize<'static>,
128{
129    type Type<'b> = LazyJson<T, LIMIT>;
130    type Error = Error;
131
132    async fn from_request(ctx: &'a WebContext<'r, C, B>) -> Result<Self, Self::Error> {
133        HeaderRef::<'a, { header::CONTENT_TYPE }>::from_request(ctx).await?;
134        let (bytes, _) = <(Vec<u8>, Limit<LIMIT>)>::from_request(ctx).await?;
135        Ok(LazyJson {
136            bytes,
137            _json: PhantomData,
138        })
139    }
140}
141
142impl<'r, C, B, T> Responder<WebContext<'r, C, B>> for Json<T>
143where
144    T: Serialize,
145{
146    type Response = WebResponse;
147    type Error = Error;
148
149    #[inline]
150    async fn respond(self, ctx: WebContext<'r, C, B>) -> Result<Self::Response, Self::Error> {
151        self._respond(|bytes| ctx.into_response(bytes))
152    }
153
154    #[inline]
155    fn map(self, res: Self::Response) -> Result<Self::Response, Self::Error> {
156        self._respond(|bytes| res.map(|_| bytes.into()))
157    }
158}
159
160impl<T> Json<T> {
161    fn _respond<F>(self, func: F) -> Result<WebResponse, Error>
162    where
163        T: Serialize,
164        F: FnOnce(Bytes) -> WebResponse,
165    {
166        let mut bytes = BytesMut::new();
167        serde_json::to_writer(BufMutWriter(&mut bytes), &self.0)?;
168        let mut res = func(bytes.freeze());
169        res.headers_mut().insert(CONTENT_TYPE, JSON);
170        Ok(res)
171    }
172}
173
174impl<'r, C, B> Responder<WebContext<'r, C, B>> for serde_json::Value {
175    type Response = WebResponse;
176    type Error = Error;
177
178    #[inline]
179    async fn respond(self, ctx: WebContext<'r, C, B>) -> Result<Self::Response, Self::Error> {
180        Json(self).respond(ctx).await
181    }
182
183    #[inline]
184    fn map(self, res: Self::Response) -> Result<Self::Response, Self::Error> {
185        Responder::<WebContext<'r, C, B>>::map(Json(self), res)
186    }
187}
188
189error_from_service!(serde_json::Error);
190forward_blank_bad_request!(serde_json::Error);
191
192impl<T> PathGen for Json<T> {}
193
194impl<T> RouteGen for Json<T> {
195    type Route<R> = RouterMapErr<R>;
196
197    fn route_gen<R>(route: R) -> Self::Route<R> {
198        RouterMapErr(route)
199    }
200}
201
202impl<T> Service for Json<T>
203where
204    T: Clone,
205{
206    type Response = Self;
207    type Error = Infallible;
208
209    async fn call(&self, _: ()) -> Result<Self::Response, Self::Error> {
210        Ok(self.clone())
211    }
212}
213
214impl<'r, C, B, T> Service<WebContext<'r, C, B>> for Json<T>
215where
216    T: Serialize + Clone,
217{
218    type Response = WebResponse;
219    type Error = Error;
220
221    #[inline]
222    async fn call(&self, ctx: WebContext<'r, C, B>) -> Result<Self::Response, Self::Error> {
223        self.clone().respond(ctx).await
224    }
225}
226
227#[cfg(test)]
228mod test {
229    use xitca_unsafe_collection::futures::NowOrPanic;
230
231    use crate::{
232        App,
233        handler::handler_service,
234        http::{WebRequest, header::CONTENT_LENGTH},
235        test::collect_string_body,
236    };
237
238    use super::*;
239
240    #[derive(serde::Deserialize, serde::Serialize, Clone)]
241    struct Gacha<'a> {
242        credit_card: &'a str,
243    }
244
245    #[test]
246    fn extract_lazy() {
247        let mut ctx = WebContext::new_test(&());
248        let mut ctx = ctx.as_web_ctx();
249
250        let body = serde_json::to_string(&Gacha {
251            credit_card: "declined",
252        })
253        .unwrap();
254
255        ctx.req_mut().headers_mut().insert(CONTENT_TYPE, JSON);
256        ctx.req_mut().headers_mut().insert(CONTENT_LENGTH, body.len().into());
257
258        *ctx.body_borrow_mut() = body.into();
259
260        async fn handler(lazy: LazyJson<Gacha<'_>>) -> &'static str {
261            let ga = lazy.deserialize().unwrap();
262            assert_eq!(ga.credit_card, "declined");
263            "bankruptcy"
264        }
265
266        let service = handler_service(handler).call(()).now_or_panic().unwrap();
267
268        let body = service.call(ctx).now_or_panic().unwrap().into_body();
269        let res = collect_string_body(body).now_or_panic().unwrap();
270
271        assert_eq!(res, "bankruptcy");
272    }
273
274    #[test]
275    fn service() {
276        let res = App::new()
277            .at("/", Json(Gacha { credit_card: "mom" }))
278            .finish()
279            .call(())
280            .now_or_panic()
281            .unwrap()
282            .call(WebRequest::default())
283            .now_or_panic()
284            .unwrap();
285        assert_eq!(res.status().as_u16(), 200);
286    }
287}