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, ScopedContainer, ScopePool,
145 };
146 pub use std::sync::Arc;
147
148 // Compile-time safety types
149 pub use crate::typed::{TypedBuilder, TypedContainer, Has, HasType, HasService, DeclaresDeps, Reg, DepsPresent};
150 pub use crate::verified::{Service, ServiceProvider, ServiceModule, Resolvable};
151
152 #[cfg(feature = "derive")]
153 pub use crate::{Inject, Service as ServiceDerive, TypedRequire};
154}
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159 use std::sync::atomic::{AtomicU32, Ordering};
160
161 #[derive(Clone)]
162 struct Database {
163 url: String,
164 }
165
166 #[allow(dead_code)]
167 #[derive(Clone)]
168 struct UserService {
169 name: String,
170 }
171
172 #[test]
173 fn test_singleton_registration() {
174 let container = Container::new();
175 container.singleton(Database { url: "test".into() });
176
177 let db = container.get::<Database>().unwrap();
178 assert_eq!(db.url, "test");
179 }
180
181 #[test]
182 fn test_multiple_resolve_same_instance() {
183 let container = Container::new();
184 container.singleton(Database { url: "test".into() });
185
186 let db1 = container.get::<Database>().unwrap();
187 let db2 = container.get::<Database>().unwrap();
188
189 // Same Arc instance
190 assert!(Arc::ptr_eq(&db1, &db2));
191 }
192
193 #[test]
194 fn test_transient_creates_new_instance() {
195 static COUNTER: AtomicU32 = AtomicU32::new(0);
196
197 #[derive(Clone)]
198 struct Counter(u32);
199
200 let container = Container::new();
201 container.transient(|| Counter(COUNTER.fetch_add(1, Ordering::SeqCst)));
202
203 let c1 = container.get::<Counter>().unwrap();
204 let c2 = container.get::<Counter>().unwrap();
205
206 assert_ne!(c1.0, c2.0);
207 }
208
209 #[test]
210 fn test_lazy_singleton() {
211 static CREATED: AtomicU32 = AtomicU32::new(0);
212
213 #[derive(Clone)]
214 struct LazyService;
215
216 let container = Container::new();
217 container.lazy(|| {
218 CREATED.fetch_add(1, Ordering::SeqCst);
219 LazyService
220 });
221
222 assert_eq!(CREATED.load(Ordering::SeqCst), 0);
223
224 let _ = container.get::<LazyService>().unwrap();
225 assert_eq!(CREATED.load(Ordering::SeqCst), 1);
226
227 // Second resolve doesn't create new instance
228 let _ = container.get::<LazyService>().unwrap();
229 assert_eq!(CREATED.load(Ordering::SeqCst), 1);
230 }
231
232 #[test]
233 fn test_scoped_container() {
234 let root = Container::new();
235 root.singleton(Database { url: "root".into() });
236
237 let child = root.scope();
238 child.singleton(UserService {
239 name: "child".into(),
240 });
241
242 // Child can access root services
243 assert!(child.contains::<Database>());
244 assert!(child.contains::<UserService>());
245
246 // Root cannot access child services
247 assert!(root.contains::<Database>());
248 assert!(!root.contains::<UserService>());
249 }
250
251 #[test]
252 fn test_not_found_error() {
253 let container = Container::new();
254 let result = container.get::<Database>();
255 assert!(result.is_err());
256 }
257
258 #[test]
259 fn test_override_in_scope() {
260 let root = Container::new();
261 root.singleton(Database {
262 url: "production".into(),
263 });
264
265 let test_scope = root.scope();
266 test_scope.singleton(Database { url: "test".into() });
267
268 // Root has production
269 let root_db = root.get::<Database>().unwrap();
270 assert_eq!(root_db.url, "production");
271
272 // Child has test override
273 let child_db = test_scope.get::<Database>().unwrap();
274 assert_eq!(child_db.url, "test");
275 }
276}