Skip to main content

rust_dix/
collection.rs

1use crate::entry::{AsyncServiceFactory, ServiceDescriptor, ServiceFactory, ServiceLifetime};
2use crate::registration::ServiceRegistration;
3use std::any::{Any, TypeId};
4use std::future::Future;
5use std::pin::Pin;
6use std::sync::Arc;
7
8pub struct ServiceCollection {
9    descriptors: Vec<ServiceDescriptor>,
10}
11
12impl ServiceCollection {
13    pub fn new() -> Self {
14        Self {
15            descriptors: Vec::new(),
16        }
17    }
18
19    pub fn singleton<T: ?Sized + Send + Sync + 'static>(
20        mut self,
21        f: impl Fn(&dyn crate::entry::IServiceResolver) -> Arc<T> + Send + Sync + 'static,
22    ) -> Self {
23        self.push(ServiceLifetime::Singleton, None, f);
24        self
25    }
26
27    pub fn transient<T: ?Sized + Send + Sync + 'static>(
28        mut self,
29        f: impl Fn(&dyn crate::entry::IServiceResolver) -> Arc<T> + Send + Sync + 'static,
30    ) -> Self {
31        self.push(ServiceLifetime::Transient, None, f);
32        self
33    }
34
35    pub fn scoped<T: ?Sized + Send + Sync + 'static>(
36        mut self,
37        f: impl Fn(&dyn crate::entry::IServiceResolver) -> Arc<T> + Send + Sync + 'static,
38    ) -> Self {
39        self.push(ServiceLifetime::Scoped, None, f);
40        self
41    }
42
43    /// Register a keyed singleton service.
44    ///
45    /// The key distinguishes multiple implementations of the same trait.
46    /// Use `get_keyed::<T>(key)` or `try_get_keyed::<T>(key)` to resolve.
47    pub fn keyed_singleton<T: ?Sized + Send + Sync + 'static>(
48        mut self,
49        k: impl Into<String>,
50        f: impl Fn(&dyn crate::entry::IServiceResolver) -> Arc<T> + Send + Sync + 'static,
51    ) -> Self {
52        self.push(ServiceLifetime::Singleton, Some(k.into()), f);
53        self
54    }
55
56    /// Register a keyed transient service (new instance each resolution).
57    pub fn keyed_transient<T: ?Sized + Send + Sync + 'static>(
58        mut self,
59        k: impl Into<String>,
60        f: impl Fn(&dyn crate::entry::IServiceResolver) -> Arc<T> + Send + Sync + 'static,
61    ) -> Self {
62        self.push(ServiceLifetime::Transient, Some(k.into()), f);
63        self
64    }
65
66    /// Register a keyed scoped service (shared within a scope).
67    pub fn keyed_scoped<T: ?Sized + Send + Sync + 'static>(
68        mut self,
69        k: impl Into<String>,
70        f: impl Fn(&dyn crate::entry::IServiceResolver) -> Arc<T> + Send + Sync + 'static,
71    ) -> Self {
72        self.push(ServiceLifetime::Scoped, Some(k.into()), f);
73        self
74    }
75
76    /// Register an async singleton service (initialized once during `build_async`).
77    pub fn async_singleton<T: ?Sized + Send + Sync + 'static>(
78        mut self,
79        f: impl Fn(
80                Arc<crate::provider::ServiceProvider>,
81            ) -> Pin<Box<dyn Future<Output = Arc<T>> + Send>>
82            + Send
83            + Sync
84            + 'static,
85    ) -> Self {
86        self.push_async(ServiceLifetime::Singleton, None, f);
87        self
88    }
89
90    /// Register an async transient service (new async instance each resolution).
91    pub fn async_transient<T: ?Sized + Send + Sync + 'static>(
92        mut self,
93        f: impl Fn(
94                Arc<crate::provider::ServiceProvider>,
95            ) -> Pin<Box<dyn Future<Output = Arc<T>> + Send>>
96            + Send
97            + Sync
98            + 'static,
99    ) -> Self {
100        self.push_async(ServiceLifetime::Transient, None, f);
101        self
102    }
103
104    /// Register an async scoped service (shared within a scope).
105    pub fn async_scoped<T: ?Sized + Send + Sync + 'static>(
106        mut self,
107        f: impl Fn(
108                Arc<crate::provider::ServiceProvider>,
109            ) -> Pin<Box<dyn Future<Output = Arc<T>> + Send>>
110            + Send
111            + Sync
112            + 'static,
113    ) -> Self {
114        self.push_async(ServiceLifetime::Scoped, None, f);
115        self
116    }
117
118    /// Register an async keyed singleton service.
119    pub fn async_keyed_singleton<T: ?Sized + Send + Sync + 'static>(
120        mut self,
121        k: impl Into<String>,
122        f: impl Fn(
123                Arc<crate::provider::ServiceProvider>,
124            ) -> Pin<Box<dyn Future<Output = Arc<T>> + Send>>
125            + Send
126            + Sync
127            + 'static,
128    ) -> Self {
129        self.push_async(ServiceLifetime::Singleton, Some(k.into()), f);
130        self
131    }
132
133    /// Register an async keyed transient service.
134    pub fn async_keyed_transient<T: ?Sized + Send + Sync + 'static>(
135        mut self,
136        k: impl Into<String>,
137        f: impl Fn(
138                Arc<crate::provider::ServiceProvider>,
139            ) -> Pin<Box<dyn Future<Output = Arc<T>> + Send>>
140            + Send
141            + Sync
142            + 'static,
143    ) -> Self {
144        self.push_async(ServiceLifetime::Transient, Some(k.into()), f);
145        self
146    }
147
148    /// Register an async keyed scoped service.
149    pub fn async_keyed_scoped<T: ?Sized + Send + Sync + 'static>(
150        mut self,
151        k: impl Into<String>,
152        f: impl Fn(
153                Arc<crate::provider::ServiceProvider>,
154            ) -> Pin<Box<dyn Future<Output = Arc<T>> + Send>>
155            + Send
156            + Sync
157            + 'static,
158    ) -> Self {
159        self.push_async(ServiceLifetime::Scoped, Some(k.into()), f);
160        self
161    }
162
163    pub fn try_add<T: ?Sized + Send + Sync + 'static>(
164        self,
165        f: impl Fn(&dyn crate::entry::IServiceResolver) -> Arc<T> + Send + Sync + 'static,
166    ) -> Self {
167        self.try_add_with_lifetime(ServiceLifetime::Singleton, f)
168    }
169
170    /// Register a singleton only if no unkeyed registration for `T` exists yet.
171    pub fn try_add_singleton<T: ?Sized + Send + Sync + 'static>(
172        self,
173        f: impl Fn(&dyn crate::entry::IServiceResolver) -> Arc<T> + Send + Sync + 'static,
174    ) -> Self {
175        self.try_add_with_lifetime(ServiceLifetime::Singleton, f)
176    }
177
178    /// Register a scoped service only if no unkeyed registration for `T` exists yet.
179    pub fn try_add_scoped<T: ?Sized + Send + Sync + 'static>(
180        self,
181        f: impl Fn(&dyn crate::entry::IServiceResolver) -> Arc<T> + Send + Sync + 'static,
182    ) -> Self {
183        self.try_add_with_lifetime(ServiceLifetime::Scoped, f)
184    }
185
186    /// Register a transient service only if no unkeyed registration for `T` exists yet.
187    pub fn try_add_transient<T: ?Sized + Send + Sync + 'static>(
188        self,
189        f: impl Fn(&dyn crate::entry::IServiceResolver) -> Arc<T> + Send + Sync + 'static,
190    ) -> Self {
191        self.try_add_with_lifetime(ServiceLifetime::Transient, f)
192    }
193
194    fn try_add_with_lifetime<T: ?Sized + Send + Sync + 'static>(
195        mut self,
196        lt: ServiceLifetime,
197        f: impl Fn(&dyn crate::entry::IServiceResolver) -> Arc<T> + Send + Sync + 'static,
198    ) -> Self {
199        let tid = TypeId::of::<T>();
200        if self
201            .descriptors
202            .iter()
203            .any(|d| d.type_id == tid && d.key.is_none())
204        {
205            return self;
206        }
207        self.push(lt, None, f);
208        self
209    }
210
211    pub fn add<T: ?Sized + Send + Sync + 'static>(
212        mut self,
213        lt: ServiceLifetime,
214        f: impl Fn(&dyn crate::entry::IServiceResolver) -> Arc<T> + Send + Sync + 'static,
215    ) -> Self {
216        self.push(lt, None, f);
217        self
218    }
219
220    pub fn instance<T: Send + Sync + 'static>(mut self, v: Arc<T>) -> Self {
221        let ff: ServiceFactory = Arc::new(move |_| Arc::new(v.clone()));
222        self.descriptors.push(ServiceDescriptor {
223            type_id: TypeId::of::<T>(),
224            type_name: std::any::type_name::<T>(),
225            key: None,
226            factory: ff,
227            async_factory: None,
228            lifetime: ServiceLifetime::Singleton,
229        });
230        self
231    }
232
233    pub fn singleton_value<T: Send + Sync + 'static>(self, v: T) -> Self {
234        self.instance(Arc::new(v))
235    }
236
237    pub fn build(self) -> Result<Arc<crate::provider::ServiceProvider>, crate::error::RdiError> {
238        self.build_with(crate::provider::ServiceProviderOptions::default())
239    }
240
241    /// Build with explicit [`ServiceProviderOptions`](crate::ServiceProviderOptions).
242    ///
243    /// `build()` equals `build_with(ServiceProviderOptions::default())`, i.e.
244    /// strict scope validation is enabled by default (MEDI `ValidateScopes`).
245    /// Use [`crate::ServiceProviderOptions::lenient`] to restore the ≤ 0.7 root-scope
246    /// behavior where the root provider caches scoped services.
247    pub fn build_with(
248        self,
249        options: crate::provider::ServiceProviderOptions,
250    ) -> Result<Arc<crate::provider::ServiceProvider>, crate::error::RdiError> {
251        crate::provider::ServiceProvider::new_with(self.into_store(), options)
252    }
253
254    /// Build the provider, executing async factories for singleton services.
255    ///
256    /// Async singleton factories are run during build and their results are
257    /// cached as singleton instances. Async transient/scoped factories are
258    /// stored for later resolution.
259    ///
260    /// Returns `Arc<ServiceProvider>` so that `provider_arc()` works for
261    /// async resolution via `get_async` / `get_keyed_async`.
262    ///
263    /// Use this instead of [`build`](Self::build) when any service requires
264    /// async initialization (e.g., database connections, remote config).
265    pub async fn build_async(
266        self,
267    ) -> Result<Arc<crate::provider::ServiceProvider>, crate::error::RdiError> {
268        self.build_async_with(crate::provider::ServiceProviderOptions::default())
269            .await
270    }
271
272    /// Async variant of [`build_with`](Self::build_with).
273    pub async fn build_async_with(
274        self,
275        options: crate::provider::ServiceProviderOptions,
276    ) -> Result<Arc<crate::provider::ServiceProvider>, crate::error::RdiError> {
277        crate::provider::ServiceProvider::new_async_with(self.into_store(), options).await
278    }
279
280    /// Materialize descriptors into the by-TypeId `ServiceStore`.
281    ///
282    /// `cache_key` is the descriptor index (registration order); it drives
283    /// deterministic eager initialization and reverse-order disposal.
284    fn into_store(self) -> crate::entry::ServiceStore {
285        let mut s = crate::entry::ServiceStore::new();
286        for (n, d) in self.descriptors.into_iter().enumerate() {
287            let e = crate::entry::ServiceEntry {
288                cache_key: n,
289                key: d.key,
290                type_name: d.type_name,
291                factory: d.factory,
292                async_factory: d.async_factory,
293                lifetime: d.lifetime,
294            };
295            s.entry(d.type_id).or_default().push(e);
296        }
297        s
298    }
299
300    /// Build a `ServiceCollection` from all `#[rust_dix::inject]` annotations
301    /// in the current binary (across all crates).
302    #[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))]
303    pub fn from_injected() -> Self {
304        let mut descriptors = Vec::new();
305        for reg in inventory::iter::<ServiceRegistration> {
306            let factory: ServiceFactory = Arc::new(move |r| (reg.factory)(r));
307            descriptors.push(ServiceDescriptor {
308                type_id: reg.type_id,
309                type_name: (reg.type_name_fn)(),
310                key: None,
311                factory,
312                async_factory: None,
313                lifetime: reg.lifetime,
314            });
315        }
316        Self { descriptors }
317    }
318
319    /// Register a new singleton service.
320    fn push<T: ?Sized + Send + Sync + 'static>(
321        &mut self,
322        lt: ServiceLifetime,
323        key: Option<String>,
324        f: impl Fn(&dyn crate::entry::IServiceResolver) -> Arc<T> + Send + Sync + 'static,
325    ) {
326        let sf: ServiceFactory = Arc::new(move |r| {
327            let val: Arc<T> = (f)(r);
328            Arc::new(val) as Arc<dyn Any + Send + Sync>
329        });
330        self.descriptors.push(ServiceDescriptor {
331            type_id: TypeId::of::<T>(),
332            type_name: std::any::type_name::<T>(),
333            key,
334            factory: sf,
335            async_factory: None,
336            lifetime: lt,
337        });
338    }
339
340    /// Register an async service.
341    fn push_async<T: ?Sized + Send + Sync + 'static>(
342        &mut self,
343        lt: ServiceLifetime,
344        key: Option<String>,
345        f: impl Fn(
346                Arc<crate::provider::ServiceProvider>,
347            ) -> Pin<Box<dyn Future<Output = Arc<T>> + Send>>
348            + Send
349            + Sync
350            + 'static,
351    ) {
352        let af: AsyncServiceFactory = Arc::new(move |sp: Arc<crate::provider::ServiceProvider>| {
353            let fut = (f)(sp);
354            Box::pin(async move {
355                let val: Arc<T> = fut.await;
356                Arc::new(val) as Arc<dyn Any + Send + Sync>
357            }) as Pin<Box<dyn Future<Output = Arc<dyn Any + Send + Sync>> + Send>>
358        });
359        // Sync factory: typed error if somehow invoked; `build()` rejects async
360        // registrations before this path is reached.
361        let type_name = std::any::type_name::<T>();
362        let sf: ServiceFactory = Arc::new(move |_| {
363            std::panic::panic_any(crate::error::RdiError::AsyncServiceRequiresBuildAsync {
364                service: type_name.to_string(),
365            })
366        });
367        self.descriptors.push(ServiceDescriptor {
368            type_id: TypeId::of::<T>(),
369            type_name: std::any::type_name::<T>(),
370            key,
371            factory: sf,
372            async_factory: Some(af),
373            lifetime: lt,
374        });
375    }
376}
377impl Default for ServiceCollection {
378    fn default() -> Self {
379        Self::new()
380    }
381}
382
383#[cfg(test)]
384mod tests {
385    use super::*;
386    #[derive(Debug, PartialEq)]
387    struct G {
388        n: String,
389    }
390    #[derive(Debug, PartialEq)]
391    struct C {
392        v: i32,
393    }
394    #[test]
395    fn empty() {
396        let p = ServiceCollection::new().build().unwrap();
397        assert!(p.get_optional::<G>().is_none());
398    }
399    #[test]
400    fn singleton() {
401        let p = ServiceCollection::new()
402            .singleton(|_| Arc::new(G { n: "Hi".into() }))
403            .build()
404            .unwrap();
405        assert_eq!(p.get::<G>().unwrap().n, "Hi");
406    }
407    #[test]
408    fn singleton_caches() {
409        use std::sync::atomic::{AtomicUsize, Ordering};
410        static CNT: AtomicUsize = AtomicUsize::new(0);
411        let p = ServiceCollection::new()
412            .singleton(|_| {
413                CNT.fetch_add(1, Ordering::SeqCst);
414                Arc::new(C { v: 42 })
415            })
416            .build()
417            .unwrap();
418        let _ = p.get::<C>();
419        let _ = p.get::<C>();
420        assert_eq!(CNT.load(Ordering::SeqCst), 1);
421    }
422    #[test]
423    fn transient_not_cached() {
424        use std::sync::atomic::{AtomicUsize, Ordering};
425        static CNT: AtomicUsize = AtomicUsize::new(0);
426        let p = ServiceCollection::new()
427            .transient(|_| {
428                CNT.fetch_add(1, Ordering::SeqCst);
429                Arc::new(C { v: 1 })
430            })
431            .build()
432            .unwrap();
433        let _ = p.get::<C>();
434        let _ = p.get::<C>();
435        assert_eq!(CNT.load(Ordering::SeqCst), 2);
436    }
437    #[test]
438    fn instance() {
439        let g = Arc::new(G { n: "Inst".into() });
440        let p = ServiceCollection::new()
441            .instance(g.clone())
442            .build()
443            .unwrap();
444        assert!(Arc::ptr_eq(&g, &p.get::<G>().unwrap()));
445    }
446    #[test]
447    fn keyed_svc() {
448        let p = ServiceCollection::new()
449            .keyed_singleton("a", |_| Arc::new(G { n: "A".into() }))
450            .keyed_singleton("b", |_| Arc::new(G { n: "B".into() }))
451            .build()
452            .unwrap();
453        assert_eq!(p.get_keyed::<G>("a").unwrap().n, "A");
454        assert_eq!(p.get_keyed::<G>("b").unwrap().n, "B");
455    }
456    #[test]
457    fn get_all() {
458        let p = ServiceCollection::new()
459            .keyed_singleton("x", |_| Arc::new(C { v: 1 }))
460            .keyed_singleton("y", |_| Arc::new(C { v: 2 }))
461            .build()
462            .unwrap();
463        assert!(
464            p.get_all::<C>().is_empty(),
465            "get_all excludes keyed registrations (MEDI GetServices)"
466        );
467        let keyed: Vec<i32> = p.get_all_keyed::<C>().iter().map(|c| c.v).collect();
468        assert_eq!(keyed, vec![1, 2]);
469    }
470    #[test]
471    fn try_add_skips() {
472        let p = ServiceCollection::new()
473            .singleton(|_| Arc::new(G { n: "First".into() }))
474            .try_add(|_| Arc::new(G { n: "Second".into() }))
475            .build()
476            .unwrap();
477        assert_eq!(p.get::<G>().unwrap().n, "First");
478    }
479}