Skip to main content

molframe_core/structure/
extensions.rs

1//! Type-safe, structure-local domain extensions.
2
3use std::any::Any;
4use std::collections::BTreeMap;
5use std::fmt;
6use std::panic::RefUnwindSafe;
7use std::sync::Arc;
8
9trait ExtensionValue: Send + Sync + RefUnwindSafe {
10    fn as_any(&self) -> &dyn Any;
11    fn into_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync>;
12}
13
14impl<T> ExtensionValue for T
15where
16    T: Any + Send + Sync + RefUnwindSafe,
17{
18    fn as_any(&self) -> &dyn Any {
19        self
20    }
21
22    fn into_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync> {
23        self
24    }
25}
26
27type SharedExtension = Arc<dyn ExtensionValue>;
28
29/// Cold, typed metadata owned by a structure snapshot.
30///
31/// Keys are stable schema identifiers. Values are immutable and reference
32/// counted, so cloning a structure does not copy domain metadata.
33#[derive(Clone, Default)]
34pub struct ExtensionStore {
35    values: BTreeMap<&'static str, SharedExtension>,
36}
37
38impl ExtensionStore {
39    /// Creates an empty store.
40    #[must_use]
41    pub const fn new() -> Self {
42        Self {
43            values: BTreeMap::new(),
44        }
45    }
46
47    /// Inserts or replaces a typed extension.
48    pub fn insert<T>(&mut self, key: &'static str, value: T)
49    where
50        T: Any + Send + Sync + RefUnwindSafe,
51    {
52        let _ = self.values.insert(key, Arc::new(value));
53    }
54
55    /// Borrows an extension when its key and concrete type both match.
56    #[must_use]
57    pub fn get<T>(&self, key: &str) -> Option<&T>
58    where
59        T: Any + Send + Sync + RefUnwindSafe,
60    {
61        self.values.get(key)?.as_ref().as_any().downcast_ref()
62    }
63
64    /// Clones the shared handle to an extension when its type matches.
65    #[must_use]
66    pub fn get_shared<T>(&self, key: &str) -> Option<Arc<T>>
67    where
68        T: Any + Send + Sync + RefUnwindSafe,
69    {
70        Arc::clone(self.values.get(key)?).into_any().downcast().ok()
71    }
72
73    /// Removes every extension.
74    pub fn clear(&mut self) {
75        self.values.clear();
76    }
77
78    /// Returns whether no extensions are attached.
79    #[must_use]
80    pub fn is_empty(&self) -> bool {
81        self.values.is_empty()
82    }
83
84    /// Returns the number of attached extensions.
85    #[must_use]
86    pub fn len(&self) -> usize {
87        self.values.len()
88    }
89
90    /// Iterates stable schema keys in lexical order.
91    pub fn keys(&self) -> impl Iterator<Item = &'static str> + '_ {
92        self.values.keys().copied()
93    }
94}
95
96impl fmt::Debug for ExtensionStore {
97    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
98        formatter
99            .debug_struct("ExtensionStore")
100            .field("keys", &self.values.keys().collect::<Vec<_>>())
101            .finish()
102    }
103}