elif_core/bootstrap/
providers.rs

1//! Provider Auto-Configuration System
2//!
3//! This module implements automatic provider registration from module declarations
4//! with proper lifetime management and dependency injection.
5
6use std::collections::HashMap;
7use crate::container::{IocContainer, ServiceConventions, ServiceLifetime};
8use crate::modules::CompileTimeModuleMetadata;
9use crate::errors::CoreError;
10
11/// Configuration error types for provider auto-configuration
12#[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/// Dependency resolution error types
25#[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/// Provider type enumeration for different binding strategies
46#[derive(Debug, Clone)]
47pub enum ProviderType {
48    /// Concrete service implementation
49    Concrete(String),
50    /// Trait/interface implementation
51    Trait {
52        trait_name: String,
53        impl_name: String,
54    },
55    /// Named trait implementation with explicit naming
56    NamedTrait {
57        trait_name: String,
58        impl_name: String,
59        name: String,
60    },
61}
62
63/// Provider metadata extracted from module declarations
64#[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    /// Create new provider metadata for concrete service
73    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    /// Create new provider metadata for trait implementation
82    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    /// Create new provider metadata for named trait implementation
94    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    /// Set the service lifetime
107    pub fn with_lifetime(mut self, lifetime: ServiceLifetime) -> Self {
108        self.lifetime = Some(lifetime);
109        self
110    }
111    
112    /// Add a dependency
113    pub fn with_dependency(mut self, dependency: String) -> Self {
114        self.dependencies.push(dependency);
115        self
116    }
117}
118
119/// Provider configuration engine for automatic DI container setup
120pub struct ProviderConfigurator {
121    container: IocContainer,
122    conventions: ServiceConventions,
123    providers: Vec<ProviderMetadata>,
124}
125
126impl ProviderConfigurator {
127    /// Create a new provider configurator
128    pub fn new(container: IocContainer) -> Self {
129        Self {
130            container,
131            conventions: ServiceConventions::new(),
132            providers: Vec::new(),
133        }
134    }
135    
136    /// Create a new provider configurator with custom conventions
137    pub fn with_conventions(container: IocContainer, conventions: ServiceConventions) -> Self {
138        Self {
139            container,
140            conventions,
141            providers: Vec::new(),
142        }
143    }
144    
145    /// Configure providers from module metadata
146    pub fn configure_from_modules(&mut self, modules: &[CompileTimeModuleMetadata]) -> Result<(), ConfigError> {
147        // Extract all providers from modules
148        self.extract_providers_from_modules(modules)?;
149        
150        // Apply lifetime conventions
151        self.apply_lifetime_conventions()?;
152        
153        // Validate dependencies
154        self.validate_dependencies()?;
155        
156        // Register providers in container
157        self.register_providers()?;
158        
159        Ok(())
160    }
161    
162    /// Extract providers from module metadata
163    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    /// Parse provider declaration string into ProviderMetadata
174    fn parse_provider_declaration(&self, provider_str: &str) -> Result<ProviderMetadata, ConfigError> {
175        // Handle different provider declaration patterns:
176        // - "UserService" -> Concrete service
177        // - "dyn EmailService => SmtpEmailService" -> Trait mapping
178        // - "dyn EmailService => SmtpEmailService @ smtp" -> Named trait service
179        
180        if let Some(arrow_pos) = provider_str.find(" => ") {
181            // Trait mapping or named trait service
182            let trait_part = provider_str[..arrow_pos].trim();
183            let impl_part = provider_str[arrow_pos + 4..].trim();
184            
185            // Remove "dyn " prefix if present
186            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            // Check for named trait service (@ symbol)
193            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            // Concrete service
202            Ok(ProviderMetadata::concrete(provider_str.trim().to_string()))
203        }
204    }
205    
206    /// Apply lifetime conventions to providers
207    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    /// Validate all dependencies can be resolved
224    pub fn validate_dependencies(&self) -> Result<(), DependencyError> {
225        // Build service registry with proper keys for different provider types
226        let mut services: HashMap<String, &ProviderMetadata> = HashMap::new();
227        let mut named_services: HashMap<String, &ProviderMetadata> = HashMap::new();
228        
229        // Register services by their resolution keys
230        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                    // Named services can be resolved both by trait name and by qualified name
240                    let qualified_name = format!("{}@{}", trait_name, name);
241                    services.insert(qualified_name.clone(), provider);
242                    named_services.insert(name.clone(), provider);
243                    
244                    // Also make available by trait name if no other implementation exists
245                    if !services.contains_key(trait_name) {
246                        services.insert(trait_name.clone(), provider);
247                    }
248                }
249            }
250        }
251        
252        // Check each service's dependencies
253        for provider in &self.providers {
254            let service_key = self.get_service_key(&provider.service_type);
255            
256            for dependency in &provider.dependencies {
257                // Try to resolve dependency in multiple ways:
258                // 1. Direct service name
259                // 2. Named service lookup
260                // 3. Qualified name lookup
261                let dep_provider = services.get(dependency)
262                    .or_else(|| named_services.get(dependency))
263                    .or_else(|| {
264                        // Try to find by qualified name pattern (trait@name)
265                        if dependency.contains('@') {
266                            services.get(dependency)
267                        } else {
268                            None
269                        }
270                    });
271                
272                if let Some(dep_provider) = dep_provider {
273                    // Check lifetime compatibility
274                    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        // Check for circular dependencies
285        self.detect_circular_dependencies(&services)?;
286        
287        Ok(())
288    }
289    
290    /// Get the service key for dependency resolution
291    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    /// Validate service lifetime compatibility
302    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        // Check lifetime rules:
313        // - Singleton can depend on any lifetime
314        // - Scoped cannot depend on Transient
315        // - Transient can depend on any lifetime
316        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    /// Detect circular dependencies using depth-first search
330    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    /// Depth-first search for cycle detection
357    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                    // Found cycle
383                    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    /// Register all providers in the DI container
396    fn register_providers(&mut self) -> Result<(), ConfigError> {
397        // Clone providers to avoid borrow checker issues
398        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                    // For now, we'll use placeholder registration
406                    // In a real implementation, this would use reflection or codegen
407                    // to actually register the concrete types
408                    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    /// Register a concrete service (placeholder implementation)
423    fn register_concrete_service(&mut self, _service_type: &str, _lifetime: ServiceLifetime) -> Result<(), ConfigError> {
424        // TODO: Implement actual service registration using codegen or reflection
425        // For now, this is a placeholder that would be filled in with actual
426        // container.bind::<ServiceType, ServiceType>() calls
427        Ok(())
428    }
429    
430    /// Register a trait service (placeholder implementation)
431    fn register_trait_service(
432        &mut self,
433        _trait_type: &str,
434        _impl_type: &str,
435        _lifetime: ServiceLifetime,
436    ) -> Result<(), ConfigError> {
437        // TODO: Implement actual trait registration using codegen or reflection
438        // For now, this is a placeholder that would be filled in with actual
439        // container.bind_trait::<TraitType, ImplType>() calls
440        Ok(())
441    }
442    
443    /// Register a named trait service (placeholder implementation)
444    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        // TODO: Implement actual named trait service registration using codegen or reflection
452        // For now, this is a placeholder that would be filled in with actual
453        // container.bind_named_trait::<TraitType, ImplType>(name) calls
454        Ok(())
455    }
456    
457    /// Get the configured container
458    pub fn into_container(self) -> IocContainer {
459        self.container
460    }
461    
462    /// Get reference to the container
463    pub fn container(&self) -> &IocContainer {
464        &self.container
465    }
466    
467    /// Get mutable reference to the container
468    pub fn container_mut(&mut self) -> &mut IocContainer {
469        &mut self.container
470    }
471    
472    /// Get reference to the providers list
473    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        // Create test module with providers
528        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        // Check that lifetime conventions were applied
538        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        // Add provider with dependencies
587        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        // Scoped service depending on Transient service (should fail)
614        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        // Create circular dependency: A -> B -> C -> A
642        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        // Create a named trait provider and a service that depends on it
675        configurator.providers.push(
676            ProviderMetadata::named_trait(
677                "EmailService".to_string(),
678                "SmtpEmailService".to_string(),
679                "smtp".to_string()
680            )
681        );
682        
683        // UserController depends on EmailService@smtp (qualified name)
684        configurator.providers.push(
685            ProviderMetadata::concrete("UserController".to_string())
686                .with_dependency("EmailService@smtp".to_string()),
687        );
688        
689        // NotificationService depends on EmailService (should resolve to the named trait)
690        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        // Create multiple named implementations of the same trait
704        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        // Controller depending on specific named implementation
721        configurator.providers.push(
722            ProviderMetadata::concrete("UserController".to_string())
723                .with_dependency("EmailService@smtp".to_string()),
724        );
725        
726        // Another controller depending on different implementation
727        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        // Test concrete service key
741        let concrete_key = configurator.get_service_key(&ProviderType::Concrete("UserService".to_string()));
742        assert_eq!(concrete_key, "UserService");
743        
744        // Test trait service key
745        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        // Test named trait service key
752        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}