elif_core/container/
validation.rs

1use std::collections::{HashMap, HashSet};
2
3use crate::container::descriptor::{ServiceDescriptor, ServiceId};
4use crate::container::scope::ServiceScope;
5use crate::errors::CoreError;
6
7/// Compile-time validation error types
8#[derive(Debug, Clone, PartialEq)]
9pub enum ValidationError {
10    /// Service registration is missing
11    MissingRegistration {
12        service_type: String,
13        required_by: String,
14    },
15    /// Circular dependency detected
16    CircularDependency { cycle: Vec<String> },
17    /// Lifetime compatibility issue
18    LifetimeIncompatibility {
19        service: String,
20        service_lifetime: ServiceScope,
21        dependency: String,
22        dependency_lifetime: ServiceScope,
23    },
24    /// Interface without implementation
25    UnboundInterface { interface: String },
26    /// Multiple default implementations
27    MultipleDefaults {
28        interface: String,
29        implementations: Vec<String>,
30    },
31    /// Invalid factory signature
32    InvalidFactory { service: String, error: String },
33    /// Missing auto-wire information
34    MissingAutoWire { service: String },
35}
36
37impl ValidationError {
38    /// Convert to CoreError for runtime use
39    pub fn to_core_error(&self) -> CoreError {
40        match self {
41            ValidationError::MissingRegistration {
42                service_type,
43                required_by,
44            } => CoreError::ServiceNotFound {
45                service_type: format!("{} (required by {})", service_type, required_by),
46            },
47            ValidationError::CircularDependency { cycle } => CoreError::InvalidServiceDescriptor {
48                message: format!("Circular dependency detected: {}", cycle.join(" -> ")),
49            },
50            ValidationError::LifetimeIncompatibility {
51                service,
52                service_lifetime,
53                dependency,
54                dependency_lifetime,
55            } => CoreError::InvalidServiceDescriptor {
56                message: format!(
57                    "Lifetime incompatibility: {} ({:?}) depends on {} ({:?})",
58                    service, service_lifetime, dependency, dependency_lifetime
59                ),
60            },
61            ValidationError::UnboundInterface { interface } => CoreError::ServiceNotFound {
62                service_type: format!("No implementation bound for interface {}", interface),
63            },
64            ValidationError::MultipleDefaults {
65                interface,
66                implementations,
67            } => CoreError::InvalidServiceDescriptor {
68                message: format!(
69                    "Multiple default implementations for {}: {}",
70                    interface,
71                    implementations.join(", ")
72                ),
73            },
74            ValidationError::InvalidFactory { service, error } => {
75                CoreError::InvalidServiceDescriptor {
76                    message: format!("Invalid factory for {}: {}", service, error),
77                }
78            }
79            ValidationError::MissingAutoWire { service } => CoreError::InvalidServiceDescriptor {
80                message: format!(
81                    "Service {} is marked for auto-wiring but no constructor info available",
82                    service
83                ),
84            },
85        }
86    }
87}
88
89/// Compile-time dependency validator
90#[derive(Debug)]
91pub struct DependencyValidator {
92    dependency_graph: HashMap<ServiceId, Vec<ServiceId>>,
93    interface_bindings: HashMap<String, Vec<ServiceId>>,
94    service_ids: Vec<ServiceId>,
95    service_lifetimes: HashMap<ServiceId, ServiceScope>,
96}
97
98impl DependencyValidator {
99    /// Create a new validator from service descriptors
100    pub fn new(descriptors: &[ServiceDescriptor]) -> Self {
101        let mut dependency_graph = HashMap::new();
102        let mut interface_bindings: HashMap<String, Vec<ServiceId>> = HashMap::new();
103        let mut service_lifetimes = HashMap::new();
104
105        // Build dependency graph, interface bindings, and store lifetimes
106        for descriptor in descriptors {
107            dependency_graph.insert(
108                descriptor.service_id.clone(),
109                descriptor.dependencies.clone(),
110            );
111            service_lifetimes.insert(
112                descriptor.service_id.clone(),
113                descriptor.lifetime,
114            );
115
116            // Track interface bindings (simplified - would need more metadata in real implementation)
117            if let Some(interface_name) = descriptor.service_id.name.as_ref() {
118                interface_bindings
119                    .entry(interface_name.clone())
120                    .or_default()
121                    .push(descriptor.service_id.clone());
122            }
123        }
124
125        let service_ids = descriptors.iter().map(|d| d.service_id.clone()).collect();
126
127        Self {
128            dependency_graph,
129            interface_bindings,
130            service_ids,
131            service_lifetimes,
132        }
133    }
134
135    /// Validate all dependencies and return any errors
136    pub fn validate(&self) -> Result<(), Vec<ValidationError>> {
137        let mut errors = Vec::new();
138
139        // Check for missing registrations
140        errors.extend(self.validate_registrations());
141
142        // Check for circular dependencies
143        errors.extend(self.validate_circular_dependencies());
144
145        // Check lifetime compatibility
146        errors.extend(self.validate_lifetime_compatibility());
147
148        // Check interface bindings
149        errors.extend(self.validate_interface_bindings());
150
151        // Check for multiple defaults
152        errors.extend(self.validate_default_implementations());
153
154        if errors.is_empty() {
155            Ok(())
156        } else {
157            Err(errors)
158        }
159    }
160
161    /// Validate that all dependencies are registered
162    fn validate_registrations(&self) -> Vec<ValidationError> {
163        let mut errors = Vec::new();
164        let registered_services: HashSet<&ServiceId> = self.service_ids.iter().collect();
165
166        for (service_id, dependencies) in &self.dependency_graph {
167            for dependency in dependencies {
168                if !registered_services.contains(dependency) {
169                    errors.push(ValidationError::MissingRegistration {
170                        service_type: dependency.type_name().to_string(),
171                        required_by: service_id.type_name().to_string(),
172                    });
173                }
174            }
175        }
176
177        errors
178    }
179
180    /// Validate there are no circular dependencies
181    fn validate_circular_dependencies(&self) -> Vec<ValidationError> {
182        let mut errors = Vec::new();
183        let mut visited = HashSet::new();
184        let mut rec_stack = HashSet::new();
185        let mut path = Vec::new();
186
187        for service_id in self.dependency_graph.keys() {
188            if !visited.contains(service_id) {
189                if let Some(cycle) =
190                    self.detect_cycle(service_id, &mut visited, &mut rec_stack, &mut path)
191                {
192                    errors.push(ValidationError::CircularDependency { cycle });
193                }
194            }
195        }
196
197        errors
198    }
199
200    /// Detect cycles in dependency graph using DFS
201    fn detect_cycle(
202        &self,
203        service_id: &ServiceId,
204        visited: &mut HashSet<ServiceId>,
205        rec_stack: &mut HashSet<ServiceId>,
206        path: &mut Vec<String>,
207    ) -> Option<Vec<String>> {
208        visited.insert(service_id.clone());
209        rec_stack.insert(service_id.clone());
210        path.push(service_id.type_name().to_string());
211
212        if let Some(dependencies) = self.dependency_graph.get(service_id) {
213            for dependency in dependencies {
214                if !visited.contains(dependency) {
215                    if let Some(cycle) = self.detect_cycle(dependency, visited, rec_stack, path) {
216                        return Some(cycle);
217                    }
218                } else if rec_stack.contains(dependency) {
219                    // Found a back edge - cycle detected
220                    let cycle_start = path
221                        .iter()
222                        .position(|name| name == dependency.type_name())
223                        .unwrap_or(0);
224                    let mut cycle = path[cycle_start..].to_vec();
225                    cycle.push(dependency.type_name().to_string());
226                    return Some(cycle);
227                }
228            }
229        }
230
231        rec_stack.remove(service_id);
232        path.pop();
233        None
234    }
235
236    /// Validate lifetime compatibility
237    fn validate_lifetime_compatibility(&self) -> Vec<ValidationError> {
238        let mut errors = Vec::new();
239        
240        for (service_id, dependencies) in &self.dependency_graph {
241            if let Some(&service_lifetime) = self.service_lifetimes.get(service_id) {
242                for dependency_id in dependencies {
243                    if let Some(&dependency_lifetime) = self.service_lifetimes.get(dependency_id) {
244                        if !self.is_lifetime_compatible(service_lifetime, dependency_lifetime) {
245                            errors.push(ValidationError::LifetimeIncompatibility {
246                                service: service_id.type_name().to_string(),
247                                service_lifetime,
248                                dependency: dependency_id.type_name().to_string(),
249                                dependency_lifetime,
250                            });
251                        }
252                    }
253                }
254            }
255        }
256        
257        errors
258    }
259
260    /// Check if service lifetime is compatible with dependency lifetime
261    fn is_lifetime_compatible(&self, service: ServiceScope, dependency: ServiceScope) -> bool {
262        match (service, dependency) {
263            // Singleton can depend on anything
264            (ServiceScope::Singleton, _) => true,
265
266            // Scoped can depend on Singleton or Scoped, but not Transient
267            (ServiceScope::Scoped, ServiceScope::Transient) => false,
268            (ServiceScope::Scoped, _) => true,
269
270            // Transient can depend on anything
271            (ServiceScope::Transient, _) => true,
272        }
273    }
274
275    /// Validate interface bindings
276    fn validate_interface_bindings(&self) -> Vec<ValidationError> {
277        let mut errors = Vec::new();
278
279        // This would be more sophisticated in a real implementation
280        // For now, just check if we have interfaces without implementations
281        for (interface, implementations) in &self.interface_bindings {
282            if implementations.is_empty() {
283                errors.push(ValidationError::UnboundInterface {
284                    interface: interface.clone(),
285                });
286            }
287        }
288
289        errors
290    }
291
292    /// Validate default implementations
293    fn validate_default_implementations(&self) -> Vec<ValidationError> {
294        let mut errors = Vec::new();
295        let defaults_per_interface: HashMap<String, Vec<String>> = HashMap::new();
296
297        // This would need additional metadata to track which services are marked as default
298        // For now, this is a placeholder
299
300        for (interface, implementations) in defaults_per_interface {
301            if implementations.len() > 1 {
302                errors.push(ValidationError::MultipleDefaults {
303                    interface,
304                    implementations,
305                });
306            }
307        }
308
309        errors
310    }
311
312    /// Get dependency graph for visualization
313    pub fn dependency_graph(&self) -> &HashMap<ServiceId, Vec<ServiceId>> {
314        &self.dependency_graph
315    }
316
317    /// Get topologically sorted services
318    pub fn topological_sort(&self) -> Result<Vec<ServiceId>, ValidationError> {
319        let mut visited = HashSet::new();
320        let mut temp_visited = HashSet::new();
321        let mut result = Vec::new();
322
323        // Sort service IDs to ensure consistent ordering
324        let mut service_ids: Vec<_> = self.dependency_graph.keys().cloned().collect();
325        service_ids.sort_by(|a, b| a.type_name().cmp(b.type_name()));
326        
327        for service_id in service_ids {
328            if !visited.contains(&service_id) {
329                self.visit_for_topo_sort(&service_id, &mut visited, &mut temp_visited, &mut result)?
330            }
331        }
332
333        // No reverse needed - DFS post-order already gives correct dependency order
334        Ok(result)
335    }
336
337    /// Visit node for topological sorting
338    fn visit_for_topo_sort(
339        &self,
340        service_id: &ServiceId,
341        visited: &mut HashSet<ServiceId>,
342        temp_visited: &mut HashSet<ServiceId>,
343        result: &mut Vec<ServiceId>,
344    ) -> Result<(), ValidationError> {
345        if temp_visited.contains(service_id) {
346            return Err(ValidationError::CircularDependency {
347                cycle: vec![service_id.type_name().to_string()], // Simplified
348            });
349        }
350
351        if visited.contains(service_id) {
352            return Ok(());
353        }
354
355        temp_visited.insert(service_id.clone());
356
357        if let Some(dependencies) = self.dependency_graph.get(service_id) {
358            for dependency in dependencies {
359                self.visit_for_topo_sort(dependency, visited, temp_visited, result)?;
360            }
361        }
362
363        temp_visited.remove(service_id);
364        visited.insert(service_id.clone());
365        result.push(service_id.clone());
366
367        Ok(())
368    }
369}
370
371/// Container validator for runtime validation
372#[derive(Debug)]
373pub struct ContainerValidator;
374
375impl ContainerValidator {
376    /// Create a new container validator
377    pub fn new() -> Self {
378        Self
379    }
380
381    /// Validate container configuration
382    pub fn validate_container(
383        &self,
384        descriptors: &[ServiceDescriptor],
385    ) -> Result<ValidationReport, Vec<ValidationError>> {
386        let validator = DependencyValidator::new(descriptors);
387
388        match validator.validate() {
389            Ok(()) => {
390                let topo_order = validator.topological_sort().map_err(|e| vec![e])?;
391
392                Ok(ValidationReport {
393                    is_valid: true,
394                    service_count: descriptors.len(),
395                    dependency_count: validator
396                        .dependency_graph
397                        .values()
398                        .map(|deps| deps.len())
399                        .sum(),
400                    resolution_order: topo_order,
401                    errors: vec![],
402                    warnings: vec![],
403                })
404            }
405            Err(errors) => Err(errors),
406        }
407    }
408
409    /// Validate and provide warnings for potential issues
410    pub fn validate_with_warnings(&self, descriptors: &[ServiceDescriptor]) -> ValidationReport {
411        let validator = DependencyValidator::new(descriptors);
412        let mut warnings = Vec::new();
413
414        match validator.validate() {
415            Ok(()) => {
416                // Check for potential warnings
417                warnings.extend(self.check_performance_warnings(descriptors));
418                warnings.extend(self.check_design_warnings(descriptors));
419
420                let topo_order = validator.topological_sort().unwrap_or_else(|_| vec![]);
421
422                ValidationReport {
423                    is_valid: true,
424                    service_count: descriptors.len(),
425                    dependency_count: validator
426                        .dependency_graph
427                        .values()
428                        .map(|deps| deps.len())
429                        .sum(),
430                    resolution_order: topo_order,
431                    errors: vec![],
432                    warnings,
433                }
434            }
435            Err(errors) => ValidationReport {
436                is_valid: false,
437                service_count: descriptors.len(),
438                dependency_count: 0,
439                resolution_order: vec![],
440                errors,
441                warnings,
442            },
443        }
444    }
445
446    /// Check for performance-related warnings
447    fn check_performance_warnings(
448        &self,
449        descriptors: &[ServiceDescriptor],
450    ) -> Vec<ValidationWarning> {
451        let mut warnings = Vec::new();
452
453        // Check for too many transient services
454        let transient_count = descriptors
455            .iter()
456            .filter(|d| d.lifetime == ServiceScope::Transient)
457            .count();
458
459        if transient_count > descriptors.len() / 2 {
460            warnings.push(ValidationWarning::PerformanceConcern {
461                issue: format!("High number of transient services ({}/{}). Consider using singleton or scoped lifetimes.", transient_count, descriptors.len()),
462            });
463        }
464
465        // Check for deeply nested dependencies
466        // TODO: Implement depth checking
467
468        warnings
469    }
470
471    /// Check for design-related warnings
472    fn check_design_warnings(&self, descriptors: &[ServiceDescriptor]) -> Vec<ValidationWarning> {
473        let mut warnings = Vec::new();
474
475        // Check for services with too many dependencies
476        for descriptor in descriptors {
477            if descriptor.dependencies.len() > 5 {
478                warnings.push(ValidationWarning::DesignConcern {
479                    service: descriptor.service_id.type_name().to_string(),
480                    issue: format!(
481                        "Service has {} dependencies. Consider breaking it down.",
482                        descriptor.dependencies.len()
483                    ),
484                });
485            }
486        }
487
488        warnings
489    }
490}
491
492impl Default for ContainerValidator {
493    fn default() -> Self {
494        Self::new()
495    }
496}
497
498/// Validation warning types
499#[derive(Debug, Clone)]
500pub enum ValidationWarning {
501    /// Performance-related concern
502    PerformanceConcern { issue: String },
503    /// Design-related concern
504    DesignConcern { service: String, issue: String },
505    /// Best practice violation
506    BestPracticeViolation {
507        service: String,
508        violation: String,
509        suggestion: String,
510    },
511}
512
513/// Validation report containing results and recommendations
514#[derive(Debug)]
515pub struct ValidationReport {
516    pub is_valid: bool,
517    pub service_count: usize,
518    pub dependency_count: usize,
519    pub resolution_order: Vec<ServiceId>,
520    pub errors: Vec<ValidationError>,
521    pub warnings: Vec<ValidationWarning>,
522}
523
524impl ValidationReport {
525    /// Generate a human-readable report
526    pub fn to_string(&self) -> String {
527        let mut report = String::new();
528
529        report.push_str("Container Validation Report\n");
530        report.push_str("==========================\n\n");
531
532        report.push_str(&format!(
533            "Status: {}\n",
534            if self.is_valid { "VALID" } else { "INVALID" }
535        ));
536        report.push_str(&format!("Services: {}\n", self.service_count));
537        report.push_str(&format!("Dependencies: {}\n", self.dependency_count));
538        report.push_str(&format!(
539            "Resolution Order: {} services\n\n",
540            self.resolution_order.len()
541        ));
542
543        if !self.errors.is_empty() {
544            report.push_str("ERRORS:\n");
545            for (i, error) in self.errors.iter().enumerate() {
546                report.push_str(&format!("  {}. {:?}\n", i + 1, error));
547            }
548            report.push('\n');
549        }
550
551        if !self.warnings.is_empty() {
552            report.push_str("WARNINGS:\n");
553            for (i, warning) in self.warnings.iter().enumerate() {
554                report.push_str(&format!("  {}. {:?}\n", i + 1, warning));
555            }
556            report.push('\n');
557        }
558
559        if self.is_valid {
560            report.push_str("✅ Container configuration is valid and ready for use.\n");
561        } else {
562            report.push_str("❌ Container configuration has errors that must be fixed.\n");
563        }
564
565        report
566    }
567}
568
569#[cfg(test)]
570mod tests {
571    use super::*;
572    use crate::container::descriptor::{ServiceActivationStrategy, ServiceDescriptor};
573    use std::any::{Any, TypeId};
574
575    fn create_test_descriptor(
576        type_name: &str,
577        lifetime: ServiceScope,
578        deps: Vec<&str>,
579    ) -> ServiceDescriptor {
580        let service_id = ServiceId {
581            type_id: TypeId::of::<()>(),
582            type_name: "test_service",
583            name: Some(type_name.to_string()),
584        };
585
586        let dependencies: Vec<ServiceId> = deps
587            .iter()
588            .map(|dep| ServiceId {
589                type_id: TypeId::of::<()>(),
590                type_name: "test_service",
591                name: Some(dep.to_string()),
592            })
593            .collect();
594
595        ServiceDescriptor {
596            service_id,
597            implementation_id: TypeId::of::<()>(),
598            lifetime,
599            dependencies,
600            activation_strategy: ServiceActivationStrategy::Factory(Box::new(|| {
601                Ok(Box::new(()) as Box<dyn Any + Send + Sync>)
602            })),
603        }
604    }
605
606    #[test]
607    fn test_valid_dependencies() {
608        let descriptors = vec![
609            create_test_descriptor("ServiceA", ServiceScope::Singleton, vec![]),
610            create_test_descriptor("ServiceB", ServiceScope::Singleton, vec!["ServiceA"]),
611            create_test_descriptor(
612                "ServiceC",
613                ServiceScope::Transient,
614                vec!["ServiceA", "ServiceB"],
615            ),
616        ];
617
618        let validator = DependencyValidator::new(&descriptors);
619        let result = validator.validate();
620
621        assert!(result.is_ok());
622    }
623
624    #[test]
625    fn test_missing_dependency() {
626        let descriptors = vec![create_test_descriptor(
627            "ServiceA",
628            ServiceScope::Singleton,
629            vec!["MissingService"],
630        )];
631
632        let validator = DependencyValidator::new(&descriptors);
633        let result = validator.validate();
634
635        assert!(result.is_err());
636        let errors = result.unwrap_err();
637        assert_eq!(errors.len(), 1);
638        assert!(matches!(
639            errors[0],
640            ValidationError::MissingRegistration { .. }
641        ));
642    }
643
644    #[test]
645    fn test_circular_dependency() {
646        let descriptors = vec![
647            create_test_descriptor("ServiceA", ServiceScope::Singleton, vec!["ServiceB"]),
648            create_test_descriptor("ServiceB", ServiceScope::Singleton, vec!["ServiceA"]),
649        ];
650
651        let validator = DependencyValidator::new(&descriptors);
652        let result = validator.validate();
653
654        assert!(result.is_err());
655        let errors = result.unwrap_err();
656        assert!(errors
657            .iter()
658            .any(|e| matches!(e, ValidationError::CircularDependency { .. })));
659    }
660
661    #[test]
662    fn test_lifetime_incompatibility() {
663        let descriptors = vec![
664            create_test_descriptor(
665                "SingletonService",
666                ServiceScope::Singleton,
667                vec!["TransientService"],
668            ),
669            create_test_descriptor("TransientService", ServiceScope::Transient, vec![]),
670            create_test_descriptor(
671                "ScopedService",
672                ServiceScope::Scoped,
673                vec!["TransientService"],
674            ),
675        ];
676
677        let validator = DependencyValidator::new(&descriptors);
678        let result = validator.validate();
679
680        assert!(result.is_err());
681        let errors = result.unwrap_err();
682        // Scoped depending on Transient should be an error
683        assert!(errors
684            .iter()
685            .any(|e| matches!(e, ValidationError::LifetimeIncompatibility { .. })));
686    }
687
688    #[test]
689    fn test_topological_sort() {
690        let descriptors = vec![
691            create_test_descriptor(
692                "ServiceC",
693                ServiceScope::Singleton,
694                vec!["ServiceA", "ServiceB"],
695            ),
696            create_test_descriptor("ServiceB", ServiceScope::Singleton, vec!["ServiceA"]),
697            create_test_descriptor("ServiceA", ServiceScope::Singleton, vec![]),
698        ];
699
700        let validator = DependencyValidator::new(&descriptors);
701        let topo_order = validator.topological_sort().unwrap();
702
703        // ServiceA should come first (no dependencies)
704        // ServiceB should come second (depends on A)
705        // ServiceC should come last (depends on A and B)
706        let names: Vec<String> = topo_order
707            .iter()
708            .map(|id| id.name.as_ref().unwrap().clone())
709            .collect();
710
711        assert_eq!(names[0], "ServiceA");
712        assert_eq!(names[1], "ServiceB");
713        assert_eq!(names[2], "ServiceC");
714    }
715
716    #[test]
717    fn test_container_validator() {
718        let descriptors = vec![
719            create_test_descriptor("ServiceA", ServiceScope::Singleton, vec![]),
720            create_test_descriptor("ServiceB", ServiceScope::Transient, vec!["ServiceA"]),
721        ];
722
723        let validator = ContainerValidator::new();
724        let report = validator.validate_with_warnings(&descriptors);
725
726        assert!(report.is_valid);
727        assert_eq!(report.service_count, 2);
728        assert_eq!(report.resolution_order.len(), 2);
729    }
730}