1use std::any::{Any, TypeId, type_name};
18use std::collections::HashMap;
19use std::fmt::Debug;
20
21pub trait Context: Debug {}
25
26impl<'c, C> Context for &'c C where C: Context {}
27
28pub trait ContextProvides<V> {
36    fn provide(&self) -> &V;
37}
38
39impl<'c, C, V> ContextProvides<V> for &'c C
40where
41    C: ContextProvides<V>,
42{
43    fn provide(&self) -> &V {
44        (**self).provide()
45    }
46}
47
48#[derive(Debug, Default)]
52pub struct DynContext {
53    backing: HashMap<TypeId, Box<dyn Any>>,
54}
55
56impl DynContext {
57    pub fn provide_if_set<T: 'static>(&self) -> Option<&T> {
59        let found = self.backing.get(&TypeId::of::<T>());
60        found.map(|any| any.downcast_ref().unwrap())
61    }
62}
63
64impl Context for DynContext {}
65
66impl<V> ContextProvides<V> for DynContext
67where
68    V: 'static,
69{
70    fn provide(&self) -> &V {
71        self.provide_if_set()
72            .unwrap_or_else(|| panic!("Context does not provide {:?}", type_name::<V>()))
73    }
74}
75
76#[derive(Debug, Default)]
78pub struct DynContextBuilder(HashMap<TypeId, Box<dyn Any>>);
79
80impl DynContextBuilder {
81    pub fn add_key<T: 'static>(&mut self, value: T) -> &mut Self {
83        self.0.insert(TypeId::of::<T>(), Box::new(value));
84        self
85    }
86
87    pub fn build(self) -> DynContext {
89        DynContext { backing: self.0 }
90    }
91}
92
93#[derive(Copy, Clone, Debug)]
95pub struct EmptyContext;
96
97impl Context for EmptyContext {}
98
99#[derive(Clone, Debug)]
101pub struct SingularContext<V>(pub V);
102
103impl<V: Debug> Context for SingularContext<V> {}
104
105impl<V> ContextProvides<V> for SingularContext<V> {
106    fn provide(&self) -> &V {
107        &self.0
108    }
109}
110
111#[macro_export]
130macro_rules! static_context_provides {
131    ($struct_name:ty, $field_type:ident, $field_name:ident) => {
132        impl ContextProvides<$field_type> for $struct_name {
133            fn provide(&self) -> &$field_type {
134                &self.$field_name
135            }
136        }
137    };
138    ($struct_name:ty, $field_type:ident, $field_name:ident, $($further:ident),+) => {
139        impl ContextProvides<$field_type> for $struct_name {
140            fn provide(&self) -> &$field_type {
141                &self.$field_name
142            }
143        }
144        static_context_provides!($struct_name, $($further),+);
145    }
146}
147
148#[cfg(test)]
149mod tests {
150    use super::ContextProvides;
151    use static_assertions::assert_impl_all;
152
153    pub struct StaticContextExample {
154        pub edition: u16,
155        pub name: String,
156    }
157    static_context_provides!(StaticContextExample, u16, edition, String, name);
158    assert_impl_all!(StaticContextExample: ContextProvides<u16>, ContextProvides<String>);
159
160    #[test]
161    fn provide_same_values() {
162        let example = StaticContextExample {
163            edition: 32,
164            name: "test".to_string(),
165        };
166        let edition: &u16 = example.provide();
167        let name: &String = example.provide();
168        assert_eq!(*edition, 32);
169        assert_eq!(name, "test");
170    }
171}