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}