1use std::collections::HashMap;
7use crate::container::{IocContainer, ServiceConventions, ServiceLifetime};
8use crate::modules::CompileTimeModuleMetadata;
9use crate::errors::CoreError;
10
11#[derive(Debug, thiserror::Error)]
13pub enum ConfigError {
14 #[error("Provider configuration failed: {message}")]
15 ConfigurationFailed { message: String },
16
17 #[error("Dependency validation failed: {0}")]
18 DependencyError(#[from] DependencyError),
19
20 #[error("Container error during configuration: {0}")]
21 ContainerError(#[from] CoreError),
22}
23
24#[derive(Debug, thiserror::Error)]
26pub enum DependencyError {
27 #[error("Missing dependency '{dependency}' for service '{service}'")]
28 MissingDependency {
29 service: String,
30 dependency: String,
31 },
32
33 #[error("Circular dependency detected: {cycle}")]
34 CircularDependency { cycle: String },
35
36 #[error("Service lifetime conflict: {service} ({lifetime:?}) depends on {dependency} ({dependency_lifetime:?})")]
37 LifetimeConflict {
38 service: String,
39 lifetime: ServiceLifetime,
40 dependency: String,
41 dependency_lifetime: ServiceLifetime,
42 },
43}
44
45#[derive(Debug, Clone)]
47pub enum ProviderType {
48 Concrete(String),
50 Trait {
52 trait_name: String,
53 impl_name: String,
54 },
55 NamedTrait {
57 trait_name: String,
58 impl_name: String,
59 name: String,
60 },
61}
62
63#[derive(Debug, Clone)]
65pub struct ProviderMetadata {
66 pub service_type: ProviderType,
67 pub lifetime: Option<ServiceLifetime>,
68 pub dependencies: Vec<String>,
69}
70
71impl ProviderMetadata {
72 pub fn concrete(service_name: String) -> Self {
74 Self {
75 service_type: ProviderType::Concrete(service_name),
76 lifetime: None,
77 dependencies: Vec::new(),
78 }
79 }
80
81 pub fn trait_impl(trait_name: String, impl_name: String) -> Self {
83 Self {
84 service_type: ProviderType::Trait {
85 trait_name,
86 impl_name,
87 },
88 lifetime: None,
89 dependencies: Vec::new(),
90 }
91 }
92
93 pub fn named_trait(trait_name: String, impl_name: String, name: String) -> Self {
95 Self {
96 service_type: ProviderType::NamedTrait {
97 trait_name,
98 impl_name,
99 name,
100 },
101 lifetime: None,
102 dependencies: Vec::new(),
103 }
104 }
105
106 pub fn with_lifetime(mut self, lifetime: ServiceLifetime) -> Self {
108 self.lifetime = Some(lifetime);
109 self
110 }
111
112 pub fn with_dependency(mut self, dependency: String) -> Self {
114 self.dependencies.push(dependency);
115 self
116 }
117}
118
119pub struct ProviderConfigurator {
121 container: IocContainer,
122 conventions: ServiceConventions,
123 providers: Vec<ProviderMetadata>,
124}
125
126impl ProviderConfigurator {
127 pub fn new(container: IocContainer) -> Self {
129 Self {
130 container,
131 conventions: ServiceConventions::new(),
132 providers: Vec::new(),
133 }
134 }
135
136 pub fn with_conventions(container: IocContainer, conventions: ServiceConventions) -> Self {
138 Self {
139 container,
140 conventions,
141 providers: Vec::new(),
142 }
143 }
144
145 pub fn configure_from_modules(&mut self, modules: &[CompileTimeModuleMetadata]) -> Result<(), ConfigError> {
147 self.extract_providers_from_modules(modules)?;
149
150 self.apply_lifetime_conventions()?;
152
153 self.validate_dependencies()?;
155
156 self.register_providers()?;
158
159 Ok(())
160 }
161
162 fn extract_providers_from_modules(&mut self, modules: &[CompileTimeModuleMetadata]) -> Result<(), ConfigError> {
164 for module in modules {
165 for provider_str in &module.providers {
166 let provider = self.parse_provider_declaration(provider_str)?;
167 self.providers.push(provider);
168 }
169 }
170 Ok(())
171 }
172
173 fn parse_provider_declaration(&self, provider_str: &str) -> Result<ProviderMetadata, ConfigError> {
175 if let Some(arrow_pos) = provider_str.find(" => ") {
181 let trait_part = provider_str[..arrow_pos].trim();
183 let impl_part = provider_str[arrow_pos + 4..].trim();
184
185 let trait_name = if let Some(stripped) = trait_part.strip_prefix("dyn ") {
187 stripped.trim().to_string()
188 } else {
189 trait_part.to_string()
190 };
191
192 if let Some(at_pos) = impl_part.find(" @ ") {
194 let impl_name = impl_part[..at_pos].trim().to_string();
195 let name = impl_part[at_pos + 3..].trim().to_string();
196 Ok(ProviderMetadata::named_trait(trait_name, impl_name, name))
197 } else {
198 Ok(ProviderMetadata::trait_impl(trait_name, impl_part.to_string()))
199 }
200 } else {
201 Ok(ProviderMetadata::concrete(provider_str.trim().to_string()))
203 }
204 }
205
206 pub fn apply_lifetime_conventions(&mut self) -> Result<(), ConfigError> {
208 for provider in &mut self.providers {
209 if provider.lifetime.is_none() {
210 let service_name = match &provider.service_type {
211 ProviderType::Concrete(name) => name,
212 ProviderType::Trait { impl_name, .. } => impl_name,
213 ProviderType::NamedTrait { impl_name, .. } => impl_name,
214 };
215
216 let lifetime = self.conventions.get_lifetime_for_type(service_name);
217 provider.lifetime = Some(lifetime);
218 }
219 }
220 Ok(())
221 }
222
223 pub fn validate_dependencies(&self) -> Result<(), DependencyError> {
225 let mut services: HashMap<String, &ProviderMetadata> = HashMap::new();
227 let mut named_services: HashMap<String, &ProviderMetadata> = HashMap::new();
228
229 for provider in &self.providers {
231 match &provider.service_type {
232 ProviderType::Concrete(name) => {
233 services.insert(name.clone(), provider);
234 }
235 ProviderType::Trait { trait_name, .. } => {
236 services.insert(trait_name.clone(), provider);
237 }
238 ProviderType::NamedTrait { trait_name, name, .. } => {
239 let qualified_name = format!("{}@{}", trait_name, name);
241 services.insert(qualified_name.clone(), provider);
242 named_services.insert(name.clone(), provider);
243
244 if !services.contains_key(trait_name) {
246 services.insert(trait_name.clone(), provider);
247 }
248 }
249 }
250 }
251
252 for provider in &self.providers {
254 let service_key = self.get_service_key(&provider.service_type);
255
256 for dependency in &provider.dependencies {
257 let dep_provider = services.get(dependency)
262 .or_else(|| named_services.get(dependency))
263 .or_else(|| {
264 if dependency.contains('@') {
266 services.get(dependency)
267 } else {
268 None
269 }
270 });
271
272 if let Some(dep_provider) = dep_provider {
273 self.validate_lifetime_compatibility(provider, dep_provider, &service_key, dependency)?;
275 } else {
276 return Err(DependencyError::MissingDependency {
277 service: service_key,
278 dependency: dependency.clone(),
279 });
280 }
281 }
282 }
283
284 self.detect_circular_dependencies(&services)?;
286
287 Ok(())
288 }
289
290 fn get_service_key(&self, provider_type: &ProviderType) -> String {
292 match provider_type {
293 ProviderType::Concrete(name) => name.clone(),
294 ProviderType::Trait { trait_name, .. } => trait_name.clone(),
295 ProviderType::NamedTrait { trait_name, name, .. } => {
296 format!("{}@{}", trait_name, name)
297 }
298 }
299 }
300
301 fn validate_lifetime_compatibility(
303 &self,
304 service: &ProviderMetadata,
305 dependency: &ProviderMetadata,
306 service_name: &str,
307 dependency_name: &str,
308 ) -> Result<(), DependencyError> {
309 let service_lifetime = service.lifetime.unwrap_or(ServiceLifetime::Transient);
310 let dependency_lifetime = dependency.lifetime.unwrap_or(ServiceLifetime::Transient);
311
312 match (service_lifetime, dependency_lifetime) {
317 (ServiceLifetime::Scoped, ServiceLifetime::Transient) => {
318 Err(DependencyError::LifetimeConflict {
319 service: service_name.to_string(),
320 lifetime: service_lifetime,
321 dependency: dependency_name.to_string(),
322 dependency_lifetime,
323 })
324 }
325 _ => Ok(()),
326 }
327 }
328
329 fn detect_circular_dependencies(
331 &self,
332 services: &HashMap<String, &ProviderMetadata>,
333 ) -> Result<(), DependencyError> {
334 let mut visited = HashMap::new();
335 let mut rec_stack = HashMap::new();
336
337 for service_name in services.keys() {
338 if !visited.get(service_name).unwrap_or(&false) {
339 if let Some(cycle) = self.dfs_cycle_detection(
340 service_name,
341 services,
342 &mut visited,
343 &mut rec_stack,
344 Vec::new(),
345 )? {
346 return Err(DependencyError::CircularDependency {
347 cycle: cycle.join(" → "),
348 });
349 }
350 }
351 }
352
353 Ok(())
354 }
355
356 fn dfs_cycle_detection(
358 &self,
359 service: &str,
360 services: &HashMap<String, &ProviderMetadata>,
361 visited: &mut HashMap<String, bool>,
362 rec_stack: &mut HashMap<String, bool>,
363 mut path: Vec<String>,
364 ) -> Result<Option<Vec<String>>, DependencyError> {
365 visited.insert(service.to_string(), true);
366 rec_stack.insert(service.to_string(), true);
367 path.push(service.to_string());
368
369 if let Some(provider) = services.get(service) {
370 for dependency in &provider.dependencies {
371 if !visited.get(dependency).unwrap_or(&false) {
372 if let Some(cycle) = self.dfs_cycle_detection(
373 dependency,
374 services,
375 visited,
376 rec_stack,
377 path.clone(),
378 )? {
379 return Ok(Some(cycle));
380 }
381 } else if *rec_stack.get(dependency).unwrap_or(&false) {
382 let cycle_start = path.iter().position(|s| s == dependency).expect("Dependency in recursion stack must be in path");
384 let mut cycle = path[cycle_start..].to_vec();
385 cycle.push(dependency.to_string());
386 return Ok(Some(cycle));
387 }
388 }
389 }
390
391 rec_stack.insert(service.to_string(), false);
392 Ok(None)
393 }
394
395 fn register_providers(&mut self) -> Result<(), ConfigError> {
397 let providers = self.providers.clone();
399
400 for provider in &providers {
401 let lifetime = provider.lifetime.unwrap_or(ServiceLifetime::Transient);
402
403 match &provider.service_type {
404 ProviderType::Concrete(service_type) => {
405 self.register_concrete_service(service_type, lifetime)?;
409 }
410 ProviderType::Trait { trait_name, impl_name } => {
411 self.register_trait_service(trait_name, impl_name, lifetime)?;
412 }
413 ProviderType::NamedTrait { trait_name, impl_name, name } => {
414 self.register_named_trait_service(trait_name, impl_name, name, lifetime)?;
415 }
416 }
417 }
418
419 Ok(())
420 }
421
422 fn register_concrete_service(&mut self, _service_type: &str, _lifetime: ServiceLifetime) -> Result<(), ConfigError> {
424 Ok(())
428 }
429
430 fn register_trait_service(
432 &mut self,
433 _trait_type: &str,
434 _impl_type: &str,
435 _lifetime: ServiceLifetime,
436 ) -> Result<(), ConfigError> {
437 Ok(())
441 }
442
443 fn register_named_trait_service(
445 &mut self,
446 _trait_name: &str,
447 _impl_name: &str,
448 _name: &str,
449 _lifetime: ServiceLifetime,
450 ) -> Result<(), ConfigError> {
451 Ok(())
455 }
456
457 pub fn into_container(self) -> IocContainer {
459 self.container
460 }
461
462 pub fn container(&self) -> &IocContainer {
464 &self.container
465 }
466
467 pub fn container_mut(&mut self) -> &mut IocContainer {
469 &mut self.container
470 }
471
472 pub fn providers(&self) -> &[ProviderMetadata] {
474 &self.providers
475 }
476}
477
478#[cfg(test)]
479mod tests {
480 use super::*;
481 use crate::modules::CompileTimeModuleMetadata;
482
483 #[test]
484 fn test_parse_concrete_provider() {
485 let configurator = ProviderConfigurator::new(IocContainer::new());
486 let provider = configurator.parse_provider_declaration("UserService").unwrap();
487
488 match provider.service_type {
489 ProviderType::Concrete(name) => assert_eq!(name, "UserService"),
490 _ => panic!("Expected concrete provider"),
491 }
492 }
493
494 #[test]
495 fn test_parse_trait_provider() {
496 let configurator = ProviderConfigurator::new(IocContainer::new());
497 let provider = configurator.parse_provider_declaration("dyn EmailService => SmtpEmailService").unwrap();
498
499 match provider.service_type {
500 ProviderType::Trait { trait_name, impl_name } => {
501 assert_eq!(trait_name, "EmailService");
502 assert_eq!(impl_name, "SmtpEmailService");
503 }
504 _ => panic!("Expected trait provider"),
505 }
506 }
507
508 #[test]
509 fn test_parse_named_trait_provider() {
510 let configurator = ProviderConfigurator::new(IocContainer::new());
511 let provider = configurator.parse_provider_declaration("dyn EmailService => SmtpEmailService @ smtp").unwrap();
512
513 match provider.service_type {
514 ProviderType::NamedTrait { trait_name, impl_name, name } => {
515 assert_eq!(trait_name, "EmailService");
516 assert_eq!(impl_name, "SmtpEmailService");
517 assert_eq!(name, "smtp");
518 }
519 _ => panic!("Expected named trait provider"),
520 }
521 }
522
523 #[test]
524 fn test_lifetime_conventions_applied() {
525 let mut configurator = ProviderConfigurator::new(IocContainer::new());
526
527 let module = CompileTimeModuleMetadata::new("TestModule".to_string())
529 .with_providers(vec![
530 "UserService".to_string(),
531 "UserRepository".to_string(),
532 "PaymentFactory".to_string(),
533 ]);
534
535 configurator.configure_from_modules(&[module]).unwrap();
536
537 assert_eq!(configurator.providers.len(), 3);
539
540 for provider in &configurator.providers {
541 match &provider.service_type {
542 ProviderType::Concrete(name) => {
543 let expected_lifetime = if name.ends_with("Service") {
544 ServiceLifetime::Singleton
545 } else if name.ends_with("Repository") {
546 ServiceLifetime::Scoped
547 } else if name.ends_with("Factory") {
548 ServiceLifetime::Transient
549 } else {
550 ServiceLifetime::Transient
551 };
552 assert_eq!(provider.lifetime, Some(expected_lifetime));
553 }
554 ProviderType::Trait { impl_name, .. } => {
555 let expected_lifetime = if impl_name.ends_with("Service") {
556 ServiceLifetime::Singleton
557 } else if impl_name.ends_with("Repository") {
558 ServiceLifetime::Scoped
559 } else if impl_name.ends_with("Factory") {
560 ServiceLifetime::Transient
561 } else {
562 ServiceLifetime::Transient
563 };
564 assert_eq!(provider.lifetime, Some(expected_lifetime));
565 }
566 ProviderType::NamedTrait { impl_name, .. } => {
567 let expected_lifetime = if impl_name.ends_with("Service") {
568 ServiceLifetime::Singleton
569 } else if impl_name.ends_with("Repository") {
570 ServiceLifetime::Scoped
571 } else if impl_name.ends_with("Factory") {
572 ServiceLifetime::Transient
573 } else {
574 ServiceLifetime::Transient
575 };
576 assert_eq!(provider.lifetime, Some(expected_lifetime));
577 }
578 }
579 }
580 }
581
582 #[test]
583 fn test_missing_dependency_detection() {
584 let mut configurator = ProviderConfigurator::new(IocContainer::new());
585
586 configurator.providers.push(
588 ProviderMetadata::concrete("UserController".to_string())
589 .with_dependency("UserService".to_string())
590 .with_dependency("MissingService".to_string()),
591 );
592
593 configurator.providers.push(
594 ProviderMetadata::concrete("UserService".to_string()),
595 );
596
597 let result = configurator.validate_dependencies();
598 assert!(result.is_err());
599
600 match result.unwrap_err() {
601 DependencyError::MissingDependency { service, dependency } => {
602 assert_eq!(service, "UserController");
603 assert_eq!(dependency, "MissingService");
604 }
605 _ => panic!("Expected MissingDependency error"),
606 }
607 }
608
609 #[test]
610 fn test_lifetime_conflict_detection() {
611 let mut configurator = ProviderConfigurator::new(IocContainer::new());
612
613 configurator.providers.push(
615 ProviderMetadata::concrete("ScopedService".to_string())
616 .with_lifetime(ServiceLifetime::Scoped)
617 .with_dependency("TransientService".to_string()),
618 );
619
620 configurator.providers.push(
621 ProviderMetadata::concrete("TransientService".to_string())
622 .with_lifetime(ServiceLifetime::Transient),
623 );
624
625 let result = configurator.validate_dependencies();
626 assert!(result.is_err());
627
628 match result.unwrap_err() {
629 DependencyError::LifetimeConflict { service, dependency, .. } => {
630 assert_eq!(service, "ScopedService");
631 assert_eq!(dependency, "TransientService");
632 }
633 _ => panic!("Expected LifetimeConflict error"),
634 }
635 }
636
637 #[test]
638 fn test_circular_dependency_detection() {
639 let mut configurator = ProviderConfigurator::new(IocContainer::new());
640
641 configurator.providers.push(
643 ProviderMetadata::concrete("ServiceA".to_string())
644 .with_dependency("ServiceB".to_string()),
645 );
646
647 configurator.providers.push(
648 ProviderMetadata::concrete("ServiceB".to_string())
649 .with_dependency("ServiceC".to_string()),
650 );
651
652 configurator.providers.push(
653 ProviderMetadata::concrete("ServiceC".to_string())
654 .with_dependency("ServiceA".to_string()),
655 );
656
657 let result = configurator.validate_dependencies();
658 assert!(result.is_err());
659
660 match result.unwrap_err() {
661 DependencyError::CircularDependency { cycle } => {
662 assert!(cycle.contains("ServiceA"));
663 assert!(cycle.contains("ServiceB"));
664 assert!(cycle.contains("ServiceC"));
665 }
666 _ => panic!("Expected CircularDependency error"),
667 }
668 }
669
670 #[test]
671 fn test_named_trait_dependency_resolution() {
672 let mut configurator = ProviderConfigurator::new(IocContainer::new());
673
674 configurator.providers.push(
676 ProviderMetadata::named_trait(
677 "EmailService".to_string(),
678 "SmtpEmailService".to_string(),
679 "smtp".to_string()
680 )
681 );
682
683 configurator.providers.push(
685 ProviderMetadata::concrete("UserController".to_string())
686 .with_dependency("EmailService@smtp".to_string()),
687 );
688
689 configurator.providers.push(
691 ProviderMetadata::concrete("NotificationService".to_string())
692 .with_dependency("EmailService".to_string()),
693 );
694
695 let result = configurator.validate_dependencies();
696 assert!(result.is_ok(), "Dependency validation should succeed for named traits");
697 }
698
699 #[test]
700 fn test_multiple_named_trait_implementations() {
701 let mut configurator = ProviderConfigurator::new(IocContainer::new());
702
703 configurator.providers.push(
705 ProviderMetadata::named_trait(
706 "EmailService".to_string(),
707 "SmtpEmailService".to_string(),
708 "smtp".to_string()
709 )
710 );
711
712 configurator.providers.push(
713 ProviderMetadata::named_trait(
714 "EmailService".to_string(),
715 "SendGridEmailService".to_string(),
716 "sendgrid".to_string()
717 )
718 );
719
720 configurator.providers.push(
722 ProviderMetadata::concrete("UserController".to_string())
723 .with_dependency("EmailService@smtp".to_string()),
724 );
725
726 configurator.providers.push(
728 ProviderMetadata::concrete("AdminController".to_string())
729 .with_dependency("EmailService@sendgrid".to_string()),
730 );
731
732 let result = configurator.validate_dependencies();
733 assert!(result.is_ok(), "Multiple named trait implementations should resolve correctly");
734 }
735
736 #[test]
737 fn test_service_key_generation() {
738 let configurator = ProviderConfigurator::new(IocContainer::new());
739
740 let concrete_key = configurator.get_service_key(&ProviderType::Concrete("UserService".to_string()));
742 assert_eq!(concrete_key, "UserService");
743
744 let trait_key = configurator.get_service_key(&ProviderType::Trait {
746 trait_name: "EmailService".to_string(),
747 impl_name: "SmtpEmailService".to_string(),
748 });
749 assert_eq!(trait_key, "EmailService");
750
751 let named_key = configurator.get_service_key(&ProviderType::NamedTrait {
753 trait_name: "EmailService".to_string(),
754 impl_name: "SmtpEmailService".to_string(),
755 name: "smtp".to_string(),
756 });
757 assert_eq!(named_key, "EmailService@smtp");
758 }
759}