rustlavel_core/
context.rs1use crate::config::Config;
9use std::any::{Any, TypeId};
10use std::collections::HashMap;
11use std::sync::Arc;
12
13#[derive(Clone)]
15pub struct Context {
16 inner: Arc<Inner>,
17}
18
19struct Inner {
20 config: Config,
21 state: HashMap<TypeId, Box<dyn Any + Send + Sync>>,
22}
23
24impl Context {
25 pub fn builder() -> ContextBuilder {
26 ContextBuilder { config: Config::with_defaults(), state: HashMap::new() }
27 }
28
29 pub fn config(&self) -> &Config {
30 &self.inner.config
31 }
32
33 pub fn state<T: Send + Sync + 'static>(&self) -> Option<&T> {
35 self.inner.state.get(&TypeId::of::<T>()).and_then(|value| value.downcast_ref::<T>())
36 }
37
38 pub fn expect_state<T: Send + Sync + 'static>(&self) -> &T {
42 self.state::<T>().unwrap_or_else(|| {
43 panic!(
44 "`{}` was never registered on the application. \
45 Add `.state(...)` for it in main.rs, or enable the package that provides it.",
46 std::any::type_name::<T>()
47 )
48 })
49 }
50}
51
52impl Default for Context {
53 fn default() -> Self {
54 Context::builder().build()
55 }
56}
57
58pub struct ContextBuilder {
59 config: Config,
60 state: HashMap<TypeId, Box<dyn Any + Send + Sync>>,
61}
62
63impl ContextBuilder {
64 pub fn config(mut self, config: Config) -> Self {
65 self.config = config;
66 self
67 }
68
69 pub fn state<T: Send + Sync + 'static>(mut self, value: T) -> Self {
71 self.state.insert(TypeId::of::<T>(), Box::new(value));
72 self
73 }
74
75 pub fn build(self) -> Context {
76 Context { inner: Arc::new(Inner { config: self.config, state: self.state }) }
77 }
78}
79
80#[cfg(test)]
81mod tests {
82 use super::*;
83
84 struct Database(&'static str);
85 struct Cache;
86
87 #[test]
88 fn resolves_registered_state_by_type() {
89 let context = Context::builder().state(Database("postgres")).build();
90
91 assert_eq!(context.state::<Database>().unwrap().0, "postgres");
92 assert!(context.state::<Cache>().is_none());
93 }
94
95 #[test]
96 fn carries_configuration() {
97 let config = Config::new();
98 config.set("app.name", "Rustlavel");
99 let context = Context::builder().config(config).build();
100
101 assert_eq!(context.config().string("app.name", ""), "Rustlavel");
102 }
103}