Skip to main content

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