elif_core/modules/
descriptors.rs

1//! Module descriptor system for Epic 3 - Module Descriptor Generation
2//!
3//! Provides comprehensive descriptors that capture module structure, dependencies,
4//! and auto-configuration capabilities for runtime composition and dependency resolution.
5//!
6//! ## Features
7//! - **ModuleDescriptor**: Complete module structure with providers, controllers, imports, exports
8//! - **ServiceDescriptor**: Service metadata including lifecycle and trait mappings
9//! - **ControllerDescriptor**: Controller metadata for routing integration
10//! - **Auto-configuration**: Generated `__auto_configure()` functions for IoC integration
11
12use crate::container::{ContainerBuilder, IocContainer};
13use crate::modules::ModuleError;
14use std::any::TypeId;
15
16/// Lifecycle management for services
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub enum ServiceLifecycle {
19    /// Single instance per container
20    Singleton,
21    /// Single instance per scope
22    Scoped,
23    /// New instance per request
24    Transient,
25}
26
27impl Default for ServiceLifecycle {
28    fn default() -> Self {
29        Self::Singleton
30    }
31}
32
33/// Service provider definition with complete metadata
34#[derive(Debug, Clone)]
35pub struct ServiceDescriptor {
36    /// Service type name for debugging
37    pub service_name: String,
38    /// Service type ID for resolution
39    pub service_type: TypeId,
40    /// Implementation type ID (if different from service)
41    pub implementation_type: Option<TypeId>,
42    /// Named binding identifier
43    pub name: Option<String>,
44    /// Service lifecycle management
45    pub lifecycle: ServiceLifecycle,
46    /// Whether this service implements a trait
47    pub is_trait_service: bool,
48    /// Dependencies that must be resolved first
49    pub dependencies: Vec<TypeId>,
50}
51
52impl ServiceDescriptor {
53    /// Create a new service descriptor
54    pub fn new<S: 'static>(service_name: impl Into<String>, lifecycle: ServiceLifecycle) -> Self {
55        Self {
56            service_name: service_name.into(),
57            service_type: TypeId::of::<S>(),
58            implementation_type: None,
59            name: None,
60            lifecycle,
61            is_trait_service: false,
62            dependencies: Vec::new(),
63        }
64    }
65
66    /// Create a service descriptor for trait mapping
67    pub fn trait_mapping<S: 'static, I: 'static>(
68        service_name: impl Into<String>,
69        implementation_name: impl Into<String>,
70        lifecycle: ServiceLifecycle,
71    ) -> Self {
72        Self {
73            service_name: format!("{} => {}", service_name.into(), implementation_name.into()),
74            service_type: TypeId::of::<S>(),
75            implementation_type: Some(TypeId::of::<I>()),
76            name: None,
77            lifecycle,
78            is_trait_service: true,
79            dependencies: Vec::new(),
80        }
81    }
82
83    /// Set named binding
84    pub fn with_name(mut self, name: impl Into<String>) -> Self {
85        self.name = Some(name.into());
86        self
87    }
88
89    /// Set service dependencies
90    pub fn with_dependencies(mut self, dependencies: Vec<TypeId>) -> Self {
91        self.dependencies = dependencies;
92        self
93    }
94}
95
96/// Controller definition with metadata
97#[derive(Debug, Clone)]
98pub struct ControllerDescriptor {
99    /// Controller type name for debugging
100    pub controller_name: String,
101    /// Controller type ID
102    pub controller_type: TypeId,
103    /// Base path for controller routes
104    pub base_path: Option<String>,
105    /// Middleware applied to controller
106    pub middleware: Vec<String>,
107    /// Dependencies that must be injected
108    pub dependencies: Vec<TypeId>,
109}
110
111impl ControllerDescriptor {
112    /// Create a new controller descriptor
113    pub fn new<C: 'static>(controller_name: impl Into<String>) -> Self {
114        Self {
115            controller_name: controller_name.into(),
116            controller_type: TypeId::of::<C>(),
117            base_path: None,
118            middleware: Vec::new(),
119            dependencies: Vec::new(),
120        }
121    }
122
123    /// Set controller base path
124    pub fn with_base_path(mut self, path: impl Into<String>) -> Self {
125        self.base_path = Some(path.into());
126        self
127    }
128
129    /// Set controller middleware
130    pub fn with_middleware(mut self, middleware: Vec<String>) -> Self {
131        self.middleware = middleware;
132        self
133    }
134
135    /// Set controller dependencies
136    pub fn with_dependencies(mut self, dependencies: Vec<TypeId>) -> Self {
137        self.dependencies = dependencies;
138        self
139    }
140}
141
142/// Complete module descriptor with all metadata and auto-configuration
143#[derive(Debug, Clone)]
144pub struct ModuleDescriptor {
145    /// Module name for identification
146    pub name: String,
147    /// Module version for compatibility
148    pub version: Option<String>,
149    /// Module description
150    pub description: Option<String>,
151    /// Service providers defined in this module
152    pub providers: Vec<ServiceDescriptor>,
153    /// Controllers defined in this module
154    pub controllers: Vec<ControllerDescriptor>,
155    /// Other modules that this module imports
156    pub imports: Vec<String>,
157    /// Services that this module exports to other modules
158    pub exports: Vec<String>,
159    /// Dependencies that must be loaded first
160    pub dependencies: Vec<String>,
161    /// Whether this module can be disabled
162    pub is_optional: bool,
163}
164
165impl ModuleDescriptor {
166    /// Create a new module descriptor
167    pub fn new(name: impl Into<String>) -> Self {
168        Self {
169            name: name.into(),
170            version: None,
171            description: None,
172            providers: Vec::new(),
173            controllers: Vec::new(),
174            imports: Vec::new(),
175            exports: Vec::new(),
176            dependencies: Vec::new(),
177            is_optional: true,
178        }
179    }
180
181    /// Set module version
182    pub fn with_version(mut self, version: impl Into<String>) -> Self {
183        self.version = Some(version.into());
184        self
185    }
186
187    /// Set module description
188    pub fn with_description(mut self, description: impl Into<String>) -> Self {
189        self.description = Some(description.into());
190        self
191    }
192
193    /// Add a service provider
194    pub fn with_provider(mut self, provider: ServiceDescriptor) -> Self {
195        self.providers.push(provider);
196        self
197    }
198
199    /// Add multiple service providers
200    pub fn with_providers(mut self, providers: Vec<ServiceDescriptor>) -> Self {
201        self.providers.extend(providers);
202        self
203    }
204
205    /// Add a controller
206    pub fn with_controller(mut self, controller: ControllerDescriptor) -> Self {
207        self.controllers.push(controller);
208        self
209    }
210
211    /// Add multiple controllers
212    pub fn with_controllers(mut self, controllers: Vec<ControllerDescriptor>) -> Self {
213        self.controllers.extend(controllers);
214        self
215    }
216
217    /// Set module imports
218    pub fn with_imports(mut self, imports: Vec<String>) -> Self {
219        self.imports = imports;
220        self
221    }
222
223    /// Set module exports
224    pub fn with_exports(mut self, exports: Vec<String>) -> Self {
225        self.exports = exports;
226        self
227    }
228
229    /// Set module dependencies
230    pub fn with_dependencies(mut self, dependencies: Vec<String>) -> Self {
231        self.dependencies = dependencies;
232        self
233    }
234
235    /// Set if module is optional
236    pub fn with_optional(mut self, is_optional: bool) -> Self {
237        self.is_optional = is_optional;
238        self
239    }
240
241    /// Get total service count
242    pub fn service_count(&self) -> usize {
243        self.providers.len()
244    }
245
246    /// Get total controller count
247    pub fn controller_count(&self) -> usize {
248        self.controllers.len()
249    }
250
251    /// Check if module has exports
252    pub fn has_exports(&self) -> bool {
253        !self.exports.is_empty()
254    }
255
256    /// Check if module has imports
257    pub fn has_imports(&self) -> bool {
258        !self.imports.is_empty()
259    }
260}
261
262/// Auto-configuration trait for modules to implement IoC integration
263pub trait ModuleAutoConfiguration {
264    /// Generate the module descriptor
265    fn module_descriptor() -> ModuleDescriptor;
266
267    /// Auto-configure the IoC container with this module's services
268    fn auto_configure(container: &mut IocContainer) -> Result<(), ModuleError>;
269
270    /// Configure the container builder (for compatibility with existing Module trait)
271    fn configure_builder(builder: ContainerBuilder) -> Result<ContainerBuilder, ModuleError> {
272        Ok(builder)
273    }
274}
275
276/// Module composition result for module! macro
277#[derive(Debug)]
278pub struct ModuleComposition {
279    /// Modules included in the composition
280    pub modules: Vec<ModuleDescriptor>,
281    /// Overrides applied to the composition
282    pub overrides: Vec<ServiceDescriptor>,
283    /// Final merged configuration
284    pub merged_descriptor: ModuleDescriptor,
285}
286
287impl ModuleComposition {
288    /// Create a new module composition
289    pub fn new() -> Self {
290        Self {
291            modules: Vec::new(),
292            overrides: Vec::new(),
293            merged_descriptor: ModuleDescriptor::new("Composed"),
294        }
295    }
296
297    /// Add a module to the composition
298    pub fn with_module(mut self, descriptor: ModuleDescriptor) -> Self {
299        self.modules.push(descriptor);
300        self
301    }
302
303    /// Add overrides to the composition
304    pub fn with_overrides(mut self, overrides: Vec<ServiceDescriptor>) -> Self {
305        self.overrides = overrides;
306        self
307    }
308
309    /// Apply composition and resolve conflicts
310    pub fn compose(mut self) -> Result<ModuleDescriptor, ModuleError> {
311        // First validate modules for circular imports and missing exports
312        let validator = ModuleDependencyValidator::new(&self.modules);
313        if let Err(validation_errors) = validator.validate() {
314            return Err(ModuleError::ConfigurationFailed {
315                message: format!(
316                    "Module validation failed: {}",
317                    validation_errors
318                        .iter()
319                        .map(|e| e.to_string())
320                        .collect::<Vec<_>>()
321                        .join("; ")
322                ),
323            });
324        }
325
326        // Get topological sort to ensure proper loading order
327        let loading_order =
328            validator
329                .topological_sort()
330                .map_err(|e| ModuleError::ConfigurationFailed {
331                    message: format!("Failed to determine module loading order: {}", e),
332                })?;
333
334        // Merge all modules into final descriptor in dependency order
335        let mut final_descriptor = ModuleDescriptor::new("ComposedApplication");
336
337        // Create a HashMap for O(1) module lookups instead of O(N) linear search
338        let module_map: std::collections::HashMap<_, _> =
339            self.modules.iter().map(|m| (&m.name, m)).collect();
340
341        // Process modules in topological order - now O(N) instead of O(N^2)
342        for module_name in &loading_order {
343            if let Some(module) = module_map.get(module_name) {
344                final_descriptor.providers.extend(module.providers.clone());
345                final_descriptor
346                    .controllers
347                    .extend(module.controllers.clone());
348                final_descriptor.imports.extend(module.imports.clone());
349                final_descriptor.exports.extend(module.exports.clone());
350            }
351        }
352
353        // Apply overrides (replace matching services) - O(M+N) instead of O(M*N)
354        // Overrides match on (service_type, name) - same TypeId and same named binding
355        use std::collections::HashMap;
356        let override_map: HashMap<_, _> = self
357            .overrides
358            .iter()
359            .map(|s| ((s.service_type, s.name.clone()), s.clone()))
360            .collect();
361
362        // Remove original providers that have overrides
363        final_descriptor.providers.retain(|p| {
364            let key = (p.service_type, p.name.clone());
365            !override_map.contains_key(&key)
366        });
367
368        // Add all overrides (both replacements and new services)
369        final_descriptor
370            .providers
371            .extend(override_map.into_values());
372
373        self.merged_descriptor = final_descriptor.clone();
374        Ok(final_descriptor)
375    }
376
377    /// Auto-configure all modules in the composition
378    pub fn auto_configure_all(&self, _container: &mut IocContainer) -> Result<(), ModuleError> {
379        // Apply merged configuration to container
380        for _provider in &self.merged_descriptor.providers {
381            // This is a placeholder - actual IoC integration will depend on
382            // how we adapt the existing binding system
383            // For now, we'll implement basic registration patterns
384        }
385
386        Ok(())
387    }
388}
389
390impl Default for ModuleComposition {
391    fn default() -> Self {
392        Self::new()
393    }
394}
395
396/// Module validation errors
397#[derive(Debug, Clone)]
398pub enum ModuleValidationError {
399    /// Circular import detected
400    CircularImport { module: String, cycle: Vec<String> },
401    /// Missing export - module tries to import something that isn't exported
402    MissingExport {
403        importing_module: String,
404        target_module: String,
405        missing_service: String,
406    },
407    /// Self-import detected
408    SelfImport { module: String },
409    /// Duplicate service export
410    DuplicateExport { module: String, service: String },
411}
412
413impl std::fmt::Display for ModuleValidationError {
414    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
415        match self {
416            ModuleValidationError::CircularImport { module, cycle } => {
417                write!(
418                    f,
419                    "Circular import detected in module '{}': {}",
420                    module,
421                    cycle.join(" -> ")
422                )
423            }
424            ModuleValidationError::MissingExport {
425                importing_module,
426                target_module,
427                missing_service,
428            } => {
429                write!(
430                    f,
431                    "Module '{}' tries to import '{}' from '{}', but '{}' doesn't export it",
432                    importing_module, missing_service, target_module, target_module
433                )
434            }
435            ModuleValidationError::SelfImport { module } => {
436                write!(f, "Module '{}' cannot import itself", module)
437            }
438            ModuleValidationError::DuplicateExport { module, service } => {
439                write!(
440                    f,
441                    "Module '{}' exports '{}' multiple times",
442                    module, service
443                )
444            }
445        }
446    }
447}
448
449impl std::error::Error for ModuleValidationError {}
450
451/// Module dependency validator for detecting circular imports and missing exports
452#[derive(Debug)]
453pub struct ModuleDependencyValidator<'a> {
454    /// Modules being validated
455    modules: &'a [ModuleDescriptor],
456}
457
458impl<'a> ModuleDependencyValidator<'a> {
459    /// Create a new validator
460    pub fn new(modules: &'a [ModuleDescriptor]) -> Self {
461        Self { modules }
462    }
463
464    /// Validate all modules for structural issues
465    /// Note: Circular import detection is handled by topological_sort() for efficiency
466    pub fn validate(&self) -> Result<(), Vec<ModuleValidationError>> {
467        let mut errors = Vec::new();
468
469        // Check for missing exports
470        if let Err(export_errors) = self.validate_missing_exports() {
471            errors.extend(export_errors);
472        }
473
474        // Check for self-imports
475        if let Err(self_import_errors) = self.validate_self_imports() {
476            errors.extend(self_import_errors);
477        }
478
479        // Check for duplicate exports within modules
480        if let Err(duplicate_errors) = self.validate_duplicate_exports() {
481            errors.extend(duplicate_errors);
482        }
483
484        // Check for circular imports via topological sort (efficient single-pass)
485        if let Err(circular_error) = self.topological_sort() {
486            errors.push(circular_error);
487        }
488
489        if errors.is_empty() {
490            Ok(())
491        } else {
492            Err(errors)
493        }
494    }
495
496    /// Validate that all imports have corresponding exports
497    fn validate_missing_exports(&self) -> Result<(), Vec<ModuleValidationError>> {
498        let mut errors = Vec::new();
499
500        // Build export map: module_name -> exported_services
501        let mut export_map: std::collections::HashMap<String, std::collections::HashSet<String>> =
502            std::collections::HashMap::new();
503
504        for module in self.modules {
505            export_map.insert(
506                module.name.clone(),
507                module.exports.iter().cloned().collect(),
508            );
509        }
510
511        // Check each module's imports
512        for module in self.modules {
513            for import_module in &module.imports {
514                // Check if the imported module exists
515                if let Some(exported_services) = export_map.get(import_module.as_str()) {
516                    // For now, we assume modules import all exported services
517                    // In a more sophisticated system, we could track specific service imports
518                    if exported_services.is_empty() {
519                        // Importing from a module that exports nothing might be intentional
520                        // (e.g., for side effects), so we'll allow it
521                        continue;
522                    }
523                } else {
524                    // The imported module doesn't exist in our module set
525                    // This could be an external module, so we'll create a generic error
526                    errors.push(ModuleValidationError::MissingExport {
527                        importing_module: module.name.clone(),
528                        target_module: import_module.clone(),
529                        missing_service: "*unknown*".to_string(),
530                    });
531                }
532            }
533        }
534
535        if errors.is_empty() {
536            Ok(())
537        } else {
538            Err(errors)
539        }
540    }
541
542    /// Validate that modules don't import themselves
543    fn validate_self_imports(&self) -> Result<(), Vec<ModuleValidationError>> {
544        let mut errors = Vec::new();
545
546        for module in self.modules {
547            if module.imports.contains(&module.name) {
548                errors.push(ModuleValidationError::SelfImport {
549                    module: module.name.clone(),
550                });
551            }
552        }
553
554        if errors.is_empty() {
555            Ok(())
556        } else {
557            Err(errors)
558        }
559    }
560
561    /// Validate that modules don't export the same service multiple times
562    fn validate_duplicate_exports(&self) -> Result<(), Vec<ModuleValidationError>> {
563        let mut errors = Vec::new();
564
565        for module in self.modules {
566            let mut seen_exports = std::collections::HashSet::new();
567
568            for export in &module.exports {
569                if !seen_exports.insert(export.clone()) {
570                    errors.push(ModuleValidationError::DuplicateExport {
571                        module: module.name.clone(),
572                        service: export.clone(),
573                    });
574                }
575            }
576        }
577
578        if errors.is_empty() {
579            Ok(())
580        } else {
581            Err(errors)
582        }
583    }
584
585    /// Get topological order of modules (dependencies first)
586    pub fn topological_sort(&self) -> Result<Vec<String>, ModuleValidationError> {
587        let mut visited = std::collections::HashSet::new();
588        let mut temp_visited = std::collections::HashSet::new();
589        let mut result = Vec::new();
590
591        for module in self.modules {
592            if !visited.contains(&module.name) {
593                if let Err(cycle) = self.topological_visit(
594                    &module.name,
595                    &mut visited,
596                    &mut temp_visited,
597                    &mut result,
598                ) {
599                    return Err(ModuleValidationError::CircularImport {
600                        module: module.name.clone(),
601                        cycle,
602                    });
603                }
604            }
605        }
606
607        // Result is already in correct dependency order (dependencies first)
608        Ok(result)
609    }
610
611    /// Topological sort helper using DFS
612    fn topological_visit(
613        &self,
614        module_name: &str,
615        visited: &mut std::collections::HashSet<String>,
616        temp_visited: &mut std::collections::HashSet<String>,
617        result: &mut Vec<String>,
618    ) -> Result<(), Vec<String>> {
619        if temp_visited.contains(module_name) {
620            return Err(vec![module_name.to_string()]);
621        }
622
623        if visited.contains(module_name) {
624            return Ok(());
625        }
626
627        temp_visited.insert(module_name.to_string());
628
629        if let Some(module) = self.modules.iter().find(|m| m.name == module_name) {
630            for import in &module.imports {
631                if let Err(mut cycle) =
632                    self.topological_visit(import, visited, temp_visited, result)
633                {
634                    cycle.insert(0, module_name.to_string());
635                    return Err(cycle);
636                }
637            }
638        }
639
640        temp_visited.remove(module_name);
641        visited.insert(module_name.to_string());
642        result.push(module_name.to_string());
643
644        Ok(())
645    }
646}
647
648#[cfg(test)]
649mod tests {
650    use super::*;
651    use std::any::TypeId;
652
653    #[test]
654    fn test_service_descriptor_creation() {
655        let descriptor =
656            ServiceDescriptor::new::<String>("TestService", ServiceLifecycle::Singleton);
657
658        assert_eq!(descriptor.service_name, "TestService");
659        assert_eq!(descriptor.service_type, TypeId::of::<String>());
660        assert_eq!(descriptor.lifecycle, ServiceLifecycle::Singleton);
661        assert!(!descriptor.is_trait_service);
662    }
663
664    #[test]
665    fn test_trait_service_descriptor() {
666        let descriptor = ServiceDescriptor::trait_mapping::<String, Vec<u8>>(
667            "TraitService",
668            "Implementation",
669            ServiceLifecycle::Scoped,
670        );
671
672        assert!(descriptor.service_name.contains(" => "));
673        assert_eq!(descriptor.service_type, TypeId::of::<String>());
674        assert_eq!(
675            descriptor.implementation_type,
676            Some(TypeId::of::<Vec<u8>>())
677        );
678        assert!(descriptor.is_trait_service);
679        assert_eq!(descriptor.lifecycle, ServiceLifecycle::Scoped);
680    }
681
682    #[test]
683    fn test_controller_descriptor_creation() {
684        let descriptor = ControllerDescriptor::new::<String>("TestController")
685            .with_base_path("/api")
686            .with_middleware(vec!["auth".to_string(), "cors".to_string()]);
687
688        assert_eq!(descriptor.controller_name, "TestController");
689        assert_eq!(descriptor.base_path, Some("/api".to_string()));
690        assert_eq!(descriptor.middleware, vec!["auth", "cors"]);
691    }
692
693    #[test]
694    fn test_module_descriptor_builder() {
695        let provider = ServiceDescriptor::new::<String>("TestService", ServiceLifecycle::Singleton);
696        let controller = ControllerDescriptor::new::<Vec<u8>>("TestController");
697
698        let descriptor = ModuleDescriptor::new("TestModule")
699            .with_version("1.0.0")
700            .with_description("Test module for Epic 3")
701            .with_provider(provider)
702            .with_controller(controller)
703            .with_imports(vec!["DatabaseModule".to_string()])
704            .with_exports(vec!["TestService".to_string()])
705            .with_optional(false);
706
707        assert_eq!(descriptor.name, "TestModule");
708        assert_eq!(descriptor.version, Some("1.0.0".to_string()));
709        assert_eq!(descriptor.service_count(), 1);
710        assert_eq!(descriptor.controller_count(), 1);
711        assert!(descriptor.has_imports());
712        assert!(descriptor.has_exports());
713        assert!(!descriptor.is_optional);
714    }
715
716    #[test]
717    fn test_module_composition() {
718        let module1 =
719            ModuleDescriptor::new("Module1").with_provider(ServiceDescriptor::new::<String>(
720                "Service1",
721                ServiceLifecycle::Singleton,
722            ));
723
724        let module2 =
725            ModuleDescriptor::new("Module2").with_provider(ServiceDescriptor::new::<Vec<u8>>(
726                "Service2",
727                ServiceLifecycle::Scoped,
728            ));
729
730        let composition = ModuleComposition::new()
731            .with_module(module1)
732            .with_module(module2);
733
734        let result = composition.compose().unwrap();
735
736        assert_eq!(result.name, "ComposedApplication");
737        assert_eq!(result.service_count(), 2);
738    }
739
740    #[test]
741    fn test_module_composition_with_overrides() {
742        let module =
743            ModuleDescriptor::new("TestModule").with_provider(ServiceDescriptor::new::<String>(
744                "OriginalService",
745                ServiceLifecycle::Singleton,
746            ));
747
748        let override_service =
749            ServiceDescriptor::new::<String>("OverrideService", ServiceLifecycle::Transient);
750
751        let composition = ModuleComposition::new()
752            .with_module(module)
753            .with_overrides(vec![override_service]);
754
755        let result = composition.compose().unwrap();
756
757        // Should have the override service instead of original
758        assert_eq!(result.service_count(), 1);
759        assert_eq!(result.providers[0].service_name, "OverrideService");
760        assert_eq!(result.providers[0].lifecycle, ServiceLifecycle::Transient);
761    }
762
763    #[test]
764    fn test_module_validation_circular_imports() {
765        let module_a = ModuleDescriptor::new("ModuleA").with_imports(vec!["ModuleB".to_string()]);
766
767        let module_b = ModuleDescriptor::new("ModuleB").with_imports(vec!["ModuleC".to_string()]);
768
769        let module_c = ModuleDescriptor::new("ModuleC").with_imports(vec!["ModuleA".to_string()]); // Creates cycle A -> B -> C -> A
770
771        let modules = vec![module_a, module_b, module_c];
772        let validator = ModuleDependencyValidator::new(&modules);
773        let result = validator.validate();
774
775        assert!(result.is_err());
776        let errors = result.unwrap_err();
777        assert_eq!(errors.len(), 1);
778
779        match &errors[0] {
780            ModuleValidationError::CircularImport { module, cycle } => {
781                assert!(module == "ModuleA" || module == "ModuleB" || module == "ModuleC");
782                assert!(cycle.len() >= 3);
783            }
784            _ => panic!("Expected CircularImport error"),
785        }
786    }
787
788    #[test]
789    fn test_module_validation_self_import() {
790        let module =
791            ModuleDescriptor::new("SelfModule").with_imports(vec!["SelfModule".to_string()]);
792
793        let modules = vec![module];
794        let validator = ModuleDependencyValidator::new(&modules);
795        let result = validator.validate();
796
797        assert!(result.is_err());
798        let errors = result.unwrap_err();
799
800        // Should have at least one error (self-import)
801        assert!(!errors.is_empty());
802
803        // Check that one of the errors is a self-import error
804        let has_self_import = errors.iter().any(|e| {
805            matches!(e,
806                ModuleValidationError::SelfImport { module } if module == "SelfModule"
807            )
808        });
809        assert!(has_self_import, "Should have SelfImport error");
810    }
811
812    #[test]
813    fn test_module_validation_missing_exports() {
814        let module_a =
815            ModuleDescriptor::new("ModuleA").with_imports(vec!["NonExistentModule".to_string()]);
816
817        let modules = vec![module_a];
818        let validator = ModuleDependencyValidator::new(&modules);
819        let result = validator.validate();
820
821        assert!(result.is_err());
822        let errors = result.unwrap_err();
823        assert_eq!(errors.len(), 1);
824
825        match &errors[0] {
826            ModuleValidationError::MissingExport {
827                importing_module,
828                target_module,
829                ..
830            } => {
831                assert_eq!(importing_module, "ModuleA");
832                assert_eq!(target_module, "NonExistentModule");
833            }
834            _ => panic!("Expected MissingExport error"),
835        }
836    }
837
838    #[test]
839    fn test_module_validation_duplicate_exports() {
840        let module = ModuleDescriptor::new("TestModule")
841            .with_exports(vec!["Service1".to_string(), "Service1".to_string()]);
842
843        let modules = vec![module];
844        let validator = ModuleDependencyValidator::new(&modules);
845        let result = validator.validate();
846
847        assert!(result.is_err());
848        let errors = result.unwrap_err();
849        assert_eq!(errors.len(), 1);
850
851        match &errors[0] {
852            ModuleValidationError::DuplicateExport { module, service } => {
853                assert_eq!(module, "TestModule");
854                assert_eq!(service, "Service1");
855            }
856            _ => panic!("Expected DuplicateExport error"),
857        }
858    }
859
860    #[test]
861    fn test_module_validation_success() {
862        let module_a = ModuleDescriptor::new("ModuleA").with_exports(vec!["ServiceA".to_string()]);
863
864        let module_b = ModuleDescriptor::new("ModuleB")
865            .with_imports(vec!["ModuleA".to_string()])
866            .with_exports(vec!["ServiceB".to_string()]);
867
868        let modules = vec![module_a, module_b];
869        let validator = ModuleDependencyValidator::new(&modules);
870        let result = validator.validate();
871
872        assert!(result.is_ok());
873    }
874
875    #[test]
876    fn test_module_topological_sort() {
877        let module_a = ModuleDescriptor::new("ModuleA"); // No dependencies
878
879        let module_b = ModuleDescriptor::new("ModuleB").with_imports(vec!["ModuleA".to_string()]); // Depends on A
880
881        let module_c = ModuleDescriptor::new("ModuleC").with_imports(vec!["ModuleB".to_string()]); // Depends on B
882
883        let modules = vec![module_c, module_a, module_b];
884        let validator = ModuleDependencyValidator::new(&modules);
885        let sorted = validator.topological_sort().unwrap();
886
887        // Should be sorted in dependency order: A, B, C
888        // (dependencies first, then modules that depend on them)
889        let a_pos = sorted.iter().position(|m| m == "ModuleA").unwrap();
890        let b_pos = sorted.iter().position(|m| m == "ModuleB").unwrap();
891        let c_pos = sorted.iter().position(|m| m == "ModuleC").unwrap();
892
893        assert!(a_pos < b_pos, "ModuleA should come before ModuleB");
894        assert!(b_pos < c_pos, "ModuleB should come before ModuleC");
895    }
896
897    #[test]
898    fn test_module_validation_error_display() {
899        let error = ModuleValidationError::CircularImport {
900            module: "TestModule".to_string(),
901            cycle: vec![
902                "A".to_string(),
903                "B".to_string(),
904                "C".to_string(),
905                "A".to_string(),
906            ],
907        };
908
909        let error_string = format!("{}", error);
910        assert!(error_string.contains("Circular import detected in module 'TestModule'"));
911        assert!(error_string.contains("A -> B -> C -> A"));
912    }
913}