roa_core/context/
storage.rs1use std::any::{Any, TypeId};
2use std::borrow::Cow;
3use std::collections::HashMap;
4use std::fmt::Display;
5use std::ops::Deref;
6use std::str::FromStr;
7use std::sync::Arc;
8
9use http::StatusCode;
10
11use crate::Status;
12
13pub trait Value: Any + Send + Sync {}
14
15impl<V> Value for V where V: Any + Send + Sync {}
16
17#[derive(Clone)]
19pub struct Storage(HashMap<TypeId, HashMap<Cow<'static, str>, Arc<dyn Any + Send + Sync>>>);
20
21#[derive(Debug, Clone)]
44pub struct Variable<'a, V> {
45 key: &'a str,
46 value: Arc<V>,
47}
48
49impl<V> Deref for Variable<'_, V> {
50 type Target = V;
51 #[inline]
52 fn deref(&self) -> &Self::Target {
53 &self.value
54 }
55}
56
57impl<'a, V> Variable<'a, V> {
58 #[inline]
60 fn new(key: &'a str, value: Arc<V>) -> Self {
61 Self { key, value }
62 }
63
64 #[inline]
66 pub fn value(self) -> Arc<V> {
67 self.value
68 }
69}
70
71impl<V> Variable<'_, V>
72where
73 V: AsRef<str>,
74{
75 #[inline]
77 pub fn parse<T>(&self) -> Result<T, Status>
78 where
79 T: FromStr,
80 T::Err: Display,
81 {
82 self.as_ref().parse().map_err(|err| {
83 Status::new(
84 StatusCode::BAD_REQUEST,
85 format!(
86 "{}\ntype of variable `{}` should be {}",
87 err,
88 self.key,
89 std::any::type_name::<T>()
90 ),
91 true,
92 )
93 })
94 }
95}
96
97impl Storage {
98 #[inline]
100 pub fn new() -> Self {
101 Self(HashMap::new())
102 }
103
104 pub fn insert<S, K, V>(&mut self, scope: S, key: K, value: V) -> Option<Arc<V>>
111 where
112 S: Any,
113 K: Into<Cow<'static, str>>,
114 V: Value,
115 {
116 let id = TypeId::of::<S>();
117 match self.0.get_mut(&id) {
118 Some(bucket) => bucket
119 .insert(key.into(), Arc::new(value))
120 .and_then(|value| value.downcast().ok()),
121 None => {
122 self.0.insert(id, HashMap::new());
123 self.insert(scope, key, value)
124 }
125 }
126 }
127
128 #[inline]
132 pub fn get<'a, S, V>(&self, key: &'a str) -> Option<Variable<'a, V>>
133 where
134 S: Any,
135 V: Value,
136 {
137 let value = self.0.get(&TypeId::of::<S>())?.get(key)?.clone();
138 Some(Variable::new(key, value.clone().downcast().ok()?))
139 }
140}
141
142impl Default for Storage {
143 #[inline]
144 fn default() -> Self {
145 Self::new()
146 }
147}
148
149#[cfg(test)]
150mod tests {
151 use std::sync::Arc;
152
153 use http::StatusCode;
154
155 use super::{Storage, Variable};
156
157 #[test]
158 fn storage() {
159 struct Scope;
160
161 let mut storage = Storage::default();
162 assert!(storage.get::<Scope, &'static str>("id").is_none());
163 assert!(storage.insert(Scope, "id", "1").is_none());
164 let id: i32 = storage
165 .get::<Scope, &'static str>("id")
166 .unwrap()
167 .parse()
168 .unwrap();
169 assert_eq!(1, id);
170 assert_eq!(
171 1,
172 storage
173 .insert(Scope, "id", "2")
174 .unwrap()
175 .parse::<i32>()
176 .unwrap()
177 );
178 }
179
180 #[test]
181 fn variable() {
182 assert_eq!(
183 1,
184 Variable::new("id", Arc::new("1")).parse::<i32>().unwrap()
185 );
186 let result = Variable::new("id", Arc::new("x")).parse::<usize>();
187 assert!(result.is_err());
188 let status = result.unwrap_err();
189 assert_eq!(StatusCode::BAD_REQUEST, status.status_code);
190 assert!(status
191 .message
192 .ends_with("type of variable `id` should be usize"));
193 }
194}