1use std::any::{Any, TypeId, type_name};
38use std::collections::HashMap;
39use std::fmt;
40
41use crate::task::RegisterableTask;
42
43#[derive(Default)]
48pub struct Deps {
49 map: HashMap<TypeId, Box<dyn Any + Send + Sync>>,
50}
51
52impl Deps {
53 #[must_use]
55 pub fn new() -> Self {
56 Self {
57 map: HashMap::new(),
58 }
59 }
60
61 #[must_use]
63 pub fn builder() -> DepsBuilder {
64 DepsBuilder { inner: Deps::new() }
65 }
66
67 #[must_use]
69 pub fn get<T>(&self) -> Option<T>
70 where
71 T: Clone + Send + Sync + 'static,
72 {
73 self.map
74 .get(&TypeId::of::<T>())
75 .and_then(|v| v.downcast_ref::<T>())
76 .cloned()
77 }
78
79 pub fn try_get<T>(&self) -> Result<T, MissingDep>
85 where
86 T: Clone + Send + Sync + 'static,
87 {
88 self.get::<T>().ok_or_else(MissingDep::of::<T>)
89 }
90
91 #[must_use]
101 pub fn expect<T>(&self) -> T
102 where
103 T: Clone + Send + Sync + 'static,
104 {
105 match self.get::<T>() {
106 Some(v) => v,
107 None => missing_panic(type_name::<T>()),
108 }
109 }
110
111 #[must_use]
113 pub fn contains<T>(&self) -> bool
114 where
115 T: 'static,
116 {
117 self.map.contains_key(&TypeId::of::<T>())
118 }
119
120 #[must_use]
122 pub fn len(&self) -> usize {
123 self.map.len()
124 }
125
126 #[must_use]
128 pub fn is_empty(&self) -> bool {
129 self.map.is_empty()
130 }
131
132 pub fn merge(&mut self, other: Self) {
142 self.map.extend(other.map);
143 }
144}
145
146impl fmt::Debug for Deps {
147 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
148 f.debug_struct("Deps")
149 .field("registered_types", &self.map.len())
150 .finish()
151 }
152}
153
154pub struct DepsBuilder {
156 inner: Deps,
157}
158
159impl DepsBuilder {
160 #[must_use]
165 pub fn insert<T>(mut self, dep: T) -> Self
166 where
167 T: Clone + Send + Sync + 'static,
168 {
169 self.inner.map.insert(TypeId::of::<T>(), Box::new(dep));
170 self
171 }
172
173 #[must_use]
179 pub fn merge(mut self, other: Deps) -> Self {
180 self.inner.merge(other);
181 self
182 }
183
184 #[must_use]
186 pub fn build(self) -> Deps {
187 self.inner
188 }
189}
190
191impl Default for DepsBuilder {
192 fn default() -> Self {
193 Deps::builder()
194 }
195}
196
197#[derive(Debug, Clone, PartialEq, Eq)]
202pub struct MissingDep {
203 pub type_name: &'static str,
205}
206
207impl MissingDep {
208 #[must_use]
210 pub fn of<T: ?Sized + 'static>() -> Self {
211 Self {
212 type_name: type_name::<T>(),
213 }
214 }
215}
216
217impl fmt::Display for MissingDep {
218 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
219 write!(
220 f,
221 "missing dependency `{}` in Deps container",
222 self.type_name
223 )
224 }
225}
226
227impl std::error::Error for MissingDep {}
228
229#[cold]
230#[inline(never)]
231#[allow(clippy::panic)]
232fn missing_panic(type_name: &'static str) -> ! {
233 panic!(
234 "Deps::expect: missing dependency `{type_name}` (verify_deps should have caught this at workflow build time)"
235 )
236}
237
238pub trait DepsInjectable: RegisterableTask
246where
247 Self::Input: Send + 'static,
248 Self::Output: Send + 'static,
249 Self::Future: Send + 'static,
250{
251 fn from_deps(deps: &Deps) -> Self;
255
256 fn verify_deps(deps: &Deps) -> ::std::vec::Vec<MissingDep>;
259}
260
261#[cfg(test)]
262#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
263mod tests {
264 use super::*;
265 use std::sync::Arc;
266
267 #[derive(Clone, Debug, PartialEq, Eq)]
268 struct ServiceA(u32);
269
270 #[derive(Clone, Debug, PartialEq, Eq)]
271 struct ServiceB(&'static str);
272
273 #[test]
274 fn insert_and_get_concrete() {
275 let deps = Deps::builder().insert(ServiceA(7)).build();
276 assert_eq!(deps.get::<ServiceA>(), Some(ServiceA(7)));
277 }
278
279 #[test]
280 fn insert_arc_keeps_arc_key() {
281 let deps = Deps::builder().insert(Arc::new(ServiceA(7))).build();
282 assert!(deps.contains::<Arc<ServiceA>>());
283 assert!(!deps.contains::<ServiceA>());
284 let resolved: Arc<ServiceA> = deps.expect();
285 assert_eq!(*resolved, ServiceA(7));
286 }
287
288 #[test]
289 fn multiple_types_coexist() {
290 let deps = Deps::builder()
291 .insert(ServiceA(1))
292 .insert(ServiceB("hi"))
293 .build();
294 assert_eq!(deps.len(), 2);
295 assert_eq!(deps.get::<ServiceA>(), Some(ServiceA(1)));
296 assert_eq!(deps.get::<ServiceB>(), Some(ServiceB("hi")));
297 }
298
299 #[test]
300 fn missing_type_returns_none() {
301 let deps = Deps::new();
302 assert!(deps.get::<ServiceA>().is_none());
303 }
304
305 #[test]
306 fn try_get_reports_type_name() {
307 let deps = Deps::new();
308 let err = deps.try_get::<ServiceA>().unwrap_err();
309 assert!(err.type_name.contains("ServiceA"));
310 }
311
312 #[test]
313 fn last_insert_wins_for_same_type() {
314 let deps = Deps::builder()
315 .insert(ServiceA(1))
316 .insert(ServiceA(2))
317 .build();
318 assert_eq!(deps.get::<ServiceA>(), Some(ServiceA(2)));
319 }
320
321 #[test]
322 fn expect_returns_value() {
323 let deps = Deps::builder().insert(ServiceA(42)).build();
324 let value: ServiceA = deps.expect();
325 assert_eq!(value, ServiceA(42));
326 }
327
328 #[test]
329 #[should_panic(expected = "missing dependency")]
330 fn expect_panics_with_message() {
331 let deps = Deps::new();
332 let _: ServiceA = deps.expect();
333 }
334
335 #[test]
336 fn missing_dep_display() {
337 let m = MissingDep::of::<ServiceA>();
338 let rendered = format!("{m}");
339 assert!(rendered.contains("ServiceA"));
340 assert!(rendered.contains("missing dependency"));
341 }
342
343 #[test]
344 fn empty_and_len() {
345 let mut deps = Deps::new();
346 assert!(deps.is_empty());
347 deps = Deps::builder().insert(ServiceA(0)).build();
348 assert!(!deps.is_empty());
349 assert_eq!(deps.len(), 1);
350 }
351
352 #[test]
353 fn merge_non_overlapping() {
354 let mut base = Deps::builder().insert(ServiceA(1)).build();
355 let extra = Deps::builder().insert(ServiceB("x")).build();
356 base.merge(extra);
357
358 assert_eq!(base.len(), 2);
359 assert_eq!(base.get::<ServiceA>(), Some(ServiceA(1)));
360 assert_eq!(base.get::<ServiceB>(), Some(ServiceB("x")));
361 }
362
363 #[test]
364 fn merge_overlap_other_wins() {
365 let mut base = Deps::builder().insert(ServiceA(1)).build();
366 let extra = Deps::builder().insert(ServiceA(99)).build();
367 base.merge(extra);
368
369 assert_eq!(base.len(), 1);
370 assert_eq!(base.get::<ServiceA>(), Some(ServiceA(99)));
371 }
372
373 #[test]
374 fn merge_empty_into_populated() {
375 let mut base = Deps::builder().insert(ServiceA(1)).build();
376 base.merge(Deps::new());
377 assert_eq!(base.len(), 1);
378 assert_eq!(base.get::<ServiceA>(), Some(ServiceA(1)));
379 }
380
381 #[test]
382 fn merge_populated_into_empty() {
383 let mut base = Deps::new();
384 let extra = Deps::builder().insert(ServiceA(7)).build();
385 base.merge(extra);
386 assert_eq!(base.len(), 1);
387 assert_eq!(base.get::<ServiceA>(), Some(ServiceA(7)));
388 }
389
390 #[test]
391 fn builder_merge_layers_containers() {
392 let library = Deps::builder().insert(ServiceA(1)).build();
393 let combined = Deps::builder()
394 .insert(ServiceB("local"))
395 .merge(library)
396 .build();
397
398 assert_eq!(combined.len(), 2);
399 assert_eq!(combined.get::<ServiceA>(), Some(ServiceA(1)));
400 assert_eq!(combined.get::<ServiceB>(), Some(ServiceB("local")));
401 }
402
403 #[test]
404 fn builder_merge_other_wins_on_overlap() {
405 let library = Deps::builder().insert(ServiceA(2)).build();
406 let combined = Deps::builder().insert(ServiceA(1)).merge(library).build();
407
408 assert_eq!(combined.get::<ServiceA>(), Some(ServiceA(2)));
409 }
410}