xitca_web/
context.rs

1//! web context types.
2
3use core::{
4    cell::{Ref, RefCell, RefMut},
5    mem,
6};
7
8use super::{
9    body::{RequestBody, ResponseBody},
10    handler::FromRequest,
11    http::{BorrowReq, BorrowReqMut, IntoResponse, Request, RequestExt, WebRequest, WebResponse},
12};
13
14/// web context type focus on stateful and side effect based request data access.
15pub struct WebContext<'a, C = (), B = RequestBody> {
16    pub(crate) req: &'a mut WebRequest<()>,
17    pub(crate) body: &'a mut RefCell<B>,
18    pub(crate) ctx: &'a C,
19}
20
21impl<'a, C, B> WebContext<'a, C, B> {
22    pub(crate) fn new(req: &'a mut WebRequest<()>, body: &'a mut RefCell<B>, ctx: &'a C) -> Self {
23        Self { req, body, ctx }
24    }
25
26    /// Reborrow Self so the ownership of WebRequest is not lost.
27    ///
28    /// # Note:
29    ///
30    /// Reborrow is not pure and receiver of it can mutate Self in any way they like.
31    ///
32    /// # Example:
33    /// ```rust
34    /// # use xitca_web::WebContext;
35    /// // a function need ownership of request but not return it in output.
36    /// fn handler(req: WebContext<'_>) -> Result<(), ()> {
37    ///     Err(())
38    /// }
39    ///
40    /// # fn call(mut req: WebContext<'_>) {
41    /// // use reborrow to avoid pass request by value.
42    /// match handler(req.reborrow()) {
43    ///     // still able to access request after handler return.
44    ///     Ok(_) => assert_eq!(req.state(), &()),
45    ///     Err(_) => assert_eq!(req.state(), &())
46    /// }
47    /// # }
48    /// ```
49    #[inline]
50    pub fn reborrow(&mut self) -> WebContext<'_, C, B> {
51        WebContext {
52            req: self.req,
53            body: self.body,
54            ctx: self.ctx,
55        }
56    }
57
58    /// extract typed data from WebContext. type must impl [FromRequest] trait.
59    /// this is a shortcut method that avoiding explicit import of mentioned trait.
60    /// # Examples
61    /// ```rust
62    /// # use xitca_web::{handler::state::StateRef, WebContext};
63    /// async fn extract(ctx: WebContext<'_, usize>) {
64    ///     // extract state from context.
65    ///     let StateRef(state1) = ctx.extract().await.unwrap();
66    ///
67    ///     // equivalent of above method with explicit trait import.
68    ///     use xitca_web::handler::FromRequest;
69    ///     let StateRef(state2) = StateRef::<'_, usize>::from_request(&ctx).await.unwrap();
70    ///
71    ///     // two extractors will return the same type and data state.
72    ///     assert_eq!(state1, state2);
73    /// }
74    /// ```
75    pub async fn extract<'r, T>(&'r self) -> Result<T, T::Error>
76    where
77        T: FromRequest<'r, Self>,
78    {
79        T::from_request(self).await
80    }
81
82    /// Get an immutable reference of App state
83    #[inline]
84    pub fn state(&self) -> &C {
85        self.ctx
86    }
87
88    /// Get an immutable reference of [WebRequest]
89    #[inline]
90    pub fn req(&self) -> &WebRequest<()> {
91        self.req
92    }
93
94    /// Get a mutable reference of [WebRequest]
95    #[inline]
96    pub fn req_mut(&mut self) -> &mut WebRequest<()> {
97        self.req
98    }
99
100    /// Get a immutable reference of [RequestBody]
101    #[inline]
102    pub fn body(&self) -> Ref<'_, B> {
103        self.body.borrow()
104    }
105
106    /// Get a mutable reference of [RequestBody]
107    #[inline]
108    pub fn body_borrow_mut(&self) -> RefMut<'_, B> {
109        self.body.borrow_mut()
110    }
111
112    /// Get a mutable reference of [RequestBody]
113    /// This API takes &mut WebRequest so it bypass runtime borrow checker
114    /// and therefore has zero runtime overhead.
115    #[inline]
116    pub fn body_get_mut(&mut self) -> &mut B {
117        self.body.get_mut()
118    }
119
120    pub fn take_request(&mut self) -> WebRequest<B>
121    where
122        B: Default,
123    {
124        let head = mem::take(self.req_mut());
125        let body = self.take_body_mut();
126        head.map(|ext| ext.map_body(|_| body))
127    }
128
129    /// Transform self to a WebResponse with given body type.
130    ///
131    /// The heap allocation of request would be re-used.
132    #[inline]
133    pub fn into_response<ResB: Into<ResponseBody>>(self, body: ResB) -> WebResponse {
134        self.req.as_response(body.into())
135    }
136
137    /// Transform &mut self to a WebResponse with given body type.
138    ///
139    /// The heap allocation of request would be re-used.
140    #[inline]
141    pub fn as_response<ResB: Into<ResponseBody>>(&mut self, body: ResB) -> WebResponse {
142        self.req.as_response(body.into())
143    }
144
145    pub(crate) fn take_body_ref(&self) -> B
146    where
147        B: Default,
148    {
149        mem::take(&mut *self.body_borrow_mut())
150    }
151
152    pub(crate) fn take_body_mut(&mut self) -> B
153    where
154        B: Default,
155    {
156        mem::take(self.body_get_mut())
157    }
158}
159
160impl<C, B, T> BorrowReq<T> for WebContext<'_, C, B>
161where
162    Request<RequestExt<()>>: BorrowReq<T>,
163{
164    fn borrow(&self) -> &T {
165        self.req().borrow()
166    }
167}
168
169impl<C, B, T> BorrowReqMut<T> for WebContext<'_, C, B>
170where
171    Request<RequestExt<()>>: BorrowReqMut<T>,
172{
173    fn borrow_mut(&mut self) -> &mut T {
174        self.req_mut().borrow_mut()
175    }
176}
177
178#[cfg(test)]
179pub(crate) struct TestWebContext<C> {
180    pub(crate) req: Request<RequestExt<()>>,
181    pub(crate) body: RefCell<RequestBody>,
182    pub(crate) ctx: C,
183}
184
185#[cfg(test)]
186impl<C> TestWebContext<C> {
187    pub(crate) fn as_web_ctx(&mut self) -> WebContext<'_, C> {
188        WebContext {
189            req: &mut self.req,
190            body: &mut self.body,
191            ctx: &self.ctx,
192        }
193    }
194}
195
196#[cfg(test)]
197impl<C> WebContext<'_, C> {
198    pub(crate) fn new_test(ctx: C) -> TestWebContext<C> {
199        TestWebContext {
200            req: Request::new(RequestExt::default()),
201            body: RefCell::new(RequestBody::None),
202            ctx,
203        }
204    }
205}
206
207#[cfg(test)]
208mod test {
209    use xitca_unsafe_collection::futures::NowOrPanic;
210
211    use super::*;
212
213    #[test]
214    fn extract() {
215        use crate::handler::{path::PathRef, state::StateRef};
216
217        let mut ctx = WebContext::new_test(996usize);
218        let mut ctx = ctx.as_web_ctx();
219
220        *ctx.req_mut().uri_mut() = crate::http::Uri::from_static("/foo");
221
222        let StateRef(state) = ctx.extract().now_or_panic().ok().unwrap();
223
224        assert_eq!(*state, 996);
225
226        let PathRef(path) = ctx.extract().now_or_panic().ok().unwrap();
227
228        assert_eq!(path, "/foo");
229    }
230}