Skip to main content

dependency_injector/
lib.rs

1//! # Armature DI - High-Performance Dependency Injection for Rust
2//!
3//! A lightning-fast, type-safe dependency injection container optimized for
4//! real-world web framework usage.
5//!
6//! ## Features
7//!
8//! - โšก **Lock-free** - Uses `DashMap` for concurrent access without blocking
9//! - ๐Ÿ”’ **Type-safe** - Compile-time type checking with zero runtime overhead
10//! - ๐Ÿš€ **Zero-config** - Any `Send + Sync + 'static` type is automatically injectable
11//! - ๐Ÿ”„ **Scoped containers** - Hierarchical scopes with full parent chain resolution
12//! - ๐Ÿญ **Lazy singletons** - Services created on first access
13//! - โ™ป๏ธ **Transient services** - Fresh instance on every resolve
14//! - ๐Ÿงต **Thread-local cache** - Hot path optimization for frequently accessed services
15//! - ๐Ÿ“Š **Observable** - Optional tracing integration with JSON or pretty output
16//! - โณ **Async singletons** - Optional `async` feature adds `lazy_async`/`get_async` (tokio)
17//!
18//! ## Quick Start
19//!
20//! ```rust
21//! use dependency_injector::Container;
22//!
23//! // Any Send + Sync + 'static type works - no boilerplate!
24//! #[derive(Clone)]
25//! struct Database {
26//!     url: String,
27//! }
28//!
29//! #[derive(Clone)]
30//! struct UserService {
31//!     db: Database,
32//! }
33//!
34//! let container = Container::new();
35//!
36//! // Register services
37//! container.singleton(Database { url: "postgres://localhost".into() });
38//! container.singleton(UserService {
39//!     db: Database { url: "postgres://localhost".into() }
40//! });
41//!
42//! // Resolve - returns Arc<T> for zero-copy sharing
43//! let db = container.get::<Database>().unwrap();
44//! let users = container.get::<UserService>().unwrap();
45//! ```
46//!
47//! ## Service Lifetimes
48//!
49//! ```rust
50//! use dependency_injector::Container;
51//! use std::sync::atomic::{AtomicU64, Ordering};
52//!
53//! static COUNTER: AtomicU64 = AtomicU64::new(0);
54//!
55//! #[derive(Clone, Default)]
56//! struct Config { debug: bool }
57//!
58//! #[derive(Clone)]
59//! struct RequestId(u64);
60//!
61//! let container = Container::new();
62//!
63//! // Singleton - one instance, shared everywhere
64//! container.singleton(Config { debug: true });
65//!
66//! // Lazy singleton - created on first access
67//! container.lazy(|| Config { debug: false });
68//!
69//! // Transient - new instance every time
70//! container.transient(|| RequestId(COUNTER.fetch_add(1, Ordering::SeqCst)));
71//! ```
72//!
73//! ## Scoped Containers
74//!
75//! ```rust
76//! use dependency_injector::Container;
77//!
78//! #[derive(Clone)]
79//! struct AppConfig { name: String }
80//!
81//! #[derive(Clone)]
82//! struct RequestContext { id: String }
83//!
84//! // Root container with app-wide services
85//! let root = Container::new();
86//! root.singleton(AppConfig { name: "MyApp".into() });
87//!
88//! // Per-request scope - inherits from root
89//! let request_scope = root.scope();
90//! request_scope.singleton(RequestContext { id: "req-123".into() });
91//!
92//! // Request scope can access root services
93//! assert!(request_scope.contains::<AppConfig>());
94//! assert!(request_scope.contains::<RequestContext>());
95//!
96//! // Root cannot access request-scoped services
97//! assert!(!root.contains::<RequestContext>());
98//! ```
99//!
100//! ## Performance
101//!
102//! - **Lock-free reads**: Using `DashMap` for ~10x faster concurrent access vs `RwLock`
103//! - **AHash**: Faster hashing for `TypeId` keys
104//! - **Thread-local cache**: Avoid map lookups for hot services
105//! - **Zero allocation resolve**: Returns `Arc<T>` directly, no cloning
106
107#[cfg(feature = "async")]
108pub mod async_support;
109mod container;
110mod error;
111mod factory;
112#[cfg(feature = "ffi")]
113pub mod ffi;
114#[cfg(feature = "logging")]
115pub mod logging;
116mod provider;
117mod scope;
118mod storage;
119pub mod typed;
120pub mod verified;
121
122// Re-export FrozenStorage when perfect-hash feature is enabled
123#[cfg(feature = "perfect-hash")]
124pub use storage::FrozenStorage;
125
126pub use container::*;
127pub use error::*;
128pub use factory::*;
129pub use provider::*;
130pub use scope::*;
131
132// Re-export tracing macros for convenience when logging feature is enabled
133#[cfg(feature = "logging")]
134pub use tracing::{debug, error, info, trace, warn};
135
136// Re-export derive macros when feature is enabled
137#[cfg(feature = "derive")]
138pub use dependency_injector_derive::{Inject, Service, TypedRequire};
139
140// Re-export for convenience
141pub use std::sync::Arc;
142
143/// Prelude for convenient imports
144pub mod prelude {
145    pub use crate::{
146        BatchBuilder, BatchRegistrar, Container, DiError, Factory, Injectable, Lifetime,
147        PooledScope, Provider, Result, Scope, ScopePool, ScopedContainer,
148    };
149    pub use std::sync::Arc;
150
151    // Compile-time safety types
152    pub use crate::typed::{
153        DeclaresDeps, DepsPresent, Has, HasService, HasType, Reg, TypedBuilder, TypedContainer,
154    };
155    pub use crate::verified::{Resolvable, Service, ServiceModule, ServiceProvider};
156
157    #[cfg(feature = "async")]
158    pub use crate::async_support::AsyncLazy;
159
160    #[cfg(feature = "derive")]
161    pub use crate::{Inject, Service as ServiceDerive, TypedRequire};
162}
163
164#[cfg(test)]
165mod tests {
166    use super::*;
167    use std::sync::atomic::{AtomicU32, Ordering};
168
169    #[derive(Clone)]
170    struct Database {
171        url: String,
172    }
173
174    #[allow(dead_code)]
175    #[derive(Clone)]
176    struct UserService {
177        name: String,
178    }
179
180    #[test]
181    fn test_singleton_registration() {
182        let container = Container::new();
183        container.singleton(Database { url: "test".into() });
184
185        let db = container.get::<Database>().unwrap();
186        assert_eq!(db.url, "test");
187    }
188
189    #[test]
190    fn test_multiple_resolve_same_instance() {
191        let container = Container::new();
192        container.singleton(Database { url: "test".into() });
193
194        let db1 = container.get::<Database>().unwrap();
195        let db2 = container.get::<Database>().unwrap();
196
197        // Same Arc instance
198        assert!(Arc::ptr_eq(&db1, &db2));
199    }
200
201    #[test]
202    fn test_transient_creates_new_instance() {
203        static COUNTER: AtomicU32 = AtomicU32::new(0);
204
205        #[derive(Clone)]
206        struct Counter(u32);
207
208        let container = Container::new();
209        container.transient(|| Counter(COUNTER.fetch_add(1, Ordering::SeqCst)));
210
211        let c1 = container.get::<Counter>().unwrap();
212        let c2 = container.get::<Counter>().unwrap();
213
214        assert_ne!(c1.0, c2.0);
215    }
216
217    #[test]
218    fn test_lazy_singleton() {
219        static CREATED: AtomicU32 = AtomicU32::new(0);
220
221        #[derive(Clone)]
222        struct LazyService;
223
224        let container = Container::new();
225        container.lazy(|| {
226            CREATED.fetch_add(1, Ordering::SeqCst);
227            LazyService
228        });
229
230        assert_eq!(CREATED.load(Ordering::SeqCst), 0);
231
232        let _ = container.get::<LazyService>().unwrap();
233        assert_eq!(CREATED.load(Ordering::SeqCst), 1);
234
235        // Second resolve doesn't create new instance
236        let _ = container.get::<LazyService>().unwrap();
237        assert_eq!(CREATED.load(Ordering::SeqCst), 1);
238    }
239
240    #[test]
241    fn test_scoped_container() {
242        let root = Container::new();
243        root.singleton(Database { url: "root".into() });
244
245        let child = root.scope();
246        child.singleton(UserService {
247            name: "child".into(),
248        });
249
250        // Child can access root services
251        assert!(child.contains::<Database>());
252        assert!(child.contains::<UserService>());
253
254        // Root cannot access child services
255        assert!(root.contains::<Database>());
256        assert!(!root.contains::<UserService>());
257    }
258
259    #[test]
260    fn test_not_found_error() {
261        let container = Container::new();
262        let result = container.get::<Database>();
263        assert!(result.is_err());
264    }
265
266    #[test]
267    fn test_override_in_scope() {
268        let root = Container::new();
269        root.singleton(Database {
270            url: "production".into(),
271        });
272
273        let test_scope = root.scope();
274        test_scope.singleton(Database { url: "test".into() });
275
276        // Root has production
277        let root_db = root.get::<Database>().unwrap();
278        assert_eq!(root_db.url, "production");
279
280        // Child has test override
281        let child_db = test_scope.get::<Database>().unwrap();
282        assert_eq!(child_db.url, "test");
283    }
284}