ferrous_di/collection/mod.rs
1//! Service collection module for dependency injection.
2//!
3//! This module contains the ServiceCollection type and related functionality
4//! for registering services and building service providers.
5
6use std::any::TypeId;
7use std::sync::Arc;
8
9use crate::{DiResult, DiError, Key, Lifetime, ServiceDescriptor, DiObserver};
10use crate::registration::{Registry, Registration, AnyArc};
11use crate::provider::ResolverContext;
12use crate::observer::Observers;
13use crate::prewarm::PrewarmSet;
14use crate::capabilities::CapabilityRegistry;
15use crate::ServiceProvider;
16
17
18pub mod module_system;
19pub use module_system::*;
20
21pub struct ServiceCollection {
22 registry: Registry,
23 observers: Observers,
24 prewarm: PrewarmSet,
25 pub(crate) capabilities: CapabilityRegistry,
26}
27
28impl ServiceCollection {
29 /// Creates a new empty service collection.
30 pub fn new() -> Self {
31 Self {
32 registry: Registry::new(),
33 observers: Observers::new(),
34 prewarm: PrewarmSet::new(),
35 capabilities: CapabilityRegistry::new(),
36 }
37 }
38
39 // ----- Concrete Type Registrations -----
40
41 /// Registers a singleton instance that will be shared across the entire application.
42 ///
43 /// The instance is created immediately and wrapped in an `Arc` for thread-safe sharing.
44 /// All requests for this service type will return the same instance.
45 ///
46 /// # Examples
47 ///
48 /// ```rust
49 /// # use ferrous_di::ServiceCollection;
50 /// struct Config {
51 /// database_url: String
52 /// }
53 ///
54 /// let mut services = ServiceCollection::new();
55 /// services.add_singleton(Config {
56 /// database_url: "postgres://localhost".to_string()
57 /// });
58 /// ```
59 pub fn add_singleton<T: 'static + Send + Sync>(&mut self, value: T) -> &mut Self {
60 let arc = Arc::new(value);
61 let key = Key::Type(TypeId::of::<T>(), std::any::type_name::<T>());
62 let ctor = move |_: &ResolverContext| -> DiResult<AnyArc> {
63 Ok(arc.clone())
64 };
65 self.registry.insert(key, Registration::with_metadata(
66 Lifetime::Singleton,
67 Arc::new(ctor),
68 None,
69 Some(TypeId::of::<T>()),
70 ));
71 self
72 }
73
74 /// Registers a singleton factory that creates the instance on first request.
75 ///
76 /// The factory is called only once, and the result is cached and shared across
77 /// all subsequent requests. The factory receives a `ResolverContext` to resolve
78 /// dependencies.
79 ///
80 /// # Examples
81 ///
82 /// ```rust
83 /// # use ferrous_di::{ServiceCollection, Resolver};
84 /// # use std::sync::Arc;
85 /// struct Database { url: String }
86 /// struct UserService { db: Arc<Database> }
87 ///
88 /// let mut services = ServiceCollection::new();
89 /// services.add_singleton(Database { url: "postgres://localhost".to_string() });
90 /// services.add_singleton_factory::<UserService, _>(|resolver| {
91 /// UserService {
92 /// db: resolver.get_required::<Database>()
93 /// }
94 /// });
95 /// ```
96 pub fn add_singleton_factory<T, F>(&mut self, factory: F) -> &mut Self
97 where
98 T: 'static + Send + Sync,
99 F: Fn(&ResolverContext) -> T + Send + Sync + 'static,
100 {
101 self.add_factory(Lifetime::Singleton, factory)
102 }
103
104 /// Registers a scoped factory that creates one instance per scope.
105 ///
106 /// Each scope gets its own instance, but within a scope, the same instance
107 /// is reused. Perfect for per-request services in web applications.
108 ///
109 /// # Examples
110 ///
111 /// ```rust
112 /// # use ferrous_di::{ServiceCollection, Resolver};
113 /// # use std::sync::Arc;
114 /// struct Database { url: String }
115 /// struct RequestContext { request_id: String }
116 /// struct UserService { db: Arc<Database>, context: Arc<RequestContext> }
117 ///
118 /// let mut services = ServiceCollection::new();
119 /// services.add_singleton(Database { url: "postgres://localhost".to_string() });
120 /// services.add_scoped_factory::<RequestContext, _>(|_| {
121 /// RequestContext { request_id: "req-123".to_string() }
122 /// });
123 /// services.add_scoped_factory::<UserService, _>(|resolver| {
124 /// UserService {
125 /// db: resolver.get_required::<Database>(),
126 /// context: resolver.get_required::<RequestContext>()
127 /// }
128 /// });
129 /// ```
130 pub fn add_scoped_factory<T, F>(&mut self, factory: F) -> &mut Self
131 where
132 T: 'static + Send + Sync,
133 F: Fn(&ResolverContext) -> T + Send + Sync + 'static,
134 {
135 self.add_factory(Lifetime::Scoped, factory)
136 }
137
138 /// Registers a transient factory that creates a new instance on every request.
139 ///
140 /// No caching is performed - the factory is called every time this service
141 /// is resolved, even within the same scope.
142 ///
143 /// # Examples
144 ///
145 /// ```rust
146 /// # use ferrous_di::{ServiceCollection, Resolver};
147 /// # use std::sync::Arc;
148 /// struct Database { url: String }
149 /// struct Logger { timestamp: std::time::SystemTime }
150 ///
151 /// let mut services = ServiceCollection::new();
152 /// services.add_singleton(Database { url: "postgres://localhost".to_string() });
153 /// services.add_transient_factory::<Logger, _>(|_| {
154 /// Logger { timestamp: std::time::SystemTime::now() }
155 /// });
156 /// ```
157 pub fn add_transient_factory<T, F>(&mut self, factory: F) -> &mut Self
158 where
159 T: 'static + Send + Sync,
160 F: Fn(&ResolverContext) -> T + Send + Sync + 'static,
161 {
162 self.add_factory(Lifetime::Transient, factory)
163 }
164
165 fn add_factory<T, F>(&mut self, lifetime: Lifetime, factory: F) -> &mut Self
166 where
167 T: 'static + Send + Sync,
168 F: Fn(&ResolverContext) -> T + Send + Sync + 'static,
169 {
170 let key = Key::Type(TypeId::of::<T>(), std::any::type_name::<T>());
171 let factory = Arc::new(factory);
172 let ctor = move |r: &ResolverContext| -> DiResult<AnyArc> {
173 // Let factories run - circular dependencies will panic with CircularPanic
174 // All other panics (including from get_required) will be caught at the top level
175 Ok(Arc::new(factory(r)))
176 };
177 self.registry.insert(key, Registration::with_metadata(
178 lifetime,
179 Arc::new(ctor),
180 None,
181 Some(TypeId::of::<T>()),
182 ));
183 self
184 }
185
186 // ----- Trait Single-Binding Registrations -----
187
188 /// Registers a singleton trait implementation.
189 ///
190 /// Binds a concrete implementation to a trait, creating a single instance
191 /// that's shared across the entire application. The implementation must
192 /// already be wrapped in an `Arc`.
193 ///
194 /// # Examples
195 ///
196 /// ```rust
197 /// # use ferrous_di::{ServiceCollection, Resolver};
198 /// # use std::sync::Arc;
199 /// trait Logger: Send + Sync {
200 /// fn log(&self, message: &str);
201 /// }
202 ///
203 /// struct FileLogger { path: String }
204 /// impl Logger for FileLogger {
205 /// fn log(&self, message: &str) {
206 /// // Write to file
207 /// }
208 /// }
209 ///
210 /// let mut services = ServiceCollection::new();
211 /// let logger = Arc::new(FileLogger { path: "/var/log/app.log".to_string() });
212 /// services.add_singleton_trait::<dyn Logger>(logger);
213 /// ```
214 pub fn add_singleton_trait<T>(&mut self, value: Arc<T>) -> &mut Self
215 where
216 T: ?Sized + 'static + Send + Sync,
217 {
218 let key = Key::Trait(std::any::type_name::<T>());
219 // Expert fix: Store as Arc<Arc<dyn Trait>> in Any
220 let any_arc: AnyArc = Arc::new(value.clone());
221 let ctor = move |_: &ResolverContext| -> DiResult<AnyArc> {
222 Ok(any_arc.clone())
223 };
224 self.registry.insert(key, Registration::with_metadata(
225 Lifetime::Singleton,
226 Arc::new(ctor),
227 None,
228 None, // We don't know the concrete implementation type for trait objects
229 ));
230 self
231 }
232
233 /// Registers a singleton trait factory.
234 ///
235 /// The factory creates a trait implementation on first request, and the result
236 /// is cached as a singleton. The factory must return an `Arc<Trait>`.
237 ///
238 /// # Examples
239 ///
240 /// ```rust
241 /// # use ferrous_di::{ServiceCollection, Resolver};
242 /// # use std::sync::Arc;
243 /// trait Logger: Send + Sync {
244 /// fn log(&self, message: &str);
245 /// }
246 ///
247 /// struct FileLogger { path: String }
248 /// impl Logger for FileLogger {
249 /// fn log(&self, message: &str) {
250 /// // Write to file
251 /// }
252 /// }
253 ///
254 /// let mut services = ServiceCollection::new();
255 /// services.add_singleton_trait_factory::<dyn Logger, _>(|_| {
256 /// Arc::new(FileLogger { path: "/var/log/app.log".to_string() })
257 /// });
258 /// ```
259 pub fn add_singleton_trait_factory<Trait, F>(&mut self, factory: F) -> &mut Self
260 where
261 Trait: ?Sized + 'static + Send + Sync,
262 F: Fn(&ResolverContext) -> Arc<Trait> + Send + Sync + 'static,
263 {
264 self.add_trait_factory_impl(Lifetime::Singleton, factory)
265 }
266
267 /// Registers a scoped trait factory.
268 ///
269 /// Creates one trait implementation per scope. Within a scope, the same instance
270 /// is reused, but different scopes get different instances.
271 ///
272 /// # Examples
273 ///
274 /// ```rust
275 /// # use ferrous_di::{ServiceCollection, Resolver};
276 /// # use std::sync::Arc;
277 /// trait RequestLogger: Send + Sync {
278 /// fn log_request(&self, path: &str);
279 /// }
280 ///
281 /// struct FileRequestLogger {
282 /// request_id: String,
283 /// file_handle: std::fs::File
284 /// }
285 /// impl RequestLogger for FileRequestLogger {
286 /// fn log_request(&self, path: &str) {
287 /// // Log with request ID
288 /// }
289 /// }
290 ///
291 /// let mut services = ServiceCollection::new();
292 /// services.add_scoped_trait_factory::<dyn RequestLogger, _>(|_| {
293 /// Arc::new(FileRequestLogger {
294 /// request_id: "req-456".to_string(),
295 /// file_handle: std::fs::File::create("/tmp/request.log").unwrap()
296 /// })
297 /// });
298 /// ```
299 pub fn add_scoped_trait_factory<Trait, F>(&mut self, factory: F) -> &mut Self
300 where
301 Trait: ?Sized + 'static + Send + Sync,
302 F: Fn(&ResolverContext) -> Arc<Trait> + Send + Sync + 'static,
303 {
304 self.add_trait_factory_impl(Lifetime::Scoped, factory)
305 }
306
307 /// Registers a transient trait factory.
308 ///
309 /// Creates a new trait implementation on every request. No caching is performed,
310 /// making this suitable for lightweight, stateless services.
311 ///
312 /// # Examples
313 ///
314 /// ```rust
315 /// # use ferrous_di::{ServiceCollection, Resolver};
316 /// # use std::sync::Arc;
317 /// trait TimeProvider: Send + Sync {
318 /// fn now(&self) -> std::time::SystemTime;
319 /// }
320 ///
321 /// struct SystemTimeProvider;
322 /// impl TimeProvider for SystemTimeProvider {
323 /// fn now(&self) -> std::time::SystemTime {
324 /// std::time::SystemTime::now()
325 /// }
326 /// }
327 ///
328 /// let mut services = ServiceCollection::new();
329 /// services.add_transient_trait_factory::<dyn TimeProvider, _>(|_| {
330 /// Arc::new(SystemTimeProvider)
331 /// });
332 /// ```
333 pub fn add_transient_trait_factory<Trait, F>(&mut self, factory: F) -> &mut Self
334 where
335 Trait: ?Sized + 'static + Send + Sync,
336 F: Fn(&ResolverContext) -> Arc<Trait> + Send + Sync + 'static,
337 {
338 self.add_trait_factory_impl(Lifetime::Transient, factory)
339 }
340
341 fn add_trait_factory_impl<Trait, F>(&mut self, lifetime: Lifetime, factory: F) -> &mut Self
342 where
343 Trait: ?Sized + 'static + Send + Sync,
344 F: Fn(&ResolverContext) -> Arc<Trait> + Send + Sync + 'static,
345 {
346 let key = Key::Trait(std::any::type_name::<Trait>());
347 let factory = Arc::new(factory);
348 let ctor = move |r: &ResolverContext| -> DiResult<AnyArc> {
349 // Expert fix: Store as Arc<Arc<dyn Trait>> in Any
350 Ok(Arc::new(factory(r)))
351 };
352 self.registry.insert(key, Registration::with_metadata(
353 lifetime,
354 Arc::new(ctor),
355 None,
356 None, // We don't know the concrete implementation type for trait factories
357 ));
358 self
359 }
360
361 // ----- Trait Multi-Binding Registrations -----
362
363 /// Add trait implementation to multi-binding list
364 pub fn add_trait_implementation<T>(&mut self, value: Arc<T>, lifetime: Lifetime) -> &mut Self
365 where
366 T: ?Sized + 'static + Send + Sync,
367 {
368 let name = std::any::type_name::<T>();
369 // Expert fix: Store Arc<dyn Trait> INSIDE Any as Arc<Arc<dyn Trait>>
370 let any_arc: AnyArc = Arc::new(value.clone());
371 let ctor = move |_: &ResolverContext| -> DiResult<AnyArc> {
372 Ok(any_arc.clone())
373 };
374 self.registry.many.entry(name).or_default().push(Registration::with_metadata(
375 lifetime,
376 Arc::new(ctor),
377 None,
378 None, // We don't know the concrete implementation type for trait objects
379 ));
380 self
381 }
382
383 /// Add trait factory to multi-binding list
384 pub fn add_trait_factory<Trait, F>(&mut self, lifetime: Lifetime, factory: F) -> &mut Self
385 where
386 Trait: ?Sized + 'static + Send + Sync,
387 F: Fn(&ResolverContext) -> Arc<Trait> + Send + Sync + 'static,
388 {
389 let name = std::any::type_name::<Trait>();
390 let factory = Arc::new(factory);
391 let ctor = move |r: &ResolverContext| -> DiResult<AnyArc> {
392 // Expert fix: Store as Arc<Arc<dyn Trait>> in Any
393 Ok(Arc::new(factory(r)))
394 };
395 self.registry.many.entry(name).or_default().push(Registration::with_metadata(
396 lifetime,
397 Arc::new(ctor),
398 None,
399 None, // We don't know the concrete implementation type for trait factories
400 ));
401 self
402 }
403
404 // ----- Service Descriptors and Introspection -----
405
406 /// Get all service descriptors for introspection and diagnostics.
407 ///
408 /// Returns a vector of `ServiceDescriptor` objects that describe all registered services,
409 /// including their keys, lifetimes, and implementation type information when available.
410 ///
411 /// # Examples
412 ///
413 /// ```
414 /// use ferrous_di::{ServiceCollection, Lifetime};
415 /// use std::sync::Arc;
416 ///
417 /// let mut services = ServiceCollection::new();
418 /// services.add_singleton(42usize);
419 /// services.add_scoped_factory::<String, _>(|_| "hello".to_string());
420 ///
421 /// let descriptors = services.get_service_descriptors();
422 /// assert_eq!(descriptors.len(), 2);
423 ///
424 /// // Find the usize singleton
425 /// let usize_desc = descriptors.iter()
426 /// .find(|d| d.type_name().contains("usize"))
427 /// .unwrap();
428 /// assert_eq!(usize_desc.lifetime, Lifetime::Singleton);
429 /// ```
430 pub fn get_service_descriptors(&self) -> Vec<ServiceDescriptor> {
431 let mut descriptors = Vec::new();
432
433 // Single-binding services
434 for (key, registration) in self.registry.iter() {
435 descriptors.push(ServiceDescriptor {
436 key: key.clone(),
437 lifetime: registration.lifetime,
438 impl_type_id: registration.impl_id,
439 impl_type_name: registration.impl_id.map(|_| key.display_name()), // Use the key's display name as impl name
440 has_metadata: registration.metadata.is_some(),
441 });
442 }
443
444 // Multi-binding services
445 for (trait_name, registrations) in &self.registry.many {
446 for (index, registration) in registrations.iter().enumerate() {
447 descriptors.push(ServiceDescriptor {
448 key: Key::MultiTrait(trait_name, index),
449 lifetime: registration.lifetime,
450 impl_type_id: registration.impl_id,
451 impl_type_name: registration.impl_id.map(|_| *trait_name),
452 has_metadata: registration.metadata.is_some(),
453 });
454 }
455 }
456
457 descriptors
458 }
459
460 /// Register a service with custom metadata.
461 ///
462 /// Metadata can be used for diagnostics, configuration, or other runtime introspection.
463 /// The metadata must implement Send + Sync + 'static.
464 ///
465 /// # Examples
466 ///
467 /// ```
468 /// use ferrous_di::{ServiceCollection, Lifetime};
469 /// use std::sync::Arc;
470 ///
471 /// #[derive(Debug)]
472 /// struct ServiceMetadata {
473 /// description: String,
474 /// version: String,
475 /// }
476 ///
477 /// let mut services = ServiceCollection::new();
478 /// services.add_with_metadata(
479 /// 42usize,
480 /// Lifetime::Singleton,
481 /// ServiceMetadata {
482 /// description: "Answer to everything".to_string(),
483 /// version: "1.0".to_string(),
484 /// }
485 /// );
486 /// ```
487 pub fn add_with_metadata<T, M>(&mut self, value: T, lifetime: Lifetime, metadata: M) -> &mut Self
488 where
489 T: 'static + Send + Sync,
490 M: Send + Sync + 'static,
491 {
492 let arc = Arc::new(value);
493 let key = Key::Type(TypeId::of::<T>(), std::any::type_name::<T>());
494 let ctor = move |_: &ResolverContext| -> DiResult<AnyArc> {
495 Ok(arc.clone())
496 };
497 self.registry.insert(key, Registration::with_metadata(
498 lifetime,
499 Arc::new(ctor),
500 Some(Box::new(metadata)),
501 Some(TypeId::of::<T>()),
502 ));
503 self
504 }
505
506 /// Get metadata for a specific service key.
507 ///
508 /// Returns the metadata if it exists and can be downcast to the specified type.
509 ///
510 /// # Examples
511 ///
512 /// ```
513 /// # use ferrous_di::{ServiceCollection, Lifetime, Key};
514 /// # use std::any::TypeId;
515 /// # #[derive(Debug, PartialEq)]
516 /// # struct ServiceMetadata { description: String }
517 /// # let mut services = ServiceCollection::new();
518 /// # services.add_with_metadata(42usize, Lifetime::Singleton, ServiceMetadata { description: "test".to_string() });
519 /// let key = Key::Type(TypeId::of::<usize>(), "usize");
520 /// let metadata = services.get_metadata::<ServiceMetadata>(&key);
521 /// assert!(metadata.is_some());
522 /// ```
523 pub fn get_metadata<M: 'static>(&self, key: &Key) -> Option<&M> {
524 self.registry.get(key)?
525 .metadata.as_ref()?
526 .downcast_ref::<M>()
527 }
528
529 // ----- Conditional Registration (TryAdd*) -----
530
531 /// Register a singleton if not already registered.
532 ///
533 /// This method only registers the service if no service of type `T` is currently registered.
534 /// It returns `true` if the service was registered, `false` if it was already registered.
535 ///
536 /// # Examples
537 ///
538 /// ```
539 /// use ferrous_di::ServiceCollection;
540 ///
541 /// let mut services = ServiceCollection::new();
542 ///
543 /// let registered1 = services.try_add_singleton(42usize);
544 /// assert!(registered1); // First registration succeeds
545 ///
546 /// let registered2 = services.try_add_singleton(100usize);
547 /// assert!(!registered2); // Second registration is ignored
548 /// ```
549 pub fn try_add_singleton<T: 'static + Send + Sync>(&mut self, value: T) -> bool {
550 let key = Key::Type(TypeId::of::<T>(), std::any::type_name::<T>());
551 if self.registry.contains_key(&key) {
552 false
553 } else {
554 self.add_singleton(value);
555 true
556 }
557 }
558
559 /// Register a singleton factory if not already registered.
560 pub fn try_add_singleton_factory<T, F>(&mut self, factory: F) -> bool
561 where
562 T: 'static + Send + Sync,
563 F: Fn(&ResolverContext) -> T + Send + Sync + 'static,
564 {
565 let key = Key::Type(TypeId::of::<T>(), std::any::type_name::<T>());
566 if self.registry.contains_key(&key) {
567 false
568 } else {
569 self.add_singleton_factory(factory);
570 true
571 }
572 }
573
574 /// Register a scoped factory if not already registered.
575 pub fn try_add_scoped_factory<T, F>(&mut self, factory: F) -> bool
576 where
577 T: 'static + Send + Sync,
578 F: Fn(&ResolverContext) -> T + Send + Sync + 'static,
579 {
580 let key = Key::Type(TypeId::of::<T>(), std::any::type_name::<T>());
581 if self.registry.contains_key(&key) {
582 false
583 } else {
584 self.add_scoped_factory(factory);
585 true
586 }
587 }
588
589 /// Register a transient factory if not already registered.
590 pub fn try_add_transient_factory<T, F>(&mut self, factory: F) -> bool
591 where
592 T: 'static + Send + Sync,
593 F: Fn(&ResolverContext) -> T + Send + Sync + 'static,
594 {
595 let key = Key::Type(TypeId::of::<T>(), std::any::type_name::<T>());
596 if self.registry.contains_key(&key) {
597 false
598 } else {
599 self.add_transient_factory(factory);
600 true
601 }
602 }
603
604 /// Register a singleton trait if not already registered.
605 pub fn try_add_singleton_trait<T>(&mut self, value: Arc<T>) -> bool
606 where
607 T: ?Sized + 'static + Send + Sync,
608 {
609 let key = Key::Trait(std::any::type_name::<T>());
610 if self.registry.contains_key(&key) {
611 false
612 } else {
613 self.add_singleton_trait(value);
614 true
615 }
616 }
617
618 /// Register a singleton trait factory if not already registered.
619 pub fn try_add_singleton_trait_factory<Trait, F>(&mut self, factory: F) -> bool
620 where
621 Trait: ?Sized + 'static + Send + Sync,
622 F: Fn(&ResolverContext) -> Arc<Trait> + Send + Sync + 'static,
623 {
624 let key = Key::Trait(std::any::type_name::<Trait>());
625 if self.registry.contains_key(&key) {
626 false
627 } else {
628 self.add_singleton_trait_factory(factory);
629 true
630 }
631 }
632
633 /// Register a scoped trait factory if not already registered.
634 pub fn try_add_scoped_trait_factory<Trait, F>(&mut self, factory: F) -> bool
635 where
636 Trait: ?Sized + 'static + Send + Sync,
637 F: Fn(&ResolverContext) -> Arc<Trait> + Send + Sync + 'static,
638 {
639 let key = Key::Trait(std::any::type_name::<Trait>());
640 if self.registry.contains_key(&key) {
641 false
642 } else {
643 self.add_scoped_trait_factory(factory);
644 true
645 }
646 }
647
648 /// Register a transient trait factory if not already registered.
649 pub fn try_add_transient_trait_factory<Trait, F>(&mut self, factory: F) -> bool
650 where
651 Trait: ?Sized + 'static + Send + Sync,
652 F: Fn(&ResolverContext) -> Arc<Trait> + Send + Sync + 'static,
653 {
654 let key = Key::Trait(std::any::type_name::<Trait>());
655 if self.registry.contains_key(&key) {
656 false
657 } else {
658 self.add_transient_trait_factory(factory);
659 true
660 }
661 }
662
663 /// Add enumerable trait registration (always adds, doesn't check for existing).
664 ///
665 /// This method is equivalent to `add_trait_implementation` but with a name that matches
666 /// Microsoft.Extensions.DependencyInjection conventions.
667 pub fn try_add_enumerable<T>(&mut self, value: Arc<T>, lifetime: Lifetime) -> &mut Self
668 where
669 T: ?Sized + 'static + Send + Sync,
670 {
671 // For enumerable services, we always add (no conditional logic)
672 self.add_trait_implementation(value, lifetime)
673 }
674
675 // ----- Named Service Registration -----
676
677 /// Register a named singleton service.
678 ///
679 /// Named services allow multiple registrations of the same type distinguished by name.
680 /// This is useful for scenarios like multiple database connections, different configurations, etc.
681 ///
682 /// # Examples
683 ///
684 /// ```
685 /// use ferrous_di::ServiceCollection;
686 ///
687 /// let mut services = ServiceCollection::new();
688 /// services.add_named_singleton("primary", 42usize);
689 /// services.add_named_singleton("secondary", 100usize);
690 ///
691 /// let provider = services.build();
692 /// // These would be resolved separately by name
693 /// ```
694 pub fn add_named_singleton<T: 'static + Send + Sync>(&mut self, name: &'static str, value: T) -> &mut Self {
695 let arc = Arc::new(value);
696 let key = Key::TypeNamed(TypeId::of::<T>(), std::any::type_name::<T>(), name);
697 let ctor = move |_: &ResolverContext| -> DiResult<AnyArc> {
698 Ok(arc.clone())
699 };
700 self.registry.insert(key, Registration::with_metadata(
701 Lifetime::Singleton,
702 Arc::new(ctor),
703 None,
704 Some(TypeId::of::<T>()),
705 ));
706 self
707 }
708
709 /// Register a named singleton factory.
710 pub fn add_named_singleton_factory<T, F>(&mut self, name: &'static str, factory: F) -> &mut Self
711 where
712 T: 'static + Send + Sync,
713 F: Fn(&ResolverContext) -> T + Send + Sync + 'static,
714 {
715 let key = Key::TypeNamed(TypeId::of::<T>(), std::any::type_name::<T>(), name);
716 let factory = Arc::new(factory);
717 let ctor = move |r: &ResolverContext| -> DiResult<AnyArc> {
718 Ok(Arc::new(factory(r)))
719 };
720 self.registry.insert(key, Registration::with_metadata(
721 Lifetime::Singleton,
722 Arc::new(ctor),
723 None,
724 Some(TypeId::of::<T>()),
725 ));
726 self
727 }
728
729 /// Register a named scoped factory.
730 pub fn add_named_scoped_factory<T, F>(&mut self, name: &'static str, factory: F) -> &mut Self
731 where
732 T: 'static + Send + Sync,
733 F: Fn(&ResolverContext) -> T + Send + Sync + 'static,
734 {
735 let key = Key::TypeNamed(TypeId::of::<T>(), std::any::type_name::<T>(), name);
736 let factory = Arc::new(factory);
737 let ctor = move |r: &ResolverContext| -> DiResult<AnyArc> {
738 Ok(Arc::new(factory(r)))
739 };
740 self.registry.insert(key, Registration::with_metadata(
741 Lifetime::Scoped,
742 Arc::new(ctor),
743 None,
744 Some(TypeId::of::<T>()),
745 ));
746 self
747 }
748
749 /// Register a named transient factory.
750 pub fn add_named_transient_factory<T, F>(&mut self, name: &'static str, factory: F) -> &mut Self
751 where
752 T: 'static + Send + Sync,
753 F: Fn(&ResolverContext) -> T + Send + Sync + 'static,
754 {
755 let key = Key::TypeNamed(TypeId::of::<T>(), std::any::type_name::<T>(), name);
756 let factory = Arc::new(factory);
757 let ctor = move |r: &ResolverContext| -> DiResult<AnyArc> {
758 Ok(Arc::new(factory(r)))
759 };
760 self.registry.insert(key, Registration::with_metadata(
761 Lifetime::Transient,
762 Arc::new(ctor),
763 None,
764 Some(TypeId::of::<T>()),
765 ));
766 self
767 }
768
769 /// Register a named singleton trait.
770 pub fn add_named_singleton_trait<T>(&mut self, name: &'static str, value: Arc<T>) -> &mut Self
771 where
772 T: ?Sized + 'static + Send + Sync,
773 {
774 let key = Key::TraitNamed(std::any::type_name::<T>(), name);
775 let any_arc: AnyArc = Arc::new(value.clone());
776 let ctor = move |_: &ResolverContext| -> DiResult<AnyArc> {
777 Ok(any_arc.clone())
778 };
779 self.registry.insert(key, Registration::with_metadata(
780 Lifetime::Singleton,
781 Arc::new(ctor),
782 None,
783 None, // We don't know the concrete implementation type for trait objects
784 ));
785 self
786 }
787
788 /// Register a named singleton trait factory.
789 pub fn add_named_singleton_trait_factory<Trait, F>(&mut self, name: &'static str, factory: F) -> &mut Self
790 where
791 Trait: ?Sized + 'static + Send + Sync,
792 F: Fn(&ResolverContext) -> Arc<Trait> + Send + Sync + 'static,
793 {
794 let key = Key::TraitNamed(std::any::type_name::<Trait>(), name);
795 let factory = Arc::new(factory);
796 let ctor = move |r: &ResolverContext| -> DiResult<AnyArc> {
797 Ok(Arc::new(factory(r)))
798 };
799 self.registry.insert(key, Registration::with_metadata(
800 Lifetime::Singleton,
801 Arc::new(ctor),
802 None,
803 None,
804 ));
805 self
806 }
807
808 /// Register a named scoped trait factory.
809 pub fn add_named_scoped_trait_factory<Trait, F>(&mut self, name: &'static str, factory: F) -> &mut Self
810 where
811 Trait: ?Sized + 'static + Send + Sync,
812 F: Fn(&ResolverContext) -> Arc<Trait> + Send + Sync + 'static,
813 {
814 let key = Key::TraitNamed(std::any::type_name::<Trait>(), name);
815 let factory = Arc::new(factory);
816 let ctor = move |r: &ResolverContext| -> DiResult<AnyArc> {
817 Ok(Arc::new(factory(r)))
818 };
819 self.registry.insert(key, Registration::with_metadata(
820 Lifetime::Scoped,
821 Arc::new(ctor),
822 None,
823 None,
824 ));
825 self
826 }
827
828 /// Register a named transient trait factory.
829 pub fn add_named_transient_trait_factory<Trait, F>(&mut self, name: &'static str, factory: F) -> &mut Self
830 where
831 Trait: ?Sized + 'static + Send + Sync,
832 F: Fn(&ResolverContext) -> Arc<Trait> + Send + Sync + 'static,
833 {
834 let key = Key::TraitNamed(std::any::type_name::<Trait>(), name);
835 let factory = Arc::new(factory);
836 let ctor = move |r: &ResolverContext| -> DiResult<AnyArc> {
837 Ok(Arc::new(factory(r)))
838 };
839 self.registry.insert(key, Registration::with_metadata(
840 Lifetime::Transient,
841 Arc::new(ctor),
842 None,
843 None,
844 ));
845 self
846 }
847
848 /// Add named multi-trait registration.
849 pub fn add_named_trait_implementation<T>(&mut self, name: &'static str, value: Arc<T>, lifetime: Lifetime) -> &mut Self
850 where
851 T: ?Sized + 'static + Send + Sync,
852 {
853 let trait_name = std::any::type_name::<T>();
854 let any_arc: AnyArc = Arc::new(value.clone());
855 let ctor = move |_: &ResolverContext| -> DiResult<AnyArc> {
856 Ok(any_arc.clone())
857 };
858
859 // For named multi-trait, we need to create unique keys with names
860 // We'll use a combination approach: store in many with a combined key
861 let combined_key = format!("{}#{}", trait_name, name);
862 let static_key: &'static str = Box::leak(combined_key.into_boxed_str());
863
864 self.registry.many.entry(static_key).or_default().push(Registration::with_metadata(
865 lifetime,
866 Arc::new(ctor),
867 None,
868 None,
869 ));
870 self
871 }
872
873 // ----- Observer Management -----
874
875 /// Adds a diagnostic observer for DI resolution events.
876 ///
877 /// Observers enable structured tracing and monitoring of the dependency injection
878 /// container's behavior. This is particularly valuable for agentic systems where
879 /// you need to correlate DI events with agent execution steps and debug complex
880 /// resolution chains.
881 ///
882 /// # Performance
883 ///
884 /// Observer calls are made synchronously during resolution. Keep observer
885 /// implementations lightweight to avoid impacting performance.
886 ///
887 /// # Examples
888 ///
889 /// ```
890 /// use ferrous_di::{ServiceCollection, LoggingObserver, DiObserver};
891 /// use std::sync::Arc;
892 ///
893 /// // Using the built-in logging observer
894 /// let mut services = ServiceCollection::new();
895 /// services.add_observer(Arc::new(LoggingObserver::new()));
896 ///
897 /// // Using a custom observer
898 /// struct MetricsObserver {
899 /// counter: std::sync::Arc<std::sync::atomic::AtomicU64>,
900 /// }
901 ///
902 /// impl DiObserver for MetricsObserver {
903 /// fn resolving(&self, key: &ferrous_di::Key) {
904 /// self.counter.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
905 /// }
906 ///
907 /// fn resolved(&self, _key: &ferrous_di::Key, _duration: std::time::Duration) {}
908 /// fn factory_panic(&self, _key: &ferrous_di::Key, _message: &str) {}
909 /// }
910 ///
911 /// let counter = Arc::new(std::sync::atomic::AtomicU64::new(0));
912 /// services.add_observer(Arc::new(MetricsObserver { counter: counter.clone() }));
913 ///
914 /// let provider = services.build();
915 /// // All resolutions will be observed
916 /// ```
917 pub fn add_observer(&mut self, observer: Arc<dyn DiObserver>) -> &mut Self {
918 self.observers.add(observer);
919 self
920 }
921
922 // ----- Decoration / Interceptors -----
923
924 /// Decorates all registrations of a trait with a wrapper function.
925 ///
926 /// This enables cross-cutting concerns like logging, timeouts, retries, rate limiting,
927 /// authentication, and PII scrubbing without modifying the original implementations.
928 /// The decorator function is applied to both single-binding and multi-binding registrations.
929 ///
930 /// This is particularly powerful for agentic systems where you need to apply consistent
931 /// policies across all tools or services.
932 ///
933 /// # Arguments
934 ///
935 /// * `decorator` - A function that takes an `Arc<T>` and returns a wrapped `Arc<T>`
936 ///
937 /// # Examples
938 ///
939 /// ```
940 /// use ferrous_di::{ServiceCollection, Resolver};
941 /// use std::sync::Arc;
942 ///
943 /// trait Tool: Send + Sync {
944 /// fn execute(&self, input: &str) -> String;
945 /// }
946 ///
947 /// struct FileTool;
948 /// impl Tool for FileTool {
949 /// fn execute(&self, input: &str) -> String {
950 /// format!("File operation: {}", input)
951 /// }
952 /// }
953 ///
954 /// struct LoggingWrapper<T: ?Sized> {
955/// inner: Arc<T>,
956/// }
957///
958/// impl<T: ?Sized> LoggingWrapper<T> {
959/// fn new(inner: Arc<T>) -> Self { Self { inner } }
960/// }
961///
962/// impl<T: Tool + ?Sized> Tool for LoggingWrapper<T> {
963 /// fn execute(&self, input: &str) -> String {
964 /// println!("Executing tool with input: {}", input);
965 /// let result = self.inner.execute(input);
966 /// println!("Tool result: {}", result);
967 /// result
968 /// }
969 /// }
970 ///
971 /// let mut services = ServiceCollection::new();
972 ///
973 /// // Register tools
974 /// services.add_singleton_trait::<dyn Tool>(Arc::new(FileTool));
975 ///
976 /// // Apply logging to all tools
977 /// services.decorate_trait::<dyn Tool, _>(|tool| {
978 /// Arc::new(LoggingWrapper::new(tool))
979 /// });
980 ///
981 /// let provider = services.build();
982 /// let tool = provider.get_required_trait::<dyn Tool>();
983 /// let result = tool.execute("test.txt");
984 /// // Logs: "Executing tool with input: test.txt"
985 /// // Logs: "Tool result: File operation: test.txt"
986 /// ```
987 pub fn decorate_trait<T, F>(&mut self, decorator: F) -> &mut Self
988 where
989 T: ?Sized + 'static + Send + Sync,
990 F: Fn(Arc<T>) -> Arc<T> + Send + Sync + 'static,
991 {
992 let trait_name = std::any::type_name::<T>();
993 let decorator = Arc::new(decorator);
994
995 // Decorate single-binding registration if it exists
996 let single_key = Key::Trait(trait_name);
997 if let Some(registration) = self.registry.get_mut(&single_key) {
998 let old_ctor = registration.ctor.clone();
999 let decorator_clone = decorator.clone();
1000
1001 registration.ctor = Arc::new(move |resolver| {
1002 // Call original constructor
1003 let original = old_ctor(resolver)?;
1004
1005 // Cast to the trait type and apply decorator
1006 let typed = original.downcast::<Arc<T>>()
1007 .map_err(|_| DiError::TypeMismatch(trait_name))?;
1008 let decorated = decorator_clone((*typed).clone());
1009
1010 // Wrap back in Arc<dyn Any>
1011 Ok(Arc::new(decorated))
1012 });
1013 }
1014
1015 // Decorate multi-binding registrations if they exist
1016 if let Some(registrations) = self.registry.many.get_mut(trait_name) {
1017 for registration in registrations.iter_mut() {
1018 let old_ctor = registration.ctor.clone();
1019 let decorator_clone = decorator.clone();
1020
1021 registration.ctor = Arc::new(move |resolver| {
1022 // Call original constructor
1023 let original = old_ctor(resolver)?;
1024
1025 // Cast to the trait type and apply decorator
1026 let typed = original.downcast::<Arc<T>>()
1027 .map_err(|_| DiError::TypeMismatch(trait_name))?;
1028 let decorated = decorator_clone((*typed).clone());
1029
1030 // Wrap back in Arc<dyn Any>
1031 Ok(Arc::new(decorated))
1032 });
1033 }
1034 }
1035
1036 self
1037 }
1038
1039 /// Decorates a concrete service type with a first-class decorator.
1040 ///
1041 /// This is the modern, type-safe approach to service decoration that provides
1042 /// access to the resolver during decoration. Perfect for workflow engines that
1043 /// need to inject dependencies during decoration.
1044 ///
1045 /// # Examples
1046 ///
1047 /// ```
1048 /// use ferrous_di::{ServiceCollection, ServiceDecorator, Resolver};
1049 /// use std::sync::Arc;
1050 ///
1051 /// struct UserService {
1052 /// name: String,
1053 /// }
1054 ///
1055 /// struct LoggingDecorator;
1056 ///
1057 /// impl ServiceDecorator<UserService> for LoggingDecorator {
1058 /// fn decorate(&self, original: Arc<UserService>, _resolver: &dyn ferrous_di::traits::ResolverCore) -> Arc<UserService> {
1059 /// println!("Accessing user: {}", original.name);
1060 /// original
1061 /// }
1062 /// }
1063 ///
1064 /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
1065 /// let mut services = ServiceCollection::new();
1066 /// services.add_singleton(UserService { name: "Alice".to_string() });
1067 /// services.decorate_with::<UserService, _>(LoggingDecorator);
1068 ///
1069 /// let provider = services.build();
1070 /// let user = provider.get_required::<UserService>(); // Logs: "Accessing user: Alice"
1071 /// # Ok(())
1072 /// # }
1073 /// ```
1074 pub fn decorate_with<T, D>(&mut self, decorator: D) -> &mut Self
1075 where
1076 T: 'static + Send + Sync,
1077 D: crate::decoration::ServiceDecorator<T> + 'static,
1078 {
1079 use crate::decoration::DecorationWrapper;
1080
1081 let key = crate::key::key_of_type::<T>();
1082
1083 if let Some(registration) = self.registry.get_mut(&key) {
1084 let old_ctor = registration.ctor.clone();
1085 let wrapper = Arc::new(DecorationWrapper::new(decorator));
1086
1087 registration.ctor = Arc::new(move |resolver| {
1088 // Call original constructor
1089 let original = old_ctor(resolver)?;
1090
1091 // Cast to the concrete type
1092 let typed = original.downcast::<T>()
1093 .map_err(|_| crate::DiError::TypeMismatch(std::any::type_name::<T>()))?;
1094
1095 // Apply decoration
1096 let decorated = wrapper.decorate(typed, resolver);
1097
1098 // Wrap back in Arc<dyn Any>
1099 Ok(decorated as crate::registration::AnyArc)
1100 });
1101 }
1102
1103 self
1104 }
1105
1106 /// Decorates a trait service type with a first-class decorator.
1107 ///
1108 /// Similar to `decorate_with` but works with trait objects for maximum flexibility.
1109 /// Essential for workflow engines that need to wrap trait implementations.
1110 ///
1111 /// # Examples
1112 ///
1113 /// ```
1114 /// use ferrous_di::{ServiceCollection, TraitDecorator, Resolver};
1115 /// use std::sync::Arc;
1116 ///
1117 /// trait Logger: Send + Sync {
1118 /// fn log(&self, message: &str);
1119 /// }
1120 ///
1121 /// struct ConsoleLogger;
1122 /// impl Logger for ConsoleLogger {
1123 /// fn log(&self, message: &str) {
1124 /// println!("LOG: {}", message);
1125 /// }
1126 /// }
1127 ///
1128 /// struct PrefixDecorator;
1129 ///
1130 /// impl TraitDecorator<dyn Logger> for PrefixDecorator {
1131 /// fn decorate(&self, original: Arc<dyn Logger>, _resolver: &dyn ferrous_di::traits::ResolverCore) -> Arc<dyn Logger> {
1132 /// struct PrefixLogger {
1133 /// inner: Arc<dyn Logger>,
1134 /// }
1135 /// impl Logger for PrefixLogger {
1136 /// fn log(&self, message: &str) {
1137 /// self.inner.log(&format!("[WORKFLOW] {}", message));
1138 /// }
1139 /// }
1140 /// Arc::new(PrefixLogger { inner: original })
1141 /// }
1142 /// }
1143 ///
1144 /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
1145 /// let mut services = ServiceCollection::new();
1146 /// services.add_singleton_trait::<dyn Logger>(Arc::new(ConsoleLogger));
1147 /// services.decorate_trait_with::<dyn Logger, _>(PrefixDecorator);
1148 ///
1149 /// let provider = services.build();
1150 /// let logger = provider.get_required_trait::<dyn Logger>();
1151 /// logger.log("Hello"); // Outputs: "[WORKFLOW] LOG: Hello"
1152 /// # Ok(())
1153 /// # }
1154 /// ```
1155 pub fn decorate_trait_with<T, D>(&mut self, decorator: D) -> &mut Self
1156 where
1157 T: ?Sized + 'static + Send + Sync,
1158 D: crate::decoration::TraitDecorator<T> + 'static,
1159 {
1160 use crate::decoration::TraitDecorationWrapper;
1161
1162 let trait_name = std::any::type_name::<T>();
1163 let wrapper = Arc::new(TraitDecorationWrapper::new(decorator));
1164
1165 // Decorate single-binding registration if it exists
1166 let single_key = crate::Key::Trait(trait_name);
1167 if let Some(registration) = self.registry.get_mut(&single_key) {
1168 let old_ctor = registration.ctor.clone();
1169 let wrapper_clone = wrapper.clone();
1170
1171 registration.ctor = Arc::new(move |resolver| {
1172 // Call original constructor
1173 let original = old_ctor(resolver)?;
1174
1175 // Cast to the trait type and apply decorator
1176 let typed = original.downcast::<Arc<T>>()
1177 .map_err(|_| crate::DiError::TypeMismatch(trait_name))?;
1178 let decorated = wrapper_clone.decorate((*typed).clone(), resolver);
1179
1180 // Wrap back in Arc<dyn Any>
1181 Ok(Arc::new(decorated) as crate::registration::AnyArc)
1182 });
1183 }
1184
1185 // Decorate multi-binding registrations if they exist
1186 if let Some(registrations) = self.registry.many.get_mut(trait_name) {
1187 for registration in registrations.iter_mut() {
1188 let old_ctor = registration.ctor.clone();
1189 let wrapper_clone = wrapper.clone();
1190
1191 registration.ctor = Arc::new(move |resolver| {
1192 // Call original constructor
1193 let original = old_ctor(resolver)?;
1194
1195 // Cast to the trait type and apply decorator
1196 let typed = original.downcast::<Arc<T>>()
1197 .map_err(|_| crate::DiError::TypeMismatch(trait_name))?;
1198 let decorated = wrapper_clone.decorate((*typed).clone(), resolver);
1199
1200 // Wrap back in Arc<dyn Any>
1201 Ok(Arc::new(decorated) as crate::registration::AnyArc)
1202 });
1203 }
1204 }
1205
1206 self
1207 }
1208
1209 // ----- Pre-warm / Readiness -----
1210
1211 /// Marks a concrete service type for pre-warming during startup.
1212 ///
1213 /// Pre-warmed services are resolved during the `ServiceProvider::ready()` call,
1214 /// eliminating cold-start penalties during agent execution. This is particularly
1215 /// useful for expensive-to-initialize services like ML models, database
1216 /// connections, and authentication tokens.
1217 ///
1218 /// Services that implement `ReadyCheck` will also have their readiness
1219 /// verified during the pre-warm phase.
1220 ///
1221 /// # Examples
1222 ///
1223 /// ```
1224 /// use ferrous_di::{ServiceCollection, ReadyCheck};
1225 /// use async_trait::async_trait;
1226 ///
1227 /// struct DatabaseService {
1228 /// connection: String,
1229 /// }
1230 ///
1231 /// #[async_trait]
1232 /// impl ReadyCheck for DatabaseService {
1233 /// async fn ready(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
1234 /// // Test database connection
1235 /// Ok(())
1236 /// }
1237 /// }
1238 ///
1239 /// let mut services = ServiceCollection::new();
1240 /// services.add_singleton(DatabaseService {
1241 /// connection: "postgres://localhost".to_string()
1242 /// });
1243 /// services.prewarm::<DatabaseService>(); // Pre-warm during startup
1244 ///
1245 /// // Later during startup:
1246 /// let provider = services.build();
1247 /// // let report = provider.ready().await?; // Resolves and checks DatabaseService
1248 /// ```
1249 pub fn prewarm<T: 'static + Send + Sync>(&mut self) -> &mut Self {
1250 self.prewarm.add_type::<T>();
1251 self
1252 }
1253
1254 /// Marks a trait service type for pre-warming during startup.
1255 ///
1256 /// Pre-warmed trait services have all their implementations resolved
1257 /// during the `ServiceProvider::ready()` call.
1258 ///
1259 /// # Examples
1260 ///
1261 /// ```
1262 /// use ferrous_di::{ServiceCollection, ReadyCheck};
1263 /// use async_trait::async_trait;
1264 /// use std::sync::Arc;
1265 ///
1266 /// trait CacheService: Send + Sync {
1267 /// fn get(&self, key: &str) -> Option<String>;
1268 /// }
1269 ///
1270 /// struct RedisCache;
1271 /// impl CacheService for RedisCache {
1272 /// fn get(&self, key: &str) -> Option<String> {
1273 /// // Redis implementation
1274 /// None
1275 /// }
1276 /// }
1277 ///
1278 /// #[async_trait]
1279 /// impl ReadyCheck for RedisCache {
1280 /// async fn ready(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
1281 /// // Test Redis connection
1282 /// Ok(())
1283 /// }
1284 /// }
1285 ///
1286 /// let mut services = ServiceCollection::new();
1287 /// services.add_singleton_trait::<dyn CacheService>(Arc::new(RedisCache));
1288 /// services.prewarm_trait::<dyn CacheService>(); // Pre-warm all cache implementations
1289 ///
1290 /// let provider = services.build();
1291 /// // let report = provider.ready().await?; // Resolves and checks all cache services
1292 /// ```
1293 pub fn prewarm_trait<T: ?Sized + 'static + Send + Sync>(&mut self) -> &mut Self {
1294 self.prewarm.add_trait::<T>();
1295 self
1296 }
1297
1298 /// Builds the final service provider from this collection.
1299 ///
1300 /// This method consumes the `ServiceCollection` and creates a `ServiceProvider`
1301 /// that can resolve registered services. The service provider is thread-safe
1302 /// and can be used to create scoped contexts for request-scoped services.
1303 ///
1304 /// # Returns
1305 ///
1306 /// A `ServiceProvider` that can resolve all registered services according to
1307 /// their configured lifetimes.
1308 ///
1309 /// # Examples
1310 ///
1311 /// ```
1312 /// use ferrous_di::{ServiceCollection, Resolver};
1313 /// use std::sync::Arc;
1314 ///
1315 /// let mut collection = ServiceCollection::new();
1316 /// collection.add_singleton(42usize);
1317 /// collection.add_transient_factory::<String, _>(|_| "Hello".to_string());
1318 ///
1319 /// let provider = collection.build();
1320 /// let number = provider.get_required::<usize>();
1321 /// let text = provider.get_required::<String>();
1322 ///
1323 /// assert_eq!(*number, 42);
1324 /// assert_eq!(&*text, "Hello");
1325 /// ```
1326 pub fn build(mut self) -> ServiceProvider {
1327 // Finalize registry by assigning scoped slot indices
1328 self.registry.finalize();
1329 ServiceProvider::new_with_observers_and_capabilities(self.registry, self.observers, self.capabilities)
1330 }
1331
1332 /// Registers an async singleton service with a factory.
1333 ///
1334 /// Perfect for workflow engines where nodes/tools need async initialization
1335 /// (network handshakes, auth, model warm-up).
1336 ///
1337 /// # Examples
1338 ///
1339 /// ```
1340 /// use ferrous_di::{ServiceCollection, AsyncFactory};
1341 /// use async_trait::async_trait;
1342 /// use std::sync::Arc;
1343 ///
1344 /// struct DatabasePool {
1345 /// connection_string: String,
1346 /// }
1347 ///
1348 /// struct AsyncDbPoolFactory;
1349 ///
1350 /// #[async_trait]
1351 /// impl AsyncFactory<DatabasePool> for AsyncDbPoolFactory {
1352 /// async fn create(&self, _resolver: &dyn ferrous_di::Resolver) -> Arc<DatabasePool> {
1353 /// // Simulate async database connection setup
1354 /// Arc::new(DatabasePool {
1355 /// connection_string: "postgres://localhost".to_string(),
1356 /// })
1357 /// }
1358 /// }
1359 ///
1360 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
1361 /// let mut services = ServiceCollection::new();
1362 /// services.add_singleton_async::<DatabasePool, _>(AsyncDbPoolFactory);
1363 /// # Ok(())
1364 /// # }
1365 /// ```
1366 #[cfg(feature = "async")]
1367 pub fn add_singleton_async<T, F>(&mut self, factory: F) -> &mut Self
1368 where
1369 T: Send + Sync + Clone + 'static,
1370 F: crate::async_factories::AsyncFactory<T> + 'static,
1371 {
1372 use crate::async_factories::AsyncFactoryWrapper;
1373
1374 let wrapper = AsyncFactoryWrapper::new(factory);
1375
1376 // Create a sync factory that executes the async factory in a blocking context
1377 let sync_factory = move |resolver: &ResolverContext| -> Arc<T> {
1378 // Try to get current tokio runtime handle
1379 if let Ok(_handle) = tokio::runtime::Handle::try_current() {
1380 // We're in an async context, but we can't block_on from within the runtime
1381 // Instead, use block_in_place to run the async task
1382 let future = wrapper.create(resolver);
1383 let result = tokio::task::block_in_place(|| {
1384 // Create a new runtime for this blocking task
1385 let rt = tokio::runtime::Builder::new_current_thread()
1386 .enable_all()
1387 .build()
1388 .expect("Failed to create blocking runtime");
1389 rt.block_on(future)
1390 });
1391
1392 match result {
1393 Ok(result) => result,
1394 Err(e) => {
1395 panic!("Async factory failed: {}", e);
1396 }
1397 }
1398 } else {
1399 // No async runtime, create a new one for this operation
1400 let rt = tokio::runtime::Builder::new_multi_thread()
1401 .enable_all()
1402 .build()
1403 .expect("Failed to create async runtime");
1404 match rt.block_on(wrapper.create(resolver)) {
1405 Ok(result) => result,
1406 Err(e) => {
1407 panic!("Async factory failed: {}", e);
1408 }
1409 }
1410 }
1411 };
1412
1413 // Register as singleton with the sync factory that returns Arc<T>
1414 self.add_singleton_factory::<Arc<T>, _>(sync_factory);
1415 self
1416 }
1417
1418 /// Registers an async scoped service with a factory.
1419 ///
1420 /// Perfect for per-workflow or per-node async initialization in workflow engines.
1421 ///
1422 /// # Examples
1423 ///
1424 /// ```
1425 /// use ferrous_di::{ServiceCollection, async_factory};
1426 /// use std::sync::Arc;
1427 ///
1428 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
1429 /// let mut services = ServiceCollection::new();
1430 ///
1431 /// services.add_scoped_async::<String, _>(async_factory!(|_resolver| async {
1432 /// // Simulate async session initialization
1433 /// Arc::new("session_initialized".to_string())
1434 /// }));
1435 /// # Ok(())
1436 /// # }
1437 /// ```
1438 #[cfg(feature = "async")]
1439 pub fn add_scoped_async<T, F>(&mut self, factory: F) -> &mut Self
1440 where
1441 T: Send + Sync + Clone + 'static,
1442 F: crate::async_factories::AsyncFactory<T> + 'static,
1443 {
1444 use crate::async_factories::AsyncFactoryWrapper;
1445
1446 let wrapper = AsyncFactoryWrapper::new(factory);
1447
1448 // Create a sync factory that executes the async factory in a blocking context
1449 let sync_factory = move |resolver: &ResolverContext| -> Arc<T> {
1450 // Try to get current tokio runtime handle
1451 if let Ok(_handle) = tokio::runtime::Handle::try_current() {
1452 // We're in an async context, but we can't block_on from within the runtime
1453 // Instead, use spawn_blocking to run the async task
1454 let future = wrapper.create(resolver);
1455 let result = tokio::task::block_in_place(|| {
1456 // Create a new runtime for this blocking task
1457 let rt = tokio::runtime::Builder::new_current_thread()
1458 .enable_all()
1459 .build()
1460 .expect("Failed to create blocking runtime");
1461 rt.block_on(future)
1462 });
1463
1464 match result {
1465 Ok(result) => result,
1466 Err(e) => {
1467 panic!("Async factory failed: {}", e);
1468 }
1469 }
1470 } else {
1471 // No async runtime, create a new one for this operation
1472 let rt = tokio::runtime::Builder::new_multi_thread()
1473 .enable_all()
1474 .build()
1475 .expect("Failed to create async runtime");
1476 match rt.block_on(wrapper.create(resolver)) {
1477 Ok(result) => result,
1478 Err(e) => {
1479 panic!("Async factory failed: {}", e);
1480 }
1481 }
1482 }
1483 };
1484
1485 // Register as scoped with the sync factory that returns Arc<T>
1486 self.add_scoped_factory::<Arc<T>, _>(sync_factory);
1487 self
1488 }
1489}
1490
1491impl Default for ServiceCollection {
1492 fn default() -> Self {
1493 Self::new()
1494 }
1495}
1496