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}