ntex_service/
cfg.rs

1//! Shared configuration for services
2#![allow(clippy::should_implement_trait, clippy::new_ret_no_self)]
3use std::sync::atomic::{AtomicUsize, Ordering};
4use std::{any::Any, any::TypeId, cell::RefCell, fmt, ops, ptr};
5
6type Key = (usize, TypeId);
7type HashMap<K, V> = std::collections::HashMap<K, V, foldhash::fast::RandomState>;
8
9thread_local! {
10    static DEFAULT_CFG: &'static Storage = Box::leak(Box::new(
11        Storage::new("--".to_string(), false)));
12    static MAPPING: RefCell<HashMap<Key, &'static dyn Any>> = {
13        RefCell::new(HashMap::default())
14    };
15}
16static IDX: AtomicUsize = AtomicUsize::new(0);
17
18#[derive(Debug)]
19struct Storage {
20    id: usize,
21    tag: String,
22    building: bool,
23    data: HashMap<TypeId, Box<dyn Any + Send + Sync>>,
24}
25
26impl Storage {
27    fn new(tag: String, building: bool) -> Self {
28        let id = IDX.fetch_add(1, Ordering::SeqCst);
29        Storage {
30            id,
31            tag,
32            building,
33            data: HashMap::default(),
34        }
35    }
36}
37
38pub trait Configuration: Default + Send + Sync + fmt::Debug + 'static {
39    const NAME: &'static str;
40
41    fn ctx(&self) -> &CfgContext;
42
43    fn set_ctx(&mut self, ctx: CfgContext);
44}
45
46#[derive(Copy, Clone, Debug)]
47pub struct CfgContext(&'static Storage);
48
49impl CfgContext {
50    #[inline]
51    pub fn id(&self) -> usize {
52        self.0.id
53    }
54
55    #[inline]
56    pub fn tag(&self) -> &'static str {
57        self.0.tag.as_ref()
58    }
59
60    #[inline]
61    pub fn shared(&self) -> SharedCfg {
62        SharedCfg(self.0)
63    }
64}
65
66impl Default for CfgContext {
67    #[inline]
68    fn default() -> Self {
69        CfgContext(DEFAULT_CFG.with(|cfg| *cfg))
70    }
71}
72
73#[derive(Debug)]
74pub struct Cfg<T: Configuration>(&'static T);
75
76impl<T: Configuration> Cfg<T> {
77    #[inline]
78    pub fn id(&self) -> usize {
79        self.0.ctx().0.id
80    }
81
82    #[inline]
83    pub fn tag(&self) -> &'static str {
84        self.0.ctx().tag()
85    }
86
87    #[inline]
88    pub fn shared(&self) -> SharedCfg {
89        self.0.ctx().shared()
90    }
91
92    #[inline]
93    pub fn into_static(&self) -> &'static T {
94        self.0
95    }
96}
97
98impl<T: Configuration> Copy for Cfg<T> {}
99
100impl<T: Configuration> Clone for Cfg<T> {
101    #[inline]
102    fn clone(&self) -> Self {
103        *self
104    }
105}
106
107impl<T: Configuration> ops::Deref for Cfg<T> {
108    type Target = T;
109
110    #[inline]
111    fn deref(&self) -> &'static T {
112        self.0
113    }
114}
115
116impl<T: Configuration> Default for Cfg<T> {
117    #[inline]
118    fn default() -> Self {
119        CfgContext::default().shared().get()
120    }
121}
122
123#[derive(Copy, Clone, Debug)]
124/// Shared configuration
125pub struct SharedCfg(&'static Storage);
126
127#[derive(Debug)]
128pub struct SharedCfgBuilder {
129    ctx: CfgContext,
130    storage: Option<Box<Storage>>,
131}
132
133impl Eq for SharedCfg {}
134
135impl PartialEq for SharedCfg {
136    fn eq(&self, other: &Self) -> bool {
137        ptr::from_ref(self.0) == ptr::from_ref(other.0)
138    }
139}
140
141impl SharedCfg {
142    /// Construct new configuration
143    pub fn new<T: AsRef<str>>(tag: T) -> SharedCfgBuilder {
144        SharedCfgBuilder::new(tag.as_ref().to_string())
145    }
146
147    #[inline]
148    /// Get unique shared cfg id
149    pub fn id(&self) -> usize {
150        self.0.id
151    }
152
153    #[inline]
154    /// Get tag
155    pub fn tag(&self) -> &'static str {
156        self.0.tag.as_ref()
157    }
158
159    /// Get a reference to a previously inserted on configuration.
160    ///
161    /// Panics if shared config is building
162    pub fn get<T>(&self) -> Cfg<T>
163    where
164        T: Configuration,
165    {
166        if self.0.building {
167            panic!("{}: Cannot access shared config while building", self.tag());
168        }
169        let tp = TypeId::of::<T>();
170        self.0
171            .data
172            .get(&tp)
173            .and_then(|boxed| boxed.downcast_ref())
174            .map(Cfg)
175            .unwrap_or_else(|| {
176                MAPPING.with(|store| {
177                    let key = (self.0.id, tp);
178                    if let Some(boxed) = store.borrow().get(&key) {
179                        Cfg(boxed.downcast_ref().unwrap())
180                    } else {
181                        log::info!(
182                            "{}: Configuration {:?} does not exist, using default",
183                            self.tag(),
184                            T::NAME
185                        );
186                        let mut val = T::default();
187                        val.set_ctx(CfgContext(self.0));
188                        store.borrow_mut().insert(key, Box::leak(Box::new(val)));
189                        Cfg(store.borrow().get(&key).unwrap().downcast_ref().unwrap())
190                    }
191                })
192            })
193    }
194}
195
196impl Default for SharedCfg {
197    #[inline]
198    fn default() -> Self {
199        Self(DEFAULT_CFG.with(|cfg| *cfg))
200    }
201}
202
203impl SharedCfgBuilder {
204    fn new(tag: String) -> SharedCfgBuilder {
205        let storage = Box::into_raw(Box::new(Storage::new(tag, true)));
206        unsafe {
207            SharedCfgBuilder {
208                ctx: CfgContext(storage.as_ref().unwrap()),
209                storage: Some(Box::from_raw(storage)),
210            }
211        }
212    }
213
214    /// Insert a type into this configuration.
215    ///
216    /// If a config of this type already existed, it will
217    /// be replaced.
218    pub fn add<T: Configuration>(mut self, mut val: T) -> Self {
219        val.set_ctx(self.ctx);
220        self.storage
221            .as_mut()
222            .unwrap()
223            .data
224            .insert(TypeId::of::<T>(), Box::new(val));
225        self
226    }
227}
228
229impl Drop for SharedCfgBuilder {
230    fn drop(&mut self) {
231        if let Some(mut st) = self.storage.take() {
232            st.building = false;
233            let _ = Box::leak(st);
234        }
235    }
236}
237
238impl From<SharedCfgBuilder> for SharedCfg {
239    fn from(mut cfg: SharedCfgBuilder) -> SharedCfg {
240        let mut st = cfg.storage.take().unwrap();
241        st.building = false;
242        SharedCfg(Box::leak(st))
243    }
244}
245
246#[cfg(test)]
247mod tests {
248    use super::*;
249
250    #[test]
251    #[should_panic]
252    fn access_cfg_in_building_state() {
253        #[derive(Debug)]
254        struct TestCfg {
255            config: CfgContext,
256        }
257        impl TestCfg {
258            fn new() -> Self {
259                Self {
260                    config: CfgContext::default(),
261                }
262            }
263        }
264        impl Default for TestCfg {
265            fn default() -> Self {
266                panic!()
267            }
268        }
269        impl Configuration for TestCfg {
270            const NAME: &str = "TEST";
271            fn ctx(&self) -> &CfgContext {
272                &self.config
273            }
274            fn set_ctx(&mut self, ctx: CfgContext) {
275                self.config = ctx;
276                let _ = ctx.shared().get::<TestCfg>();
277            }
278        }
279        SharedCfg::new("TEST").add(TestCfg::new());
280    }
281
282    #[test]
283    fn shared_cfg() {
284        #[derive(Default, Debug)]
285        struct TestCfg {
286            config: CfgContext,
287        }
288        impl Configuration for TestCfg {
289            const NAME: &str = "TEST";
290            fn ctx(&self) -> &CfgContext {
291                &self.config
292            }
293            fn set_ctx(&mut self, ctx: CfgContext) {
294                self.config = ctx;
295            }
296        }
297
298        let cfg: SharedCfg = SharedCfg::new("TEST").add(TestCfg::default()).into();
299
300        assert_eq!(cfg.tag(), "TEST");
301        let t = cfg.get::<TestCfg>();
302        assert_eq!(t.tag(), "TEST");
303        assert_eq!(t.shared(), cfg);
304        let t: Cfg<TestCfg> = Default::default();
305        assert_eq!(t.tag(), "--");
306        assert_eq!(t.ctx().id(), t.id());
307        assert_eq!(t.ctx().id(), t.ctx().clone().id());
308
309        let cfg: SharedCfg = SharedCfg::new("TEST2").into();
310        let t = cfg.get::<TestCfg>();
311        assert_eq!(t.tag(), "TEST2");
312        assert_eq!(t.id(), cfg.id());
313    }
314}