ngyn_shared/server/
context.rs

1use http::Request;
2use matchit::Params;
3use serde::{Deserialize, Serialize};
4use std::{any::Any, collections::HashMap, mem::ManuallyDrop, sync::Arc};
5
6use crate::server::{NgynRequest, NgynResponse, Transformer};
7
8/// Represents the value of a context in Ngyn
9#[derive(Serialize, Deserialize)]
10struct NgynContextValue<V> {
11    value: V,
12}
13
14impl<V> NgynContextValue<V> {
15    pub fn create(value: V) -> Self {
16        Self { value }
17    }
18}
19
20/// Represents the state of an application in Ngyn
21
22pub trait AppState: Any + Send + Sync + 'static {
23    fn as_any(&self) -> &dyn Any;
24    fn as_any_mut(&mut self) -> &mut dyn Any;
25}
26
27impl<T: AppState> AppState for Box<T> {
28    fn as_any(&self) -> &dyn Any {
29        self.as_ref()
30    }
31
32    fn as_any_mut(&mut self) -> &mut dyn Any {
33        self.as_mut()
34    }
35}
36
37/// # Panics
38/// Panics if the state has been dropped. This should never happen unless the state is dropped manually.
39impl From<&Arc<Box<dyn AppState>>> for Box<dyn AppState> {
40    fn from(value: &Arc<Box<dyn AppState>>) -> Self {
41        // creating a clone is essential since this ref will be dropped after this function returns
42        let arc_clone = value.clone();
43        let state_ref: &dyn AppState = &**arc_clone;
44
45        let state_ptr: *const dyn AppState = state_ref as *const dyn AppState;
46
47        // SAFETY: state_ptr is never null, it is safe to convert it to a NonNull pointer, this way we can safely convert it back to a Box
48        // If it is ever found as null, this is a bug. It probably means the memory has been poisoned
49        let nn_ptr = std::ptr::NonNull::new(state_ptr as *mut dyn AppState)
50            .expect("State has been dropped, but this should never happen, ensure it is being cloned correctly."); // This should never happen, if it does, it's a bug
51        let raw_ptr = nn_ptr.as_ptr();
52
53        unsafe { Box::from_raw(raw_ptr) }
54    }
55}
56
57/// Represents the context of a request in Ngyn
58pub struct NgynContext<'a> {
59    request: Request<Vec<u8>>,
60    pub(crate) response: NgynResponse,
61    pub(crate) params: Option<Params<'a, 'a>>,
62    store: HashMap<&'a str, String>,
63    pub(crate) state: Option<ManuallyDrop<Box<dyn AppState>>>,
64}
65
66impl<'a> NgynContext<'a> {
67    /// Retrieves the request associated with the context.
68    ///
69    /// ### Returns
70    ///
71    /// A reference to the request associated with the context.
72    ///
73    /// ### Examples
74    ///
75    /// ```rust ignore
76    /// use ngyn_shared::core::context::NgynContext;
77    /// use hyper::Request;
78    ///
79    /// let request = Request::new(Vec::new());
80    /// let context = NgynContext::from_request(request);
81    ///
82    /// let request_ref = context.request();
83    /// ```
84    pub fn request(&self) -> &Request<Vec<u8>> {
85        &self.request
86    }
87
88    #[deprecated(since = "0.5.2", note = "use `response_mut()` instead")]
89    pub fn response(&mut self) -> &mut NgynResponse {
90        &mut self.response
91    }
92
93    /// Retrieves the response associated with the context.
94    ///
95    /// ### Returns
96    ///
97    /// A mutable reference to the response associated with the context.
98    ///
99    /// ### Examples
100    ///
101    /// ```rust ignore
102    /// use ngyn_shared::core::context::NgynContext;
103    /// use http::Request;
104    ///
105    /// let request = Request::new(Vec::new());
106    /// let context = NgynContext::from_request(request);
107    ///
108    /// let response_ref = context.response_mut();
109    /// ```
110    pub fn response_mut(&mut self) -> &mut NgynResponse {
111        &mut self.response
112    }
113
114    /// Retrieves the params associated with the context.
115    ///
116    /// ### Returns
117    ///
118    /// An optional reference to the params associated with the context.
119    ///
120    /// ### Examples
121    ///
122    /// ```rust ignore
123    /// use ngyn_shared::core::context::NgynContext;
124    ///
125    /// let mut context = NgynContext::from_request(request);
126    /// context.set("name", "John".to_string());
127    ///
128    /// let params_ref = context.params();
129    /// ```
130    pub fn params(&self) -> Option<&Params<'a, 'a>> {
131        self.params.as_ref()
132    }
133}
134
135impl NgynContext<'_> {
136    /// Retrieves the state of the context as a reference to the specified type.
137    ///
138    /// # Type Parameters
139    ///
140    /// * `T` - The type of the state to retrieve.
141    ///
142    /// ### Returns
143    ///
144    /// An optional reference to the state of the specified type. Returns `None` if the state is not found or if it cannot be downcasted to the specified type.
145    ///
146    /// ### Examples
147    ///
148    /// ```rust ignore
149    /// use ngyn_shared::core::context::NgynContext;
150    ///
151    /// let mut context = NgynContext::from_request(request);
152    ///
153    /// let state_ref = context.state::<TestAppState>();
154    /// ```
155    pub fn state<T: 'static>(&self) -> Option<&T> {
156        match &self.state {
157            Some(value) => value.as_any().downcast_ref::<T>(),
158            None => None,
159        }
160    }
161
162    /// Retrieves the state of the context as a mutable reference to the specified type.
163    ///
164    /// # Type Parameters
165    ///
166    /// * `T` - The type of the state to retrieve.
167    ///
168    /// ### Returns
169    ///
170    /// An optional reference to the state of the specified type. Returns `None` if the state is not found or if it cannot be downcasted to the specified type.
171    ///
172    /// ### Examples
173    ///
174    /// ```rust ignore
175    /// use ngyn_shared::core::context::NgynContext;
176    ///
177    /// let mut context = NgynContext::from_request(request);
178    ///
179    /// let state_ref = context.state::<TestAppState>();
180    /// ```
181    pub fn state_mut<T: 'static>(&mut self) -> Option<&mut T> {
182        match &mut self.state {
183            Some(value) => value.as_any_mut().downcast_mut::<T>(),
184            None => None,
185        }
186    }
187}
188
189impl<'b> NgynContext<'b> {
190    /// Retrieves the value associated with the given key from the context.
191    ///
192    /// ### Arguments
193    ///
194    /// * `key` - The key (case-insensitive) to retrieve the value for.
195    ///
196    /// ### Returns
197    ///
198    /// A reference to the value associated with the key. If the key is not found, returns a reference to an empty context value.
199    ///
200    /// ### Examples
201    ///
202    /// ```rust ignore
203    /// use ngyn_shared::core::context::NgynContext;
204    ///
205    /// let mut context = NgynContext::from_request(request);
206    /// context.set("name", "John".to_string());
207    ///
208    /// let value: String = context.get("name").unwrap();
209    /// assert_eq!(value, "John".to_string());
210    /// ```
211    pub fn get<V: for<'a> Deserialize<'a>>(&self, key: &str) -> Option<V> {
212        let value = self.store.get(key.to_lowercase().trim());
213        if let Some(value) = value {
214            if let Ok(stored_cx) = serde_json::from_str::<NgynContextValue<V>>(value) {
215                return Some(stored_cx.value);
216            }
217        }
218        None
219    }
220
221    /// Sets the value associated with the given key in the context.
222    ///
223    /// ### Arguments
224    ///
225    /// * `key` - The key (case-insensitive) to set the value for.
226    /// * `value` - The value to associate with the key.
227    ///
228    /// ### Examples
229    ///
230    /// ```rust ignore
231    /// use ngyn_shared::core::context::NgynContext;
232    ///
233    /// let mut context = NgynContext::from_request(request);
234    /// context.set("name", "John".to_string());
235    ///
236    /// let value: String = context.get("name").unwrap();
237    /// assert_eq!(value, "John".to_string());
238    /// ```
239    pub fn set<V: Serialize>(&mut self, key: &'b str, value: V) {
240        if let Ok(value) = serde_json::to_string(&NgynContextValue::create(value)) {
241            self.store.insert(key.trim(), value);
242        }
243    }
244
245    /// Removes the value associated with the given key from the context.
246    ///
247    /// ### Arguments
248    ///
249    /// * `key` - The key (case-insensitive) to remove the value for.
250    ///
251    /// ### Examples
252    ///
253    /// ```rust ignore
254    /// use ngyn_shared::core::context::NgynContext;
255    ///
256    /// let mut context = NgynContext::from_request(request);
257    /// context.set("name", "John".to_string());
258    ///
259    /// context.remove("name");
260    /// let value = context.get::<String>("name");
261    /// assert_eq!(value, None);
262    /// ```
263    pub fn remove(&mut self, key: &str) {
264        self.store.remove(key.to_lowercase().trim());
265    }
266
267    /// Clears all values from the context.
268    ///
269    /// ### Examples
270    ///
271    /// ```rust ignore
272    /// use ngyn_shared::core::context::NgynContext;
273    ///
274    /// let mut context = NgynContext::from_request(request);
275    /// context.set("name", "John".to_string());
276    /// context.set("age", 30.into());
277    ///
278    /// context.clear();
279    /// assert_eq!(context.len(), 0);
280    /// ```
281    pub fn clear(&mut self) {
282        self.store.clear();
283    }
284
285    /// Returns the number of values in the context.
286    ///
287    /// ### Returns
288    ///
289    /// The number of values in the context.
290    ///
291    /// ### Examples
292    ///
293    /// ```rust ignore
294    /// use ngyn_shared::core::context::NgynContext;
295    ///
296    /// let mut context = NgynContext::from_request(request);
297    /// context.set("name", "John".to_string());
298    /// context.set("age", 30.into());
299    ///
300    /// assert_eq!(context.len(), 2);
301    /// ```
302    pub fn len(&self) -> usize {
303        self.store.len()
304    }
305
306    /// Checks if the context is empty.
307    ///
308    /// ### Returns
309    ///
310    /// `true` if the context is empty, `false` otherwise.
311    ///
312    /// ### Examples
313    ///
314    /// ```rust ignore
315    /// use ngyn_shared::core::context::NgynContext;
316    ///
317    /// let mut context = NgynContext::from_request(request);
318    ///
319    /// assert!(context.is_empty());
320    ///
321    /// context.set("name", "John".to_string());
322    /// assert!(!context.is_empty());
323    /// ```
324    pub fn is_empty(&self) -> bool {
325        self.store.is_empty()
326    }
327
328    /// Checks if the context contains a value for the given key.
329    ///
330    /// ### Arguments
331    ///
332    /// * `key` - The key (case-insensitive) to check for.
333    ///
334    /// ### Returns
335    ///
336    /// `true` if the context contains a value for the key, `false` otherwise.
337    ///
338    /// ### Examples
339    ///
340    /// ```rust ignore
341    /// use ngyn_shared::core::context::NgynContext;
342    ///
343    /// let mut context = NgynContext::from_request(request);
344    /// context.set("name", "John".to_string());
345    ///
346    /// assert!(context.has("name"));
347    /// assert!(!context.has("age"));
348    /// ```
349    pub fn has(&self, key: &str) -> bool {
350        self.store.contains_key(key.to_lowercase().trim())
351    }
352}
353
354impl NgynContext<'_> {
355    /// Creates a new `NgynContext` from the given request.
356    ///
357    /// ### Arguments
358    ///
359    /// * `request` - The request to create the context from.
360    ///
361    /// ### Returns
362    ///
363    /// A new `NgynContext` instance.
364    ///
365    /// ### Examples
366    ///
367    /// ```rust ignore
368    /// use ngyn_shared::core::context::NgynContext;
369    /// use hyper::Request;
370    ///
371    /// let request = Request::new(Vec::new());
372    /// let context = NgynContext::from_request(request);
373    /// assert!(context.is_empty());
374    /// ```
375    pub(crate) fn from_request(request: Request<Vec<u8>>) -> Self {
376        NgynContext {
377            request,
378            response: NgynResponse::default(),
379            store: HashMap::new(),
380            params: None,
381            state: None,
382        }
383    }
384}
385
386impl<'a> Transformer<'a> for &'a NgynContext<'a> {
387    fn transform(cx: &'a mut NgynContext) -> Self {
388        cx
389    }
390}
391
392// impl<'a: 'b, 'b> Transformer<'a> for &'a mut NgynContext<'b> {
393//     fn transform(cx: &'a mut NgynContext) -> Self {
394//         cx
395//     }
396// }
397
398impl<'a> Transformer<'a> for &'a NgynRequest {
399    fn transform(cx: &'a mut NgynContext) -> Self {
400        cx.request()
401    }
402}
403
404impl Transformer<'_> for NgynRequest {
405    fn transform(cx: &mut NgynContext) -> Self {
406        cx.request().clone()
407    }
408}
409#[cfg(test)]
410mod tests {
411    use super::*;
412    use http::Method;
413
414    struct TestAppState {
415        value: u128,
416    }
417    impl AppState for TestAppState {
418        fn as_any(&self) -> &dyn Any {
419            self
420        }
421
422        fn as_any_mut(&mut self) -> &mut dyn Any {
423            self
424        }
425    }
426
427    #[test]
428    fn test_request() {
429        let request = Request::new(Vec::new());
430        let context = NgynContext::from_request(request);
431
432        let request_ref = context.request();
433        assert_eq!(request_ref.method(), Method::GET);
434    }
435
436    #[test]
437    fn test_state() {
438        let request = Request::new(Vec::new());
439        let mut context = NgynContext::from_request(request);
440
441        let state_ref = context.state::<TestAppState>();
442        assert!(state_ref.is_none());
443
444        context.state = Some(ManuallyDrop::new(Box::new(TestAppState { value: 1 })));
445
446        let state_ref = context.state::<TestAppState>();
447        assert!(state_ref.is_some());
448    }
449
450    #[test]
451    fn test_state_mut() {
452        let request = Request::new(Vec::new());
453        let mut context = NgynContext::from_request(request);
454        context.state = Some(ManuallyDrop::new(Box::new(TestAppState { value: 1 })));
455
456        let state_ref = context.state_mut::<TestAppState>();
457        assert!(state_ref.is_some());
458
459        state_ref.unwrap().value = 2;
460
461        let state_ref = context.state::<TestAppState>();
462        assert_eq!(state_ref.unwrap().value, 2);
463    }
464
465    #[test]
466    fn test_box_state_impl() {
467        let mut state = Box::new(TestAppState { value: 42 });
468
469        // Test as_any
470        let any_ref = state.as_any();
471        let downcast_result = any_ref.downcast_ref::<TestAppState>();
472        assert!(downcast_result.is_some());
473        let result = downcast_result.unwrap();
474        assert_eq!(result.value, 42);
475
476        // Test as_any_mut
477        let any_mut_ref = state.as_any_mut();
478        let downcast_mut_result = any_mut_ref.downcast_mut::<TestAppState>();
479        assert!(downcast_mut_result.is_some());
480        downcast_mut_result.unwrap().value = 99;
481        assert_eq!(state.value, 99);
482    }
483
484    #[test]
485    fn test_get() {
486        let request = Request::new(Vec::new());
487        let mut context = NgynContext::from_request(request);
488        context.set("name", "John".to_string());
489
490        let value: String = context.get("name").unwrap();
491        assert_eq!(value, "John".to_string());
492    }
493
494    #[test]
495    fn test_set() {
496        let request = Request::new(Vec::new());
497        let mut context = NgynContext::from_request(request);
498        context.set("name", "John".to_string());
499
500        let value: String = context.get("name").unwrap();
501        assert_eq!(value, "John".to_string());
502    }
503
504    #[test]
505    fn test_remove() {
506        let request = Request::new(Vec::new());
507        let mut context = NgynContext::from_request(request);
508        context.set("name", "John".to_string());
509
510        context.remove("name");
511        let value = context.get::<String>("name");
512        assert_eq!(value, None);
513    }
514
515    #[test]
516    fn test_clear() {
517        let request = Request::new(Vec::new());
518        let mut context = NgynContext::from_request(request);
519        context.set("name", "John".to_string());
520        context.set("age", 30);
521
522        context.clear();
523        assert_eq!(context.len(), 0);
524    }
525
526    #[test]
527    fn test_len() {
528        let request = Request::new(Vec::new());
529        let mut context = NgynContext::from_request(request);
530        context.set("name", "John".to_string());
531        context.set("age", 30);
532
533        assert_eq!(context.len(), 2);
534    }
535
536    #[test]
537    fn test_is_empty() {
538        let request = Request::new(Vec::new());
539        let mut context = NgynContext::from_request(request);
540
541        assert!(context.is_empty());
542
543        context.set("name", "John".to_string());
544        assert!(!context.is_empty());
545    }
546
547    #[test]
548    fn test_has() {
549        let request = Request::new(Vec::new());
550        let mut context = NgynContext::from_request(request);
551        context.set("name", "John".to_string());
552
553        assert!(context.has("name"));
554        assert!(!context.has("age"));
555    }
556}