topcoat_core/context/
context_map.rs1use std::any::{Any, type_name};
13
14use crate::context::Cx;
15
16pub fn app_context<T>(cx: &Cx) -> &T
43where
44 T: Any + Send + Sync,
45{
46 match cx.app_context.get::<T>() {
47 Some(value) => value,
48 None => panic!(
49 "attempted to access app context of type `{:?}`, but this type was not registered for this context",
50 type_name::<T>()
51 ),
52 }
53}
54
55pub fn request_context<T>(cx: &Cx) -> &T
80where
81 T: Any + Send + Sync,
82{
83 match cx.request_context.get::<T>() {
84 Some(value) => value,
85 None => panic!(
86 "attempted to access request context of type `{:?}`, but this type was not registered for this context",
87 type_name::<T>()
88 ),
89 }
90}
91
92#[derive(Default, Debug)]
99pub struct ContextMap {
100 entries: anymap3::Map<dyn Any + Send + Sync>,
101}
102
103impl ContextMap {
104 #[must_use]
106 pub fn new() -> Self {
107 Self::default()
108 }
109
110 pub fn insert<T>(&mut self, value: T) -> Option<T>
116 where
117 T: Any + Send + Sync,
118 {
119 self.entries.insert::<T>(value)
120 }
121
122 #[must_use]
124 pub fn contains<T>(&self) -> bool
125 where
126 T: Any + Send + Sync,
127 {
128 self.entries.contains::<T>()
129 }
130
131 #[must_use]
137 pub fn get<T>(&self) -> Option<&T>
138 where
139 T: Any + Send + Sync,
140 {
141 self.entries.get::<T>()
142 }
143
144 #[must_use]
147 pub fn get_mut<T>(&mut self) -> Option<&mut T>
148 where
149 T: Any + Send + Sync,
150 {
151 self.entries.get_mut::<T>()
152 }
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158 use crate::context::CxTestBuilder;
159
160 #[derive(Debug, PartialEq)]
161 struct Database(&'static str);
162
163 #[derive(Debug, PartialEq)]
164 struct Config(u32);
165
166 #[test]
167 fn register_and_get_returns_value() {
168 let mut context = ContextMap::new();
169 context.insert(Database("primary"));
170
171 assert_eq!(context.get::<Database>(), Some(&Database("primary")));
172 }
173
174 #[test]
175 fn get_returns_none_for_unregistered_type() {
176 let context = ContextMap::new();
177 assert_eq!(context.get::<Database>(), None);
178 }
179
180 #[test]
181 fn multiple_types_coexist() {
182 let mut context = ContextMap::new();
183 context.insert(Database("primary"));
184 context.insert(Config(42));
185
186 assert_eq!(context.get::<Database>(), Some(&Database("primary")));
187 assert_eq!(context.get::<Config>(), Some(&Config(42)));
188 }
189
190 #[test]
191 fn insert_replaces_and_returns_the_displaced_value() {
192 let mut context = ContextMap::new();
193 assert_eq!(context.insert(Database("primary")), None);
194 assert_eq!(
195 context.insert(Database("replica")),
196 Some(Database("primary"))
197 );
198 assert_eq!(context.get::<Database>(), Some(&Database("replica")));
199 }
200
201 #[test]
202 fn contains_reports_registered_types() {
203 let mut context = ContextMap::new();
204 assert!(!context.contains::<Database>());
205 context.insert(Database("primary"));
206 assert!(context.contains::<Database>());
207 assert!(!context.contains::<Config>());
208 }
209
210 #[test]
211 fn get_mut_allows_mutation_in_place() {
212 let mut context = ContextMap::new();
213 context.insert(Config(1));
214 context.get_mut::<Config>().unwrap().0 = 42;
215 assert_eq!(context.get::<Config>(), Some(&Config(42)));
216 assert_eq!(context.get_mut::<Database>(), None);
217 }
218
219 #[test]
220 fn app_context_returns_registered_value() {
221 let cx = CxTestBuilder::new()
222 .app_context(Database("primary"))
223 .build();
224
225 let db: &Database = app_context(&cx);
226 assert_eq!(db, &Database("primary"));
227 }
228
229 #[test]
230 #[should_panic(expected = "attempted to access app context")]
231 fn app_context_panics_for_unregistered_type() {
232 let cx = Cx::default();
233 let _: &Database = app_context(&cx);
234 }
235
236 #[test]
237 fn request_context_returns_registered_value() {
238 let cx = CxTestBuilder::new()
239 .request_context(Database("primary"))
240 .build();
241
242 let db: &Database = request_context(&cx);
243 assert_eq!(db, &Database("primary"));
244 }
245
246 #[test]
247 #[should_panic(expected = "attempted to access request context")]
248 fn request_context_panics_for_unregistered_type() {
249 let cx = Cx::default();
250 let _: &Database = request_context(&cx);
251 }
252}