1use std::collections::{HashMap, HashSet};
2
3use crate::container::descriptor::{ServiceDescriptor, ServiceId};
4use crate::container::scope::ServiceScope;
5use crate::errors::CoreError;
6
7#[derive(Debug, Clone, PartialEq)]
9pub enum ValidationError {
10 MissingRegistration {
12 service_type: String,
13 required_by: String,
14 },
15 CircularDependency { cycle: Vec<String> },
17 LifetimeIncompatibility {
19 service: String,
20 service_lifetime: ServiceScope,
21 dependency: String,
22 dependency_lifetime: ServiceScope,
23 },
24 UnboundInterface { interface: String },
26 MultipleDefaults {
28 interface: String,
29 implementations: Vec<String>,
30 },
31 InvalidFactory { service: String, error: String },
33 MissingAutoWire { service: String },
35}
36
37impl ValidationError {
38 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#[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 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 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 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 pub fn validate(&self) -> Result<(), Vec<ValidationError>> {
137 let mut errors = Vec::new();
138
139 errors.extend(self.validate_registrations());
141
142 errors.extend(self.validate_circular_dependencies());
144
145 errors.extend(self.validate_lifetime_compatibility());
147
148 errors.extend(self.validate_interface_bindings());
150
151 errors.extend(self.validate_default_implementations());
153
154 if errors.is_empty() {
155 Ok(())
156 } else {
157 Err(errors)
158 }
159 }
160
161 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 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 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 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 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 fn is_lifetime_compatible(&self, service: ServiceScope, dependency: ServiceScope) -> bool {
262 match (service, dependency) {
263 (ServiceScope::Singleton, _) => true,
265
266 (ServiceScope::Scoped, ServiceScope::Transient) => false,
268 (ServiceScope::Scoped, _) => true,
269
270 (ServiceScope::Transient, _) => true,
272 }
273 }
274
275 fn validate_interface_bindings(&self) -> Vec<ValidationError> {
277 let mut errors = Vec::new();
278
279 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 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 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 pub fn dependency_graph(&self) -> &HashMap<ServiceId, Vec<ServiceId>> {
314 &self.dependency_graph
315 }
316
317 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 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 Ok(result)
335 }
336
337 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()], });
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#[derive(Debug)]
373pub struct ContainerValidator;
374
375impl ContainerValidator {
376 pub fn new() -> Self {
378 Self
379 }
380
381 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 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 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 fn check_performance_warnings(
448 &self,
449 descriptors: &[ServiceDescriptor],
450 ) -> Vec<ValidationWarning> {
451 let mut warnings = Vec::new();
452
453 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 warnings
469 }
470
471 fn check_design_warnings(&self, descriptors: &[ServiceDescriptor]) -> Vec<ValidationWarning> {
473 let mut warnings = Vec::new();
474
475 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#[derive(Debug, Clone)]
500pub enum ValidationWarning {
501 PerformanceConcern { issue: String },
503 DesignConcern { service: String, issue: String },
505 BestPracticeViolation {
507 service: String,
508 violation: String,
509 suggestion: String,
510 },
511}
512
513#[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 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 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 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}