1use std::{
2 any::{Any, TypeId},
3 collections::HashMap,
4 sync::{Arc, RwLock},
5};
6
7pub struct RequestContext {
8 method: String,
9 path: String,
10 headers: HashMap<String, String>,
11 extensions: RwLock<HashMap<TypeId, Arc<dyn Any + Send + Sync>>>,
12}
13
14impl RequestContext {
15 pub fn new(
16 method: impl Into<String>,
17 path: impl Into<String>,
18 headers: HashMap<String, String>,
19 ) -> Self {
20 Self {
21 method: method.into(),
22 path: path.into(),
23 headers: headers
24 .into_iter()
25 .map(|(name, value)| (name.to_ascii_lowercase(), value))
26 .collect(),
27 extensions: RwLock::new(HashMap::new()),
28 }
29 }
30
31 pub fn method(&self) -> &str {
32 &self.method
33 }
34
35 pub fn path(&self) -> &str {
36 &self.path
37 }
38
39 pub fn header(&self, name: &str) -> Option<&str> {
40 self.headers
41 .get(&name.to_ascii_lowercase())
42 .map(String::as_str)
43 }
44
45 pub fn bearer_token(&self) -> Option<&str> {
46 self.header("authorization")?.strip_prefix("Bearer ")
47 }
48
49 pub fn set<T: Send + Sync + 'static>(&self, value: T) -> crate::Result<()> {
50 self.extensions
51 .write()
52 .map_err(|_| {
53 crate::exception::startup_error("request context extensions lock poisoned")
54 })?
55 .insert(TypeId::of::<T>(), Arc::new(value));
56 Ok(())
57 }
58
59 pub fn get<T: Send + Sync + 'static>(&self) -> crate::Result<Option<Arc<T>>> {
60 let value = self
61 .extensions
62 .read()
63 .map_err(|_| {
64 crate::exception::startup_error("request context extensions lock poisoned")
65 })?
66 .get(&TypeId::of::<T>())
67 .cloned();
68
69 let Some(value) = value else {
70 return Ok(None);
71 };
72
73 value
74 .clone()
75 .downcast::<T>()
76 .map(Some)
77 .map_err(|_| crate::exception::startup_error("request context extension type mismatch"))
78 }
79}