1pub mod builder;
54pub mod container;
55pub mod descriptor;
56pub mod errors;
57pub mod lifetime;
58pub mod service_key;
59
60pub use builder::ContainerBuilder;
62pub use container::{Container, ServiceProvider, ServiceScope};
63pub use descriptor::{
64 ServiceDescriptor, ServiceFactory, ServiceProvider as ServiceProviderTrait, ServiceProviderExt,
65};
66pub use errors::{DiError, DiResult};
67pub use lifetime::Lifetime;
68pub use service_key::ServiceKey;
69
70pub fn container_builder() -> ContainerBuilder {
88 ContainerBuilder::new()
89}
90
91pub fn container() -> Container {
105 Container::new()
106}
107
108#[cfg(test)]
109mod integration_tests {
110 use super::*;
111 use crate::descriptor::ServiceProviderExt;
112 use std::sync::Arc;
113
114 trait ILogger: Send + Sync {
115 fn log(&self, message: &str);
116 fn get_logs(&self) -> Vec<String>;
117 }
118
119 struct ConsoleLogger {
120 logs: std::sync::Mutex<Vec<String>>,
121 }
122
123 impl ConsoleLogger {
124 fn new() -> Self {
125 Self {
126 logs: std::sync::Mutex::new(Vec::new()),
127 }
128 }
129 }
130
131 impl ILogger for ConsoleLogger {
132 fn log(&self, message: &str) {
133 if let Ok(mut logs) = self.logs.lock() {
134 logs.push(message.to_string());
135 }
136 }
137
138 fn get_logs(&self) -> Vec<String> {
139 self.logs.lock().unwrap().clone()
140 }
141 }
142
143 struct DatabaseService {
144 logger: Arc<ConsoleLogger>,
145 connection_string: Arc<String>,
146 }
147
148 impl DatabaseService {
149 fn connect(&self) -> String {
150 self.logger.log("Connecting to database");
151 format!("Connected to {}", self.connection_string)
152 }
153 }
154
155 struct UserService {
156 database: Arc<DatabaseService>,
157 logger: Arc<ConsoleLogger>,
158 }
159
160 impl UserService {
161 fn get_user(&self, id: i32) -> String {
162 self.logger.log(&format!("Getting user {id}"));
163 let _ = self.database.connect();
164 format!("User {id}")
165 }
166 }
167
168 #[test]
169 fn test_complete_dependency_injection_scenario() {
170 let provider = ContainerBuilder::new()
171 .add_instance("postgresql://localhost:5432/mydb".to_string())
173 .add_singleton_simple::<ConsoleLogger, ConsoleLogger>(ConsoleLogger::new)
175 .add_scoped_with_deps2::<DatabaseService, DatabaseService, ConsoleLogger, String>(
177 |logger, connection_string| DatabaseService {
178 logger,
179 connection_string,
180 },
181 )
182 .add_transient_with_deps2::<UserService, UserService, DatabaseService, ConsoleLogger>(
184 |database, logger| UserService { database, logger },
185 )
186 .build();
187
188 let mut scope = provider.create_scope().unwrap();
190
191 let user_service = scope.get_required_service::<UserService>().unwrap();
193 let result = user_service.get_user(123);
194
195 assert_eq!(result, "User 123");
196
197 let logger = scope.get_required_service::<ConsoleLogger>().unwrap();
199 let logs = logger.get_logs();
200 assert!(logs.contains(&"Getting user 123".to_string()));
201 assert!(logs.contains(&"Connecting to database".to_string()));
202
203 let db1 = scope.get_required_service::<DatabaseService>().unwrap();
205 let db2 = scope.get_required_service::<DatabaseService>().unwrap();
206 assert_eq!(db1.connection_string, db2.connection_string);
208
209 let user1 = scope.get_required_service::<UserService>().unwrap();
211 let user2 = scope.get_required_service::<UserService>().unwrap();
212 assert_eq!(
214 user1.database.connection_string,
215 user2.database.connection_string
216 );
217
218 scope.dispose();
219 }
220
221 #[test]
222 fn test_keyed_services_integration() {
223 let provider = ContainerBuilder::new()
224 .add_named_singleton_simple::<ConsoleLogger, ConsoleLogger>(
225 "console",
226 ConsoleLogger::new,
227 )
228 .add_named_singleton_simple::<ConsoleLogger, ConsoleLogger>("file", ConsoleLogger::new)
229 .add_named_instance("database_url", "postgresql://localhost/db".to_string())
230 .add_named_instance("cache_url", "redis://localhost/cache".to_string())
231 .build();
232
233 let console_logger = provider
235 .get_required_keyed_service::<ConsoleLogger>("console")
236 .unwrap();
237 let file_logger = provider
238 .get_required_keyed_service::<ConsoleLogger>("file")
239 .unwrap();
240
241 console_logger.log("Console message");
242 file_logger.log("File message");
243
244 assert_eq!(console_logger.get_logs(), vec!["Console message"]);
245 assert_eq!(file_logger.get_logs(), vec!["File message"]);
246
247 let db_url = provider
249 .get_required_keyed_service::<String>("database_url")
250 .unwrap();
251 let cache_url = provider
252 .get_required_keyed_service::<String>("cache_url")
253 .unwrap();
254
255 assert_eq!(*db_url, "postgresql://localhost/db");
256 assert_eq!(*cache_url, "redis://localhost/cache");
257 }
258
259 #[test]
260 fn test_singleton_across_scopes() {
261 let provider = ContainerBuilder::new()
262 .add_singleton_simple::<ConsoleLogger, ConsoleLogger>(ConsoleLogger::new)
263 .build();
264
265 let mut scope1 = provider.create_scope().unwrap();
266 let mut scope2 = provider.create_scope().unwrap();
267
268 let logger1 = scope1.get_required_service::<ConsoleLogger>().unwrap();
269 let logger2 = scope2.get_required_service::<ConsoleLogger>().unwrap();
270
271 logger1.log("test message");
273 assert_eq!(logger1.get_logs().len(), logger2.get_logs().len());
274
275 scope1.dispose();
276 scope2.dispose();
277 }
278
279 #[test]
280 fn test_error_handling() {
281 let provider = ContainerBuilder::new().build();
282
283 let result = provider.get_service::<String>();
285 assert!(result.is_ok());
286 assert!(result.unwrap().is_none());
287
288 let result = provider.get_required_service::<String>();
290 assert!(result.is_err());
291 assert!(matches!(
292 result.unwrap_err(),
293 DiError::ServiceNotRegistered { .. }
294 ));
295
296 let result = provider.get_keyed_service::<String>("nonexistent");
298 assert!(result.is_ok());
299 assert!(result.unwrap().is_none());
300
301 let result = provider.get_required_keyed_service::<String>("nonexistent");
302 assert!(result.is_err());
303 assert!(matches!(
304 result.unwrap_err(),
305 DiError::KeyedServiceNotRegistered { .. }
306 ));
307 }
308
309 #[test]
310 fn test_dyn_compatibility() {
311 let provider = ContainerBuilder::new()
312 .add_singleton_simple::<ConsoleLogger, ConsoleLogger>(ConsoleLogger::new)
313 .build();
314
315 let dyn_provider: &dyn ServiceProviderTrait = &provider;
317
318 let raw_result = dyn_provider.get_service_raw(&ServiceKey::of_type::<ConsoleLogger>());
320 assert!(raw_result.is_ok());
321 assert!(raw_result.unwrap().is_some());
322
323 let typed_result = provider.get_service::<ConsoleLogger>();
325 assert!(typed_result.is_ok());
326 assert!(typed_result.unwrap().is_some());
327 }
328
329 #[test]
330 fn test_trait_object_storage() {
331 let provider = ContainerBuilder::new()
332 .add_singleton_simple::<ConsoleLogger, ConsoleLogger>(ConsoleLogger::new)
333 .build();
334
335 let providers: Vec<&dyn ServiceProviderTrait> = vec![&provider, &provider];
337
338 for p in providers {
339 let result = p.get_service_raw(&ServiceKey::of_type::<ConsoleLogger>());
340 assert!(result.is_ok());
341 }
342 }
343}