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