starweaver_context/
dependency.rs1use std::{
4 any::{Any, TypeId},
5 collections::{BTreeMap, BTreeSet},
6 sync::Arc,
7};
8
9#[derive(Clone, Default)]
11pub struct DependencyStore {
12 values: BTreeMap<String, Arc<dyn Any + Send + Sync>>,
13 type_keys: BTreeMap<TypeId, String>,
14}
15
16impl DependencyStore {
17 #[must_use]
19 pub fn new() -> Self {
20 Self::default()
21 }
22
23 pub fn insert<T>(&mut self, value: T)
25 where
26 T: Send + Sync + 'static,
27 {
28 self.insert_named(std::any::type_name::<T>(), value);
29 }
30
31 pub fn insert_arc<T>(&mut self, value: Arc<T>)
33 where
34 T: Send + Sync + 'static,
35 {
36 self.insert_named_arc(std::any::type_name::<T>(), value);
37 }
38
39 pub fn insert_named<T>(&mut self, name: impl Into<String>, value: T)
44 where
45 T: Send + Sync + 'static,
46 {
47 self.insert_named_arc(name, Arc::new(value));
48 }
49
50 pub fn insert_named_arc<T>(&mut self, name: impl Into<String>, value: Arc<T>)
55 where
56 T: Send + Sync + 'static,
57 {
58 let name = name.into();
59 if let Some(previous) = self.values.get(&name) {
60 let previous_type = previous.as_ref().type_id();
61 if self.type_keys.get(&previous_type) == Some(&name) {
62 self.type_keys.remove(&previous_type);
63 if let Some(fallback_name) = self
64 .values
65 .iter()
66 .filter(|(candidate_name, candidate)| {
67 *candidate_name != &name && candidate.as_ref().type_id() == previous_type
68 })
69 .map(|(candidate_name, _)| candidate_name.clone())
70 .next_back()
71 {
72 self.type_keys.insert(previous_type, fallback_name);
73 }
74 }
75 }
76 self.type_keys.insert(TypeId::of::<T>(), name.clone());
77 self.values.insert(name, value);
78 }
79
80 #[must_use]
82 pub fn get<T>(&self) -> Option<Arc<T>>
83 where
84 T: Send + Sync + 'static,
85 {
86 self.type_keys
87 .get(&TypeId::of::<T>())
88 .and_then(|name| self.get_named(name))
89 }
90
91 #[must_use]
93 pub fn get_named<T>(&self, name: &str) -> Option<Arc<T>>
94 where
95 T: Send + Sync + 'static,
96 {
97 self.values
98 .get(name)
99 .cloned()
100 .and_then(|value| value.downcast::<T>().ok())
101 }
102
103 #[must_use]
110 pub fn subset(&self, names: &BTreeSet<String>) -> Self {
111 let values: BTreeMap<_, _> = self
112 .values
113 .iter()
114 .filter(|(name, _)| names.contains(*name))
115 .map(|(name, value)| (name.clone(), Arc::clone(value)))
116 .collect();
117 let mut type_keys = BTreeMap::new();
118 for (name, value) in &values {
119 type_keys.insert(value.as_ref().type_id(), name.clone());
120 }
121 for (type_id, name) in &self.type_keys {
122 if values.contains_key(name) {
123 type_keys.insert(*type_id, name.clone());
124 }
125 }
126 Self { values, type_keys }
127 }
128
129 pub fn extend(&mut self, other: Self) {
131 for (name, value) in other.values {
132 if let Some(previous) = self.values.insert(name.clone(), value.clone()) {
133 let previous_type = previous.as_ref().type_id();
134 if self.type_keys.get(&previous_type) == Some(&name) {
135 self.type_keys.remove(&previous_type);
136 }
137 }
138 self.type_keys.insert(value.as_ref().type_id(), name);
139 }
140 for (type_id, name) in other.type_keys {
141 if self.values.contains_key(&name) {
142 self.type_keys.insert(type_id, name);
143 }
144 }
145 }
146
147 #[must_use]
149 pub fn keys(&self) -> Vec<String> {
150 self.values.keys().cloned().collect()
151 }
152
153 #[must_use]
155 pub fn is_empty(&self) -> bool {
156 self.values.is_empty()
157 }
158}
159
160impl std::fmt::Debug for DependencyStore {
161 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
162 formatter
163 .debug_struct("DependencyStore")
164 .field("keys", &self.keys())
165 .finish()
166 }
167}
168
169#[cfg(test)]
170mod tests {
171 use std::collections::BTreeSet;
172
173 use super::DependencyStore;
174
175 #[derive(Debug, Eq, PartialEq)]
176 struct First(u32);
177
178 #[derive(Debug, Eq, PartialEq)]
179 struct Second(u32);
180
181 #[test]
182 fn subset_preserves_selected_typed_and_named_lookups() {
183 let mut store = DependencyStore::new();
184 store.insert(First(1));
185 store.insert_named("second", Second(2));
186 let names = BTreeSet::from([
187 std::any::type_name::<First>().to_string(),
188 "second".to_string(),
189 ]);
190
191 let subset = store.subset(&names);
192
193 assert_eq!(subset.get::<First>().as_deref(), Some(&First(1)));
194 assert_eq!(subset.get::<Second>().as_deref(), Some(&Second(2)));
195 assert_eq!(
196 subset.get_named::<Second>("second").as_deref(),
197 Some(&Second(2))
198 );
199 }
200
201 #[test]
202 fn subset_removes_omitted_type_keys_and_values() {
203 let mut store = DependencyStore::new();
204 store.insert(First(1));
205 store.insert(Second(2));
206 let names = BTreeSet::from([std::any::type_name::<First>().to_string()]);
207
208 let subset = store.subset(&names);
209
210 assert!(subset.get::<First>().is_some());
211 assert!(subset.get::<Second>().is_none());
212 assert!(
213 subset
214 .get_named::<Second>(std::any::type_name::<Second>())
215 .is_none()
216 );
217 }
218
219 #[test]
220 fn replacing_a_name_with_another_type_removes_the_stale_typed_lookup() {
221 let mut store = DependencyStore::new();
222 store.insert_named("shared", First(1));
223 store.insert_named("shared", Second(2));
224
225 assert!(store.get::<First>().is_none());
226 assert_eq!(store.get::<Second>().as_deref(), Some(&Second(2)));
227 assert_eq!(
228 store.get_named::<Second>("shared").as_deref(),
229 Some(&Second(2))
230 );
231 }
232
233 #[test]
234 fn replacing_the_preferred_alias_reindexes_an_existing_alias() {
235 let mut store = DependencyStore::new();
236 store.insert_named("fallback", First(1));
237 store.insert_named("preferred", First(2));
238 store.insert_named("preferred", Second(3));
239
240 assert_eq!(store.get::<First>().as_deref(), Some(&First(1)));
241 assert_eq!(store.get::<Second>().as_deref(), Some(&Second(3)));
242 }
243
244 #[test]
245 fn subset_reindexes_a_selected_non_preferred_alias_for_typed_lookup() {
246 let mut store = DependencyStore::new();
247 store.insert_named("first-alias", First(1));
248 store.insert_named("preferred-alias", First(2));
249 assert_eq!(store.get::<First>().as_deref(), Some(&First(2)));
250
251 let subset = store.subset(&BTreeSet::from(["first-alias".to_string()]));
252
253 assert_eq!(subset.get::<First>().as_deref(), Some(&First(1)));
254 assert_eq!(
255 subset.get_named::<First>("first-alias").as_deref(),
256 Some(&First(1))
257 );
258 }
259}