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}