ferrous_di/
lib.rs

1//! # ferrous-di
2//!
3//! Type-safe, performant dependency injection for Rust, inspired by Microsoft.Extensions.DependencyInjection.
4//!
5//! ## Features
6//!
7//! - **Type-safe lifetimes**: Singleton, Scoped, and Transient services
8//! - **Trait support**: Single and multi-binding trait resolution
9//! - **Thread-safe**: Arc-based sharing with proper lifetime management  
10//! - **Circular dependency detection**: Prevents infinite loops with detailed error paths
11//! - **Scoped isolation**: Request-scoped services with proper cleanup
12//! - **Zero-cost abstractions**: Compile-time type safety with runtime performance
13//!
14//! ## Quick Start
15//!
16//! ```rust
17//! use ferrous_di::{ServiceCollection, Resolver};
18//! use std::sync::Arc;
19//!
20//! // Define your services
21//! struct Database {
22//!     connection_string: String,
23//! }
24//!
25//! struct UserService {
26//!     db: Arc<Database>,
27//! }
28//!
29//! // Register services
30//! let mut services = ServiceCollection::new();
31//! services.add_singleton(Database {
32//!     connection_string: "postgres://localhost".to_string(),
33//! });
34//! services.add_transient_factory::<UserService, _>(|resolver| {
35//!     UserService {
36//!         db: resolver.get_required::<Database>(),
37//!     }
38//! });
39//!
40//! // Build and use the service provider
41//! let provider = services.build();
42//! let user_service = provider.get_required::<UserService>();
43//! assert_eq!(user_service.db.connection_string, "postgres://localhost");
44//! ```
45//!
46//! ## Service Lifetimes
47//!
48//! - **Singleton**: Created once and shared across the entire application
49//! - **Scoped**: Created once per scope (ideal for web request contexts)
50//! - **Transient**: Created fresh on every resolution
51//!
52//! ## Trait Resolution
53//!
54//! ```rust
55//! use ferrous_di::{ServiceCollection, Resolver};
56//! use std::sync::Arc;
57//!
58//! trait Logger: Send + Sync {
59//!     fn log(&self, message: &str);
60//! }
61//!
62//! struct ConsoleLogger;
63//! impl Logger for ConsoleLogger {
64//!     fn log(&self, message: &str) {
65//!         println!("[LOG] {}", message);
66//!     }
67//! }
68//!
69//! let mut services = ServiceCollection::new();
70//! services.add_singleton_trait::<dyn Logger>(Arc::new(ConsoleLogger));
71//!
72//! let provider = services.build();
73//! let logger = provider.get_required_trait::<dyn Logger>();
74//! logger.log("Hello, World!");
75//! ```
76//!
77//! ## Scoped Services
78//!
79//! ```rust
80//! use ferrous_di::{ServiceCollection, Resolver};
81//! use std::sync::{Arc, Mutex};
82//!
83//! struct RequestId(String);
84//!
85//! let mut services = ServiceCollection::new();
86//! let counter = Arc::new(Mutex::new(0));
87//! let counter_clone = counter.clone();
88//!
89//! services.add_scoped_factory::<RequestId, _>(move |_| {
90//!     let mut c = counter_clone.lock().unwrap();
91//!     *c += 1;
92//!     RequestId(format!("req-{}", *c))
93//! });
94//!
95//! let provider = services.build();
96//! let scope1 = provider.create_scope();
97//! let scope2 = provider.create_scope();
98//!
99//! let req1 = scope1.get_required::<RequestId>();
100//! let req2 = scope2.get_required::<RequestId>();
101//! // Different scopes get different instances
102//! ```
103
104// Module declarations
105pub mod collection;
106pub mod provider;
107pub mod descriptors;
108pub mod error;
109pub mod key;
110pub mod lifetime;
111pub mod metrics;
112pub mod observer;
113pub mod performance;
114pub mod prewarm;
115pub mod scope_local;
116pub mod capabilities;
117pub mod validation;
118pub mod fast_singletons;
119pub mod traits;
120
121#[cfg(feature = "config")]
122pub mod config;
123
124#[cfg(feature = "web")]
125#[cfg(feature = "async")]
126pub mod web_integration;
127
128#[cfg(feature = "axum-integration")]
129pub mod axum_integration;
130
131#[cfg(feature = "async")]
132pub mod async_factories;
133pub mod cancellation;
134pub mod labeled_scopes;
135pub mod decoration;
136pub mod graph_export;
137
138// Internal modules
139mod internal;
140mod registration;
141
142// Standard library imports for Options pattern
143use std::sync::Arc;
144
145// Import for internal use in Options pattern
146use self::provider::ResolverContext as InternalResolverContext;
147
148// Re-export core types
149pub use collection::{ServiceCollection, ServiceModule, ServiceCollectionExt, ServiceCollectionModuleExt};
150pub use provider::{ServiceProvider, Scope, ScopedResolver, ResolverContext};
151pub use descriptors::ServiceDescriptor;
152pub use error::{DiError, DiResult};
153pub use internal::CircularPanic;
154pub use key::{Key, key_of_type};
155pub use lifetime::Lifetime;
156pub use observer::{DiObserver, LoggingObserver, ObservationContext, WorkflowObserver, WorkflowContextProvider, MetricsObserver};
157pub use prewarm::{ReadyCheck, ReadinessResult, ReadinessReport};
158pub use scope_local::{ScopeLocal, WorkflowContext, ScopeLocalBuilder, workflow};
159pub use capabilities::{ToolCapability, CapabilityRequirement, ToolSelectionCriteria, ToolInfo, ToolDiscoveryResult};
160pub use validation::{ValidationBuilder, ValidationResult, ValidationError, ValidationWarning};
161pub use fast_singletons::{FastSingletonCache, FastSingletonMetrics};
162pub use traits::{Dispose, AsyncDispose, Resolver, ResolverCore};
163
164#[cfg(feature = "async")]
165pub use async_factories::AsyncFactory;
166pub use cancellation::{CancellationToken, CancellationError, ScopeCancellationExt};
167pub use labeled_scopes::{LabeledScope, LabeledScopeExt, LabeledScopeContext, LabeledScopeRegistry, ScopeMetadata};
168pub use decoration::{ServiceDecorator, TraitDecorator, DecorationPipeline, decorators};
169pub use graph_export::{
170    DependencyGraph, GraphNode, GraphEdge, GraphMetadata, GraphLayout, NodePosition, LayoutBounds,
171    DependencyType, ExportOptions, ExportFormat, GraphBuilder, GraphExporter, DefaultGraphExporter,
172    exports, workflow_integration
173};
174
175// ===== Options Pattern =====
176
177/// Options interface for dependency injection.
178///
179/// This trait provides access to immutable configuration snapshots that are resolved
180/// once during application startup and remain consistent throughout the application lifetime.
181/// 
182/// Inspired by Microsoft.Extensions.DependencyInjection's `IOptions<T>` pattern.
183///
184/// # Examples
185///
186/// ```
187/// use ferrous_di::{ServiceCollection, IOptions, Options, Resolver};
188/// use std::sync::Arc;
189///
190/// #[derive(Default)]
191/// struct AppSettings {
192///     name: String,
193///     debug: bool,
194/// }
195///
196/// let mut services = ServiceCollection::new();
197/// services.add_options::<AppSettings>()
198///     .configure(|_r, s| {
199///         s.name = "MyApp".to_string();
200///         s.debug = true;
201///     })
202///     .register();
203///
204/// let provider = services.build();
205/// let options = provider.get_required::<Options<AppSettings>>();
206/// let settings = options.get();
207/// assert_eq!(settings.name, "MyApp");
208/// assert!(settings.debug);
209/// ```
210pub trait IOptions<T>: Send + Sync + 'static {
211    /// Gets the configured options instance.
212    fn get(&self) -> Arc<T>;
213}
214
215/// Immutable options wrapper that implements `IOptions<T>`.
216///
217/// This struct holds an `Arc<T>` containing the final configured options snapshot.
218/// Options are built once during container setup and remain immutable thereafter.
219///
220/// # Examples
221///
222/// ```
223/// use ferrous_di::{ServiceCollection, IOptions, Options, Resolver};
224///
225/// #[derive(Default)]
226/// struct DatabaseConfig {
227///     connection_string: String,
228///     max_connections: u32,
229/// }
230///
231/// let mut services = ServiceCollection::new();
232/// services.add_options::<DatabaseConfig>()
233///     .default_with(|| DatabaseConfig {
234///         connection_string: "postgres://localhost".to_string(),
235///         max_connections: 10,
236///     })
237///     .validate(|cfg| {
238///         if cfg.max_connections == 0 {
239///             Err("max_connections must be > 0".to_string())
240///         } else {
241///             Ok(())
242///         }
243///     })
244///     .register();
245///
246/// let provider = services.build();
247/// let config = provider.get_required::<Options<DatabaseConfig>>();
248/// let db_config = config.get();
249/// assert_eq!(db_config.max_connections, 10);
250/// ```
251pub struct Options<T> {
252    inner: Arc<T>,
253}
254
255impl<T> Options<T> {
256    /// Creates a new `Options<T>` wrapping the given value.
257    pub fn new(value: T) -> Self {
258        Self { inner: Arc::new(value) }
259    }
260
261    /// Gets a reference to the inner `Arc<T>`.
262    pub fn value(&self) -> &Arc<T> {
263        &self.inner
264    }
265
266    /// Gets a clone of the inner `Arc<T>` containing the configured options.
267    /// This is a convenience method that calls the `IOptions<T>` trait method.
268    pub fn get(&self) -> Arc<T> {
269        self.inner.clone()
270    }
271}
272
273impl<T> IOptions<T> for Options<T>
274where
275    T: Send + Sync + 'static,
276{
277    fn get(&self) -> Arc<T> {
278        self.inner.clone()
279    }
280}
281
282// Type aliases for common options patterns
283type ConfigureFn<T> = Arc<dyn Fn(&InternalResolverContext, &mut T) + Send + Sync>;
284type PostConfigureFn<T> = Arc<dyn Fn(&InternalResolverContext, &mut T) + Send + Sync>;
285type ValidateFn<T> = Arc<dyn Fn(&T) -> Result<(), String> + Send + Sync>;
286
287/// Options builder for configuring complex options with dependencies.
288///
289/// The `OptionsBuilder` provides a fluent API for configuring options that depend on
290/// other services from the DI container. It supports default value creation, configuration
291/// phases, post-configuration for computed values, and validation.
292///
293/// This builder allows you to configure options using other services from the DI container,
294/// post-process them after all configurations are applied, and validate the final result.
295/// It follows the builder pattern popularized by Microsoft.Extensions.DependencyInjection.
296///
297/// # Examples
298///
299/// ```
300/// use ferrous_di::{ServiceCollection, Resolver, Options};
301/// use std::sync::Arc;
302///
303/// #[derive(Default)]
304/// struct ApiConfig {
305///     base_url: String,
306///     timeout_ms: u64,
307///     api_key: Option<String>,
308/// }
309///
310/// // Mock config provider
311/// trait ConfigProvider: Send + Sync {
312///     fn get(&self, key: &str) -> Option<String>;
313/// }
314/// struct EnvConfig;
315/// impl ConfigProvider for EnvConfig {
316///     fn get(&self, key: &str) -> Option<String> {
317///         std::env::var(key).ok()
318///     }
319/// }
320///
321/// let mut services = ServiceCollection::new();
322/// services.add_singleton_trait::<dyn ConfigProvider>(Arc::new(EnvConfig));
323///
324/// services.add_options::<ApiConfig>()
325///     .default_with(|| ApiConfig {
326///         base_url: "https://api.example.com".to_string(),
327///         timeout_ms: 5000,
328///         api_key: None,
329///     })
330///     .configure(|resolver, config| {
331///         let provider = resolver.get_required_trait::<dyn ConfigProvider>();
332///         if let Some(url) = provider.get("API_BASE_URL") {
333///             config.base_url = url;
334///         }
335///         if let Some(timeout) = provider.get("API_TIMEOUT").and_then(|s| s.parse().ok()) {
336///             config.timeout_ms = timeout;
337///         }
338///         config.api_key = provider.get("API_KEY");
339///     })
340///     .post_configure(|_resolver, config| {
341///         // Normalize the base URL
342///         if !config.base_url.ends_with('/') {
343///             config.base_url.push('/');
344///         }
345///     })
346///     .validate(|config| {
347///         if config.timeout_ms == 0 {
348///             return Err("timeout_ms must be greater than 0".to_string());
349///         }
350///         if config.base_url.is_empty() {
351///             return Err("base_url cannot be empty".to_string());
352///         }
353///         Ok(())
354///     })
355///     .register();
356///
357/// let provider = services.build();
358/// let options = provider.get_required::<Options<ApiConfig>>();
359/// let api_config = options.get();
360/// assert!(api_config.timeout_ms > 0);
361/// assert!(api_config.base_url.ends_with('/'));
362/// ```
363pub struct OptionsBuilder<T>
364where
365    T: Default + Send + Sync + 'static,
366{
367    sc: *mut ServiceCollection, // raw ptr to allow builder-style API before build()
368    default_maker: Option<Arc<dyn Fn() -> T + Send + Sync>>,
369    configures: Vec<ConfigureFn<T>>,
370    post_configures: Vec<PostConfigureFn<T>>,
371    validates: Vec<ValidateFn<T>>,
372    // Named options: optional future extension
373    _name: Option<&'static str>,
374}
375
376impl<T> OptionsBuilder<T>
377where
378    T: Default + Send + Sync + 'static,
379{
380    pub(crate) fn new(sc: &mut ServiceCollection) -> Self {
381        Self {
382            sc,
383            default_maker: None,
384            configures: Vec::new(),
385            post_configures: Vec::new(),
386            validates: Vec::new(),
387            _name: None,
388        }
389    }
390
391    /// Provide a custom default value creator (otherwise T::default()).
392    ///
393    /// This allows you to set up initial values that differ from the type's Default implementation.
394    ///
395    /// # Examples
396    ///
397    /// ```
398    /// use ferrous_di::{ServiceCollection, IOptions, Options, Resolver};
399    ///
400    /// #[derive(Default)]
401    /// struct ServerConfig {
402    ///     host: String,
403    ///     port: u16,
404    /// }
405    ///
406    /// let mut services = ServiceCollection::new();
407    /// services.add_options::<ServerConfig>()
408    ///     .default_with(|| ServerConfig {
409    ///         host: "0.0.0.0".to_string(),
410    ///         port: 8080,
411    ///     })
412    ///     .register();
413    ///
414    /// let provider = services.build();
415    /// let options = provider.get_required::<Options<ServerConfig>>();
416    /// let config = options.get();
417    /// assert_eq!(config.host, "0.0.0.0");
418    /// assert_eq!(config.port, 8080);
419    /// ```
420    pub fn default_with<F>(mut self, f: F) -> Self
421    where
422        F: Fn() -> T + Send + Sync + 'static,
423    {
424        self.default_maker = Some(Arc::new(f));
425        self
426    }
427
428    /// Configure options by providing a callback that can resolve other services from the container.
429    ///
430    /// Configure callbacks are executed in the order they were added. The callback receives
431    /// a resolver that can be used to access other registered services.
432    ///
433    /// # Examples
434    ///
435    /// ```
436    /// use ferrous_di::{ServiceCollection, IOptions, Options, Resolver};
437    ///
438    /// #[derive(Default)]
439    /// struct AppConfig {
440    ///     feature_enabled: bool,
441    /// }
442    ///
443    /// let mut services = ServiceCollection::new();
444    /// services.add_singleton("production".to_string()); // Environment name
445    ///
446    /// services.add_options::<AppConfig>()
447    ///     .configure(|resolver, config| {
448    ///         let env = resolver.get_required::<String>();
449    ///         config.feature_enabled = env.as_str() == "production";
450    ///     })
451    ///     .register();
452    ///
453    /// let provider = services.build();
454    /// let options = provider.get_required::<Options<AppConfig>>();
455    /// let config = options.get();
456    /// assert!(config.feature_enabled);
457    /// ```
458    pub fn configure<F>(mut self, f: F) -> Self
459    where
460        F: Fn(&InternalResolverContext, &mut T) + Send + Sync + 'static,
461    {
462        self.configures.push(Arc::new(f));
463        self
464    }
465
466    /// Post-configure options after all configure actions have been applied.
467    ///
468    /// Post-configure callbacks run after all configure callbacks and are useful for
469    /// computed values, normalization, or cross-field validation and correction.
470    ///
471    /// # Examples
472    ///
473    /// ```
474    /// use ferrous_di::{ServiceCollection, IOptions, Options, Resolver};
475    ///
476    /// #[derive(Default)]
477    /// struct UrlConfig {
478    ///     base_url: String,
479    ///     api_path: String,
480    ///     full_url: String, // Computed field
481    /// }
482    ///
483    /// let mut services = ServiceCollection::new();
484    /// services.add_options::<UrlConfig>()
485    ///     .configure(|_resolver, config| {
486    ///         config.base_url = "https://api.example.com".to_string();
487    ///         config.api_path = "/v1/users".to_string();
488    ///     })
489    ///     .post_configure(|_resolver, config| {
490    ///         // Compute the full URL after base configuration
491    ///         config.full_url = format!("{}{}", config.base_url, config.api_path);
492    ///     })
493    ///     .register();
494    ///
495    /// let provider = services.build();
496    /// let options = provider.get_required::<Options<UrlConfig>>();
497    /// let config = options.get();
498    /// assert_eq!(config.full_url, "https://api.example.com/v1/users");
499    /// ```
500    pub fn post_configure<F>(mut self, f: F) -> Self
501    where
502        F: Fn(&ResolverContext, &mut T) + Send + Sync + 'static,
503    {
504        self.post_configures.push(Arc::new(f));
505        self
506    }
507
508    /// Validate the final options after all configuration steps.
509    ///
510    /// Validation callbacks are executed after all configure and post-configure callbacks.
511    /// If any validation fails, the application will panic with a descriptive error message
512    /// following the fail-fast principle for configuration errors.
513    ///
514    /// # Examples
515    ///
516    /// ```should_panic
517    /// use ferrous_di::{ServiceCollection, IOptions, Options, Resolver};
518    ///
519    /// #[derive(Default)]
520    /// struct DatabaseConfig {
521    ///     connection_string: String,
522    ///     pool_size: u32,
523    /// }
524    ///
525    /// let mut services = ServiceCollection::new();
526    /// services.add_options::<DatabaseConfig>()
527    ///     .configure(|_resolver, config| {
528    ///         config.connection_string = "".to_string(); // Invalid!
529    ///         config.pool_size = 0; // Also invalid!
530    ///     })
531    ///     .validate(|config| {
532    ///         if config.connection_string.is_empty() {
533    ///             return Err("connection_string cannot be empty".to_string());
534    ///         }
535    ///         if config.pool_size == 0 {
536    ///             return Err("pool_size must be greater than 0".to_string());
537    ///         }
538    ///         Ok(())
539    ///     })
540    ///     .register();
541    ///
542    /// // This will panic during service provider build when Options<DatabaseConfig> is first resolved
543    /// let provider = services.build();
544    /// let _options = provider.get_required::<Options<DatabaseConfig>>(); // Panics here
545    /// ```
546    pub fn validate<F>(mut self, f: F) -> Self
547    where
548        F: Fn(&T) -> Result<(), String> + Send + Sync + 'static,
549    {
550        self.validates.push(Arc::new(f));
551        self
552    }
553
554    /// Finish building and register `Options<T>` as a singleton in the DI container.
555    ///
556    /// This method consumes the builder and registers:
557    /// - `Options<T>` as a singleton factory that builds the configured options
558    /// 
559    /// To access the configured options, resolve `Options<T>` and call `.get()` to get `Arc<T>`.
560    ///
561    /// The configuration process follows this order:
562    /// 1. Create initial value (default_with or T::default())
563    /// 2. Run all configure callbacks in order
564    /// 3. Run all post_configure callbacks in order  
565    /// 4. Run all validate callbacks - panic on any failure
566    /// 5. Wrap in `Options<T>` and register as singleton
567    ///
568    /// # Panics
569    ///
570    /// Panics if any validation callback returns an error. This implements fail-fast
571    /// behavior for configuration issues.
572    pub fn register(self) {
573        // Safety: we require the builder not to outlive &mut ServiceCollection.
574        let sc = unsafe { &mut *self.sc };
575
576        // Register Options<T> as singleton (factory)
577        let default_maker = self.default_maker.clone();
578        let configures = self.configures.clone();
579        let post_configures = self.post_configures.clone();
580        let validates = self.validates.clone();
581
582        sc.add_singleton_factory::<Options<T>, _>(move |resolver| {
583            // Build value
584            let mut value: T = if let Some(mk) = &default_maker {
585                (mk)()
586            } else {
587                T::default()
588            };
589
590            // Run configure steps
591            for c in &configures {
592                c(resolver, &mut value);
593            }
594            // Run post configure
595            for pc in &post_configures {
596                pc(resolver, &mut value);
597            }
598            // Validate
599            for v in &validates {
600                if let Err(msg) = v(&value) {
601                    // Fail-fast (aligned with panic-for-misconfig stance)
602                    panic!("Options<{}> validation failed: {}", std::any::type_name::<T>(), msg);
603                }
604            }
605
606            Options::new(value)
607        });
608
609        // Note: We don't register T directly as a singleton because that would require
610        // cloning the T value from the Arc<T> inside Options<T>. Instead, users should
611        // resolve Options<T> and call .get() to get the Arc<T>, which is more efficient.
612        // 
613        // If direct T access is needed, it could be added with a Clone bound:
614        // sc.add_singleton_factory::<T, _>(|resolver| {
615        //     let opts = resolver.get_required::<Options<T>>();
616        //     (*opts.get()).clone()  // requires T: Clone
617        // });
618    }
619}
620
621/// Extensions to ServiceCollection for the Options pattern.
622impl ServiceCollection {
623    /// Start building `Options<T>`. Call `.register()` to finalize.
624    ///
625    /// This method begins the configuration of strongly-typed options that will be
626    /// available through dependency injection. The options follow an immutable
627    /// snapshot model where configuration is resolved once during container setup.
628    ///
629    /// # Examples
630    ///
631    /// ```
632    /// use ferrous_di::{ServiceCollection, IOptions, Options, Resolver};
633    ///
634    /// #[derive(Default)]
635    /// struct MySettings {
636    ///     enabled: bool,
637    ///     timeout: u64,
638    /// }
639    ///
640    /// let mut services = ServiceCollection::new();
641    /// services.add_options::<MySettings>()
642    ///     .configure(|_resolver, settings| {
643    ///         settings.enabled = true;
644    ///         settings.timeout = 5000;
645    ///     })
646    ///     .register();
647    ///
648    /// let provider = services.build();
649    /// let options = provider.get_required::<Options<MySettings>>();
650    /// let settings = options.get();
651    /// assert!(settings.enabled);
652    /// assert_eq!(settings.timeout, 5000);
653    /// ```
654    pub fn add_options<T>(&mut self) -> OptionsBuilder<T>
655    where
656        T: Default + Send + Sync + 'static,
657    {
658        OptionsBuilder::new(self)
659    }
660}
661
662#[cfg(test)]
663mod tests {
664    use super::*;
665    use std::sync::Mutex;
666    // Note: catch_unwind and AssertUnwindSafe are used in with_circular_catch function
667    
668    #[test]
669    fn test_singleton_resolution() {
670        let mut sc = ServiceCollection::new();
671        sc.add_singleton(42usize);
672        
673        let sp = sc.build();
674        let a = sp.get_required::<usize>();
675        let b = sp.get_required::<usize>();
676        
677        assert_eq!(*a, 42);
678        assert!(Arc::ptr_eq(&a, &b)); // Same instance
679    }
680    
681    #[test]
682    fn test_transient_resolution() {
683        let mut sc = ServiceCollection::new();
684        let counter = Arc::new(Mutex::new(0));
685        let counter_clone = counter.clone();
686        
687        sc.add_transient_factory::<String, _>(move |_| {
688            let mut c = counter_clone.lock().unwrap();
689            *c += 1;
690            format!("instance-{}", *c)
691        });
692        
693        let sp = sc.build();
694        let a = sp.get_required::<String>();
695        let b = sp.get_required::<String>();
696        
697        assert_eq!(a.as_str(), "instance-1");
698        assert_eq!(b.as_str(), "instance-2");
699        assert!(!Arc::ptr_eq(&a, &b)); // Different instances
700    }
701    
702    #[test]
703    fn test_scoped_resolution() {
704        let mut sc = ServiceCollection::new();
705        let counter = Arc::new(Mutex::new(0));
706        let counter_clone = counter.clone();
707        
708        sc.add_scoped_factory::<String, _>(move |_| {
709            let mut c = counter_clone.lock().unwrap();
710            *c += 1;
711            format!("scoped-{}", *c)
712        });
713        
714        let sp = sc.build();
715        
716        // Same scope should have same instance
717        let scope1 = sp.create_scope();
718        let s1a = scope1.get_required::<String>();
719        let s1b = scope1.get_required::<String>();
720        assert!(Arc::ptr_eq(&s1a, &s1b));
721        
722        // Different scope should have different instance
723        let scope2 = sp.create_scope();
724        let s2 = scope2.get_required::<String>();
725        assert!(!Arc::ptr_eq(&s1a, &s2));
726    }
727    
728    #[test]
729    fn test_trait_resolution() {
730        trait TestTrait: Send + Sync {
731            fn get_value(&self) -> i32;
732        }
733        
734        struct TestImpl {
735            value: i32,
736        }
737        
738        impl TestTrait for TestImpl {
739            fn get_value(&self) -> i32 {
740                self.value
741            }
742        }
743        
744        let mut sc = ServiceCollection::new();
745        sc.add_singleton_trait::<dyn TestTrait>(Arc::new(TestImpl { value: 42 }));
746        
747        let sp = sc.build();
748        let service = sp.get_required_trait::<dyn TestTrait>();
749        assert_eq!(service.get_value(), 42);
750    }
751
752    #[test]
753    fn test_options_pattern() {
754        #[derive(Default)]
755        struct TestConfig {
756            value: i32,
757        }
758
759        let mut sc = ServiceCollection::new();
760        sc.add_options::<TestConfig>()
761            .configure(|_resolver, config| {
762                config.value = 42;
763            })
764            .register();
765
766        let sp = sc.build();
767        let options = sp.get_required::<Options<TestConfig>>();
768        let config = options.get();
769        assert_eq!(config.value, 42);
770    }
771}