1use sklears_core::error::{Result as SklResult, SklearsError};
8use std::collections::{HashMap, HashSet};
9use std::sync::{Arc, RwLock};
10use thiserror::Error;
11
12use super::component_framework::{ComponentDependency, PluggableComponent};
13
14#[derive(Debug)]
19pub struct DependencyResolver {
20 dependency_graph: Arc<RwLock<DependencyGraph>>,
22 version_solver: VersionConstraintSolver,
24 injection_registry: Arc<RwLock<DependencyInjectionRegistry>>,
26 config: DependencyResolutionConfig,
28 resolution_cache: Arc<RwLock<HashMap<String, ResolutionResult>>>,
30 stats: Arc<RwLock<DependencyStatistics>>,
32}
33
34impl DependencyResolver {
35 #[must_use]
37 pub fn new() -> Self {
38 Self {
39 dependency_graph: Arc::new(RwLock::new(DependencyGraph::new())),
40 version_solver: VersionConstraintSolver::new(),
41 injection_registry: Arc::new(RwLock::new(DependencyInjectionRegistry::new())),
42 config: DependencyResolutionConfig::default(),
43 resolution_cache: Arc::new(RwLock::new(HashMap::new())),
44 stats: Arc::new(RwLock::new(DependencyStatistics::new())),
45 }
46 }
47
48 pub fn add_component_dependencies(
50 &self,
51 component_id: &str,
52 component_type: &str,
53 version: &str,
54 dependencies: Vec<ComponentDependency>,
55 ) -> SklResult<()> {
56 let mut graph = self
57 .dependency_graph
58 .write()
59 .unwrap_or_else(|e| e.into_inner());
60 let mut stats = self.stats.write().unwrap_or_else(|e| e.into_inner());
61
62 let node = DependencyNode {
63 component_id: component_id.to_string(),
64 component_type: component_type.to_string(),
65 version: version.to_string(),
66 dependencies: dependencies.clone(),
67 resolved_dependencies: HashMap::new(),
68 dependency_state: DependencyState::Unresolved,
69 };
70
71 graph.add_node(component_id.to_string(), node);
72
73 for dependency in dependencies {
75 graph.add_edge(component_id.to_string(), dependency.component_type.clone());
76 stats.total_dependencies += 1;
77 }
78
79 stats.total_components += 1;
80 Ok(())
81 }
82
83 pub fn resolve_dependencies(&self, component_id: &str) -> SklResult<ResolutionResult> {
85 let mut stats = self.stats.write().unwrap_or_else(|e| e.into_inner());
86 stats.resolution_attempts += 1;
87
88 {
90 let cache = self
91 .resolution_cache
92 .read()
93 .unwrap_or_else(|e| e.into_inner());
94 if let Some(cached_result) = cache.get(component_id) {
95 if !self.is_cache_expired(&cached_result.resolution_time) {
96 stats.cache_hits += 1;
97 return Ok(cached_result.clone());
98 }
99 }
100 }
101
102 stats.cache_misses += 1;
103
104 let resolution_order = self.topological_sort(component_id)?;
106 let version_constraints = self.collect_version_constraints(&resolution_order)?;
107 let resolved_versions = self.version_solver.solve_constraints(version_constraints)?;
108
109 let result = ResolutionResult {
110 component_id: component_id.to_string(),
111 resolution_order,
112 resolved_versions,
113 resolution_time: std::time::Instant::now(),
114 dependency_conflicts: self.detect_conflicts(component_id)?,
115 optional_dependencies: self.collect_optional_dependencies(component_id)?,
116 };
117
118 {
120 let mut cache = self
121 .resolution_cache
122 .write()
123 .unwrap_or_else(|e| e.into_inner());
124 cache.insert(component_id.to_string(), result.clone());
125 }
126
127 self.update_dependency_states(component_id, &result)?;
129
130 stats.successful_resolutions += 1;
131 Ok(result)
132 }
133
134 pub fn topological_sort(&self, component_id: &str) -> SklResult<Vec<String>> {
136 let graph = self
137 .dependency_graph
138 .read()
139 .unwrap_or_else(|e| e.into_inner());
140 let mut visited = HashSet::new();
141 let mut visiting = HashSet::new();
142 let mut order = Vec::new();
143
144 self.topological_sort_visit(
145 &graph,
146 component_id,
147 &mut visited,
148 &mut visiting,
149 &mut order,
150 )?;
151
152 Ok(order)
153 }
154
155 pub fn detect_circular_dependencies(&self) -> SklResult<Vec<CircularDependency>> {
157 let graph = self
158 .dependency_graph
159 .read()
160 .unwrap_or_else(|e| e.into_inner());
161 let mut circular_deps = Vec::new();
162 let mut visited = HashSet::new();
163 let mut visiting = HashSet::new();
164
165 for component_id in graph.nodes.keys() {
166 if !visited.contains(component_id) {
167 if let Err(DependencyError::CircularDependency { cycle }) = self.detect_cycles(
168 &graph,
169 component_id,
170 &mut visited,
171 &mut visiting,
172 &mut Vec::new(),
173 ) {
174 circular_deps.push(CircularDependency {
175 cycle_components: cycle,
176 detection_time: std::time::Instant::now(),
177 });
178 }
179 }
180 }
181
182 Ok(circular_deps)
183 }
184
185 pub fn check_compatibility(
187 &self,
188 component_a: &str,
189 component_b: &str,
190 ) -> SklResult<CompatibilityResult> {
191 let graph = self
192 .dependency_graph
193 .read()
194 .unwrap_or_else(|e| e.into_inner());
195
196 let node_a = graph.nodes.get(component_a).ok_or_else(|| {
197 SklearsError::InvalidInput(format!("Component {component_a} not found"))
198 })?;
199 let node_b = graph.nodes.get(component_b).ok_or_else(|| {
200 SklearsError::InvalidInput(format!("Component {component_b} not found"))
201 })?;
202
203 let mut compatibility_issues = Vec::new();
204
205 for dep in &node_a.dependencies {
207 if dep.component_type == node_b.component_type
208 && !self
209 .version_solver
210 .is_version_compatible(&node_b.version, &dep.version_requirement)
211 {
212 compatibility_issues.push(CompatibilityIssue {
213 issue_type: CompatibilityIssueType::VersionMismatch,
214 component_a: component_a.to_string(),
215 component_b: component_b.to_string(),
216 description: format!(
217 "Version mismatch: {} requires {} but {} has version {}",
218 component_a, dep.version_requirement, component_b, node_b.version
219 ),
220 });
221 }
222 }
223
224 self.check_capability_compatibility(node_a, node_b, &mut compatibility_issues)?;
226
227 Ok(CompatibilityResult {
228 compatible: compatibility_issues.is_empty(),
229 issues: compatibility_issues,
230 compatibility_score: self.calculate_compatibility_score(node_a, node_b),
231 })
232 }
233
234 pub fn register_injection_provider<T: 'static>(
236 &self,
237 provider: Box<dyn DependencyProvider<T>>,
238 ) -> SklResult<()> {
239 let mut registry = self
240 .injection_registry
241 .write()
242 .unwrap_or_else(|e| e.into_inner());
243 registry.register_provider(std::any::TypeId::of::<T>(), provider)?;
244 Ok(())
245 }
246
247 pub fn inject_dependencies(&self, component: &mut dyn PluggableComponent) -> SklResult<()> {
249 let registry = self
250 .injection_registry
251 .read()
252 .unwrap_or_else(|e| e.into_inner());
253
254 let dependencies = component.dependencies();
256
257 for dependency in dependencies {
258 if let Some(_provider) = registry.get_provider(&dependency.component_type) {
259 } else if !dependency.optional {
262 return Err(SklearsError::InvalidInput(format!(
263 "Required dependency provider not found: {}",
264 dependency.component_type
265 )));
266 }
267 }
268
269 Ok(())
270 }
271
272 #[must_use]
274 pub fn get_statistics(&self) -> DependencyStatistics {
275 let stats = self.stats.read().unwrap_or_else(|e| e.into_inner());
276 stats.clone()
277 }
278
279 pub fn clear_cache(&self) {
281 let mut cache = self
282 .resolution_cache
283 .write()
284 .unwrap_or_else(|e| e.into_inner());
285 cache.clear();
286 }
287
288 fn topological_sort_visit(
290 &self,
291 graph: &DependencyGraph,
292 component_id: &str,
293 visited: &mut HashSet<String>,
294 visiting: &mut HashSet<String>,
295 order: &mut Vec<String>,
296 ) -> SklResult<()> {
297 if visiting.contains(component_id) {
298 return Err(DependencyError::CircularDependency {
299 cycle: vec![component_id.to_string()],
300 }
301 .into());
302 }
303
304 if visited.contains(component_id) {
305 return Ok(());
306 }
307
308 visiting.insert(component_id.to_string());
309
310 if let Some(edges) = graph.edges.get(component_id) {
311 for dependency in edges {
312 self.topological_sort_visit(graph, dependency, visited, visiting, order)?;
313 }
314 }
315
316 visiting.remove(component_id);
317 visited.insert(component_id.to_string());
318 order.push(component_id.to_string());
319
320 Ok(())
321 }
322
323 fn detect_cycles(
324 &self,
325 graph: &DependencyGraph,
326 component_id: &str,
327 visited: &mut HashSet<String>,
328 visiting: &mut HashSet<String>,
329 path: &mut Vec<String>,
330 ) -> Result<(), DependencyError> {
331 if visiting.contains(component_id) {
332 let cycle_start = path.iter().position(|id| id == component_id).unwrap_or(0);
333 let cycle = path[cycle_start..].to_vec();
334 return Err(DependencyError::CircularDependency { cycle });
335 }
336
337 if visited.contains(component_id) {
338 return Ok(());
339 }
340
341 visiting.insert(component_id.to_string());
342 path.push(component_id.to_string());
343
344 if let Some(edges) = graph.edges.get(component_id) {
345 for dependency in edges {
346 self.detect_cycles(graph, dependency, visited, visiting, path)?;
347 }
348 }
349
350 visiting.remove(component_id);
351 visited.insert(component_id.to_string());
352 path.pop();
353
354 Ok(())
355 }
356
357 fn collect_version_constraints(
358 &self,
359 components: &[String],
360 ) -> SklResult<Vec<VersionConstraint>> {
361 let graph = self
362 .dependency_graph
363 .read()
364 .unwrap_or_else(|e| e.into_inner());
365 let mut constraints = Vec::new();
366
367 for component_id in components {
368 if let Some(node) = graph.nodes.get(component_id) {
369 for dependency in &node.dependencies {
370 constraints.push(VersionConstraint {
371 component_type: dependency.component_type.clone(),
372 constraint: dependency.version_requirement.clone(),
373 required_by: component_id.clone(),
374 });
375 }
376 }
377 }
378
379 Ok(constraints)
380 }
381
382 fn detect_conflicts(&self, component_id: &str) -> SklResult<Vec<DependencyConflict>> {
383 Ok(Vec::new())
385 }
386
387 fn collect_optional_dependencies(&self, component_id: &str) -> SklResult<Vec<String>> {
388 let graph = self
389 .dependency_graph
390 .read()
391 .unwrap_or_else(|e| e.into_inner());
392 let mut optional_deps = Vec::new();
393
394 if let Some(node) = graph.nodes.get(component_id) {
395 for dependency in &node.dependencies {
396 if dependency.optional {
397 optional_deps.push(dependency.component_type.clone());
398 }
399 }
400 }
401
402 Ok(optional_deps)
403 }
404
405 fn update_dependency_states(
406 &self,
407 component_id: &str,
408 result: &ResolutionResult,
409 ) -> SklResult<()> {
410 let mut graph = self
411 .dependency_graph
412 .write()
413 .unwrap_or_else(|e| e.into_inner());
414
415 if let Some(node) = graph.nodes.get_mut(component_id) {
416 node.dependency_state = if result.dependency_conflicts.is_empty() {
417 DependencyState::Resolved
418 } else {
419 DependencyState::Conflicted
420 };
421 }
422
423 Ok(())
424 }
425
426 fn is_cache_expired(&self, resolution_time: &std::time::Instant) -> bool {
427 resolution_time.elapsed() > self.config.cache_expiry_duration
428 }
429
430 fn check_capability_compatibility(
431 &self,
432 node_a: &DependencyNode,
433 node_b: &DependencyNode,
434 issues: &mut Vec<CompatibilityIssue>,
435 ) -> SklResult<()> {
436 for dependency in &node_a.dependencies {
438 if dependency.component_type == node_b.component_type {
439 }
442 }
443 Ok(())
444 }
445
446 fn calculate_compatibility_score(
447 &self,
448 node_a: &DependencyNode,
449 node_b: &DependencyNode,
450 ) -> f64 {
451 1.0 }
454}
455
456#[derive(Debug)]
458pub struct DependencyGraph {
459 pub nodes: HashMap<String, DependencyNode>,
461 pub edges: HashMap<String, Vec<String>>,
463}
464
465impl DependencyGraph {
466 #[must_use]
467 pub fn new() -> Self {
468 Self {
469 nodes: HashMap::new(),
470 edges: HashMap::new(),
471 }
472 }
473
474 pub fn add_node(&mut self, component_id: String, node: DependencyNode) {
475 self.nodes.insert(component_id, node);
476 }
477
478 pub fn add_edge(&mut self, from: String, to: String) {
479 self.edges.entry(from).or_default().push(to);
480 }
481}
482
483#[derive(Debug, Clone)]
485pub struct DependencyNode {
486 pub component_id: String,
488 pub component_type: String,
490 pub version: String,
492 pub dependencies: Vec<ComponentDependency>,
494 pub resolved_dependencies: HashMap<String, String>,
496 pub dependency_state: DependencyState,
498}
499
500#[derive(Debug, Clone, PartialEq, Eq)]
502pub enum DependencyState {
503 Unresolved,
505 Resolved,
507 Conflicted,
509 PartiallyResolved,
511}
512
513pub struct VersionConstraintSolver {
515 constraint_operators: HashMap<String, Box<dyn Fn(&str, &str) -> bool + Send + Sync>>,
517}
518
519impl VersionConstraintSolver {
520 #[must_use]
521 pub fn new() -> Self {
522 let mut operators = HashMap::new();
523
524 operators.insert(
526 "=".to_string(),
527 Box::new(|version: &str, constraint: &str| version == constraint)
528 as Box<dyn Fn(&str, &str) -> bool + Send + Sync>,
529 );
530
531 operators.insert(
532 ">=".to_string(),
533 Box::new(|version: &str, constraint: &str| {
534 Self::compare_versions(version, constraint) >= 0
535 }) as Box<dyn Fn(&str, &str) -> bool + Send + Sync>,
536 );
537
538 Self {
539 constraint_operators: operators,
540 }
541 }
542
543 pub fn solve_constraints(
544 &self,
545 constraints: Vec<VersionConstraint>,
546 ) -> SklResult<HashMap<String, String>> {
547 let mut resolved_versions = HashMap::new();
548 let mut component_constraints: HashMap<String, Vec<String>> = HashMap::new();
549
550 for constraint in constraints {
552 component_constraints
553 .entry(constraint.component_type.clone())
554 .or_default()
555 .push(constraint.constraint);
556 }
557
558 for (component_type, version_constraints) in component_constraints {
560 let resolved_version = self.resolve_version_constraints(version_constraints)?;
561 resolved_versions.insert(component_type, resolved_version);
562 }
563
564 Ok(resolved_versions)
565 }
566
567 #[must_use]
568 pub fn is_version_compatible(&self, version: &str, constraint: &str) -> bool {
569 VersionConstraintSolver::compare_versions(version, constraint) >= 0
572 }
573
574 fn resolve_version_constraints(&self, constraints: Vec<String>) -> SklResult<String> {
575 Ok(constraints
578 .into_iter()
579 .max()
580 .unwrap_or_else(|| "1.0.0".to_string()))
581 }
582
583 fn compare_versions(version_a: &str, version_b: &str) -> i32 {
584 let parts_a: Vec<u32> = version_a
585 .split('.')
586 .filter_map(|s| s.parse().ok())
587 .collect();
588 let parts_b: Vec<u32> = version_b
589 .split('.')
590 .filter_map(|s| s.parse().ok())
591 .collect();
592
593 for i in 0..std::cmp::max(parts_a.len(), parts_b.len()) {
594 let a = parts_a.get(i).copied().unwrap_or(0);
595 let b = parts_b.get(i).copied().unwrap_or(0);
596
597 match a.cmp(&b) {
598 std::cmp::Ordering::Less => return -1,
599 std::cmp::Ordering::Greater => return 1,
600 std::cmp::Ordering::Equal => {}
601 }
602 }
603
604 0
605 }
606}
607
608impl std::fmt::Debug for VersionConstraintSolver {
609 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
610 f.debug_struct("VersionConstraintSolver")
611 .field(
612 "constraint_operators",
613 &format!("<{} operators>", self.constraint_operators.len()),
614 )
615 .finish()
616 }
617}
618
619#[derive(Debug, Clone)]
621pub struct VersionConstraint {
622 pub component_type: String,
624 pub constraint: String,
626 pub required_by: String,
628}
629
630#[derive(Debug)]
632pub struct DependencyInjectionRegistry {
633 providers: HashMap<std::any::TypeId, Box<dyn std::any::Any + Send + Sync>>,
635 provider_metadata: HashMap<String, ProviderMetadata>,
637}
638
639impl DependencyInjectionRegistry {
640 #[must_use]
641 pub fn new() -> Self {
642 Self {
643 providers: HashMap::new(),
644 provider_metadata: HashMap::new(),
645 }
646 }
647
648 pub fn register_provider<T: 'static>(
649 &mut self,
650 type_id: std::any::TypeId,
651 provider: Box<dyn DependencyProvider<T>>,
652 ) -> SklResult<()> {
653 self.providers.insert(type_id, Box::new(provider));
654 Ok(())
655 }
656
657 #[must_use]
658 pub fn get_provider(&self, component_type: &str) -> Option<&dyn std::any::Any> {
659 None
662 }
663}
664
665pub trait DependencyProvider<T>: Send + Sync {
667 fn inject(&self, component: &mut dyn PluggableComponent) -> SklResult<()>;
669
670 fn metadata(&self) -> ProviderMetadata;
672}
673
674#[derive(Debug, Clone)]
676pub struct ProviderMetadata {
677 pub name: String,
679 pub provided_type: String,
681 pub version: String,
683}
684
685#[derive(Debug, Clone)]
687pub struct ResolutionResult {
688 pub component_id: String,
690 pub resolution_order: Vec<String>,
692 pub resolved_versions: HashMap<String, String>,
694 pub resolution_time: std::time::Instant,
696 pub dependency_conflicts: Vec<DependencyConflict>,
698 pub optional_dependencies: Vec<String>,
700}
701
702#[derive(Debug, Clone)]
704pub struct DependencyConflict {
705 pub components: Vec<String>,
707 pub conflict_type: ConflictType,
709 pub description: String,
711}
712
713#[derive(Debug, Clone)]
715pub enum ConflictType {
716 VersionConflict,
718 CapabilityConflict,
720 ResourceConflict,
722 CircularDependency,
724}
725
726#[derive(Debug, Clone)]
728pub struct CircularDependency {
729 pub cycle_components: Vec<String>,
731 pub detection_time: std::time::Instant,
733}
734
735#[derive(Debug, Clone)]
737pub struct CompatibilityResult {
738 pub compatible: bool,
740 pub issues: Vec<CompatibilityIssue>,
742 pub compatibility_score: f64,
744}
745
746#[derive(Debug, Clone)]
748pub struct CompatibilityIssue {
749 pub issue_type: CompatibilityIssueType,
751 pub component_a: String,
753 pub component_b: String,
755 pub description: String,
757}
758
759#[derive(Debug, Clone)]
761pub enum CompatibilityIssueType {
762 VersionMismatch,
764 MissingCapability,
766 ResourceConflict,
768 ConfigurationConflict,
770}
771
772#[derive(Debug, Clone)]
774pub struct DependencyResolutionConfig {
775 pub enable_circular_detection: bool,
777 pub max_resolution_depth: usize,
779 pub cache_expiry_duration: std::time::Duration,
781 pub allow_version_downgrades: bool,
783 pub strict_version_matching: bool,
785}
786
787impl Default for DependencyResolutionConfig {
788 fn default() -> Self {
789 Self {
790 enable_circular_detection: true,
791 max_resolution_depth: 50,
792 cache_expiry_duration: std::time::Duration::from_secs(300),
793 allow_version_downgrades: false,
794 strict_version_matching: false,
795 }
796 }
797}
798
799#[derive(Debug, Clone)]
801pub struct DependencyStatistics {
802 pub total_components: u64,
804 pub total_dependencies: u64,
806 pub resolution_attempts: u64,
808 pub successful_resolutions: u64,
810 pub cache_hits: u64,
812 pub cache_misses: u64,
814 pub circular_dependencies_detected: u64,
816 pub average_resolution_time: std::time::Duration,
818}
819
820impl DependencyStatistics {
821 #[must_use]
822 pub fn new() -> Self {
823 Self {
824 total_components: 0,
825 total_dependencies: 0,
826 resolution_attempts: 0,
827 successful_resolutions: 0,
828 cache_hits: 0,
829 cache_misses: 0,
830 circular_dependencies_detected: 0,
831 average_resolution_time: std::time::Duration::from_secs(0),
832 }
833 }
834
835 #[must_use]
837 pub fn cache_hit_rate(&self) -> f64 {
838 if self.resolution_attempts == 0 {
839 0.0
840 } else {
841 self.cache_hits as f64 / self.resolution_attempts as f64
842 }
843 }
844
845 #[must_use]
847 pub fn resolution_success_rate(&self) -> f64 {
848 if self.resolution_attempts == 0 {
849 0.0
850 } else {
851 self.successful_resolutions as f64 / self.resolution_attempts as f64
852 }
853 }
854}
855
856#[derive(Debug, Error)]
858pub enum DependencyError {
859 #[error("Circular dependency detected: {cycle:?}")]
860 CircularDependency { cycle: Vec<String> },
861
862 #[error("Version constraint conflict: {0}")]
863 VersionConstraintConflict(String),
864
865 #[error("Dependency not found: {0}")]
866 DependencyNotFound(String),
867
868 #[error("Resolution failed: {0}")]
869 ResolutionFailed(String),
870
871 #[error("Injection failed: {0}")]
872 InjectionFailed(String),
873}
874
875impl From<DependencyError> for SklearsError {
876 fn from(err: DependencyError) -> Self {
877 SklearsError::InvalidInput(err.to_string())
878 }
879}
880
881impl Default for DependencyResolver {
882 fn default() -> Self {
883 Self::new()
884 }
885}
886
887impl Default for DependencyGraph {
888 fn default() -> Self {
889 Self::new()
890 }
891}
892
893impl Default for VersionConstraintSolver {
894 fn default() -> Self {
895 Self::new()
896 }
897}
898
899impl Default for DependencyInjectionRegistry {
900 fn default() -> Self {
901 Self::new()
902 }
903}
904
905impl Default for DependencyStatistics {
906 fn default() -> Self {
907 Self::new()
908 }
909}
910
911#[allow(non_snake_case)]
912#[cfg(test)]
913mod tests {
914 use super::*;
915
916 #[test]
917 fn test_dependency_resolver_creation() {
918 let resolver = DependencyResolver::new();
919 let stats = resolver.get_statistics();
920 assert_eq!(stats.total_components, 0);
921 assert_eq!(stats.total_dependencies, 0);
922 }
923
924 #[test]
925 fn test_version_constraint_solver() {
926 let solver = VersionConstraintSolver::new();
927
928 assert!(solver.is_version_compatible("1.2.0", "1.0.0"));
929 assert!(!solver.is_version_compatible("1.0.0", "1.2.0"));
930 assert!(solver.is_version_compatible("1.0.0", "1.0.0"));
931 }
932
933 #[test]
934 fn test_dependency_graph() {
935 let mut graph = DependencyGraph::new();
936
937 let node = DependencyNode {
938 component_id: "comp1".to_string(),
939 component_type: "type1".to_string(),
940 version: "1.0.0".to_string(),
941 dependencies: Vec::new(),
942 resolved_dependencies: HashMap::new(),
943 dependency_state: DependencyState::Unresolved,
944 };
945
946 graph.add_node("comp1".to_string(), node);
947 graph.add_edge("comp1".to_string(), "comp2".to_string());
948
949 assert!(graph.nodes.contains_key("comp1"));
950 assert_eq!(
951 graph
952 .edges
953 .get("comp1")
954 .expect("operation should succeed")
955 .len(),
956 1
957 );
958 }
959
960 #[test]
961 fn test_circular_dependency_detection() {
962 let resolver = DependencyResolver::new();
963
964 let dep1 = ComponentDependency {
966 component_type: "comp2".to_string(),
967 version_requirement: "1.0.0".to_string(),
968 optional: false,
969 required_capabilities: Vec::new(),
970 };
971
972 let dep2 = ComponentDependency {
973 component_type: "comp1".to_string(),
974 version_requirement: "1.0.0".to_string(),
975 optional: false,
976 required_capabilities: Vec::new(),
977 };
978
979 resolver
980 .add_component_dependencies("comp1", "type1", "1.0.0", vec![dep1])
981 .unwrap_or_default();
982 resolver
983 .add_component_dependencies("comp2", "type2", "1.0.0", vec![dep2])
984 .unwrap_or_default();
985
986 let circular_deps = resolver.detect_circular_dependencies().unwrap_or_default();
987 assert!(!circular_deps.is_empty());
988 }
989
990 #[test]
991 fn test_topological_sort() {
992 let resolver = DependencyResolver::new();
993
994 let dep1 = ComponentDependency {
996 component_type: "comp2".to_string(),
997 version_requirement: "1.0.0".to_string(),
998 optional: false,
999 required_capabilities: Vec::new(),
1000 };
1001
1002 let dep2 = ComponentDependency {
1003 component_type: "comp3".to_string(),
1004 version_requirement: "1.0.0".to_string(),
1005 optional: false,
1006 required_capabilities: Vec::new(),
1007 };
1008
1009 resolver
1010 .add_component_dependencies("comp1", "type1", "1.0.0", vec![dep1])
1011 .unwrap_or_default();
1012 resolver
1013 .add_component_dependencies("comp2", "type2", "1.0.0", vec![dep2])
1014 .unwrap_or_default();
1015 resolver
1016 .add_component_dependencies("comp3", "type3", "1.0.0", vec![])
1017 .unwrap_or_default();
1018
1019 let order = resolver.topological_sort("comp1").unwrap_or_default();
1020
1021 assert_eq!(order.len(), 3);
1023 assert_eq!(order[0], "comp3");
1024 assert_eq!(order[1], "comp2");
1025 assert_eq!(order[2], "comp1");
1026 }
1027}