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.dependency_graph.write().unwrap();
57 let mut stats = self.stats.write().unwrap();
58
59 let node = DependencyNode {
60 component_id: component_id.to_string(),
61 component_type: component_type.to_string(),
62 version: version.to_string(),
63 dependencies: dependencies.clone(),
64 resolved_dependencies: HashMap::new(),
65 dependency_state: DependencyState::Unresolved,
66 };
67
68 graph.add_node(component_id.to_string(), node);
69
70 for dependency in dependencies {
72 graph.add_edge(component_id.to_string(), dependency.component_type.clone());
73 stats.total_dependencies += 1;
74 }
75
76 stats.total_components += 1;
77 Ok(())
78 }
79
80 pub fn resolve_dependencies(&self, component_id: &str) -> SklResult<ResolutionResult> {
82 let mut stats = self.stats.write().unwrap();
83 stats.resolution_attempts += 1;
84
85 {
87 let cache = self.resolution_cache.read().unwrap();
88 if let Some(cached_result) = cache.get(component_id) {
89 if !self.is_cache_expired(&cached_result.resolution_time) {
90 stats.cache_hits += 1;
91 return Ok(cached_result.clone());
92 }
93 }
94 }
95
96 stats.cache_misses += 1;
97
98 let resolution_order = self.topological_sort(component_id)?;
100 let version_constraints = self.collect_version_constraints(&resolution_order)?;
101 let resolved_versions = self.version_solver.solve_constraints(version_constraints)?;
102
103 let result = ResolutionResult {
104 component_id: component_id.to_string(),
105 resolution_order,
106 resolved_versions,
107 resolution_time: std::time::Instant::now(),
108 dependency_conflicts: self.detect_conflicts(component_id)?,
109 optional_dependencies: self.collect_optional_dependencies(component_id)?,
110 };
111
112 {
114 let mut cache = self.resolution_cache.write().unwrap();
115 cache.insert(component_id.to_string(), result.clone());
116 }
117
118 self.update_dependency_states(component_id, &result)?;
120
121 stats.successful_resolutions += 1;
122 Ok(result)
123 }
124
125 pub fn topological_sort(&self, component_id: &str) -> SklResult<Vec<String>> {
127 let graph = self.dependency_graph.read().unwrap();
128 let mut visited = HashSet::new();
129 let mut visiting = HashSet::new();
130 let mut order = Vec::new();
131
132 self.topological_sort_visit(
133 &graph,
134 component_id,
135 &mut visited,
136 &mut visiting,
137 &mut order,
138 )?;
139
140 Ok(order)
141 }
142
143 pub fn detect_circular_dependencies(&self) -> SklResult<Vec<CircularDependency>> {
145 let graph = self.dependency_graph.read().unwrap();
146 let mut circular_deps = Vec::new();
147 let mut visited = HashSet::new();
148 let mut visiting = HashSet::new();
149
150 for component_id in graph.nodes.keys() {
151 if !visited.contains(component_id) {
152 if let Err(DependencyError::CircularDependency { cycle }) = self.detect_cycles(
153 &graph,
154 component_id,
155 &mut visited,
156 &mut visiting,
157 &mut Vec::new(),
158 ) {
159 circular_deps.push(CircularDependency {
160 cycle_components: cycle,
161 detection_time: std::time::Instant::now(),
162 });
163 }
164 }
165 }
166
167 Ok(circular_deps)
168 }
169
170 pub fn check_compatibility(
172 &self,
173 component_a: &str,
174 component_b: &str,
175 ) -> SklResult<CompatibilityResult> {
176 let graph = self.dependency_graph.read().unwrap();
177
178 let node_a = graph.nodes.get(component_a).ok_or_else(|| {
179 SklearsError::InvalidInput(format!("Component {component_a} not found"))
180 })?;
181 let node_b = graph.nodes.get(component_b).ok_or_else(|| {
182 SklearsError::InvalidInput(format!("Component {component_b} not found"))
183 })?;
184
185 let mut compatibility_issues = Vec::new();
186
187 for dep in &node_a.dependencies {
189 if dep.component_type == node_b.component_type
190 && !self
191 .version_solver
192 .is_version_compatible(&node_b.version, &dep.version_requirement)
193 {
194 compatibility_issues.push(CompatibilityIssue {
195 issue_type: CompatibilityIssueType::VersionMismatch,
196 component_a: component_a.to_string(),
197 component_b: component_b.to_string(),
198 description: format!(
199 "Version mismatch: {} requires {} but {} has version {}",
200 component_a, dep.version_requirement, component_b, node_b.version
201 ),
202 });
203 }
204 }
205
206 self.check_capability_compatibility(node_a, node_b, &mut compatibility_issues)?;
208
209 Ok(CompatibilityResult {
210 compatible: compatibility_issues.is_empty(),
211 issues: compatibility_issues,
212 compatibility_score: self.calculate_compatibility_score(node_a, node_b),
213 })
214 }
215
216 pub fn register_injection_provider<T: 'static>(
218 &self,
219 provider: Box<dyn DependencyProvider<T>>,
220 ) -> SklResult<()> {
221 let mut registry = self.injection_registry.write().unwrap();
222 registry.register_provider(std::any::TypeId::of::<T>(), provider)?;
223 Ok(())
224 }
225
226 pub fn inject_dependencies(&self, component: &mut dyn PluggableComponent) -> SklResult<()> {
228 let registry = self.injection_registry.read().unwrap();
229
230 let dependencies = component.dependencies();
232
233 for dependency in dependencies {
234 if let Some(_provider) = registry.get_provider(&dependency.component_type) {
235 } else if !dependency.optional {
238 return Err(SklearsError::InvalidInput(format!(
239 "Required dependency provider not found: {}",
240 dependency.component_type
241 )));
242 }
243 }
244
245 Ok(())
246 }
247
248 #[must_use]
250 pub fn get_statistics(&self) -> DependencyStatistics {
251 let stats = self.stats.read().unwrap();
252 stats.clone()
253 }
254
255 pub fn clear_cache(&self) {
257 let mut cache = self.resolution_cache.write().unwrap();
258 cache.clear();
259 }
260
261 fn topological_sort_visit(
263 &self,
264 graph: &DependencyGraph,
265 component_id: &str,
266 visited: &mut HashSet<String>,
267 visiting: &mut HashSet<String>,
268 order: &mut Vec<String>,
269 ) -> SklResult<()> {
270 if visiting.contains(component_id) {
271 return Err(DependencyError::CircularDependency {
272 cycle: vec![component_id.to_string()],
273 }
274 .into());
275 }
276
277 if visited.contains(component_id) {
278 return Ok(());
279 }
280
281 visiting.insert(component_id.to_string());
282
283 if let Some(edges) = graph.edges.get(component_id) {
284 for dependency in edges {
285 self.topological_sort_visit(graph, dependency, visited, visiting, order)?;
286 }
287 }
288
289 visiting.remove(component_id);
290 visited.insert(component_id.to_string());
291 order.push(component_id.to_string());
292
293 Ok(())
294 }
295
296 fn detect_cycles(
297 &self,
298 graph: &DependencyGraph,
299 component_id: &str,
300 visited: &mut HashSet<String>,
301 visiting: &mut HashSet<String>,
302 path: &mut Vec<String>,
303 ) -> Result<(), DependencyError> {
304 if visiting.contains(component_id) {
305 let cycle_start = path.iter().position(|id| id == component_id).unwrap_or(0);
306 let cycle = path[cycle_start..].to_vec();
307 return Err(DependencyError::CircularDependency { cycle });
308 }
309
310 if visited.contains(component_id) {
311 return Ok(());
312 }
313
314 visiting.insert(component_id.to_string());
315 path.push(component_id.to_string());
316
317 if let Some(edges) = graph.edges.get(component_id) {
318 for dependency in edges {
319 self.detect_cycles(graph, dependency, visited, visiting, path)?;
320 }
321 }
322
323 visiting.remove(component_id);
324 visited.insert(component_id.to_string());
325 path.pop();
326
327 Ok(())
328 }
329
330 fn collect_version_constraints(
331 &self,
332 components: &[String],
333 ) -> SklResult<Vec<VersionConstraint>> {
334 let graph = self.dependency_graph.read().unwrap();
335 let mut constraints = Vec::new();
336
337 for component_id in components {
338 if let Some(node) = graph.nodes.get(component_id) {
339 for dependency in &node.dependencies {
340 constraints.push(VersionConstraint {
341 component_type: dependency.component_type.clone(),
342 constraint: dependency.version_requirement.clone(),
343 required_by: component_id.clone(),
344 });
345 }
346 }
347 }
348
349 Ok(constraints)
350 }
351
352 fn detect_conflicts(&self, component_id: &str) -> SklResult<Vec<DependencyConflict>> {
353 Ok(Vec::new())
355 }
356
357 fn collect_optional_dependencies(&self, component_id: &str) -> SklResult<Vec<String>> {
358 let graph = self.dependency_graph.read().unwrap();
359 let mut optional_deps = Vec::new();
360
361 if let Some(node) = graph.nodes.get(component_id) {
362 for dependency in &node.dependencies {
363 if dependency.optional {
364 optional_deps.push(dependency.component_type.clone());
365 }
366 }
367 }
368
369 Ok(optional_deps)
370 }
371
372 fn update_dependency_states(
373 &self,
374 component_id: &str,
375 result: &ResolutionResult,
376 ) -> SklResult<()> {
377 let mut graph = self.dependency_graph.write().unwrap();
378
379 if let Some(node) = graph.nodes.get_mut(component_id) {
380 node.dependency_state = if result.dependency_conflicts.is_empty() {
381 DependencyState::Resolved
382 } else {
383 DependencyState::Conflicted
384 };
385 }
386
387 Ok(())
388 }
389
390 fn is_cache_expired(&self, resolution_time: &std::time::Instant) -> bool {
391 resolution_time.elapsed() > self.config.cache_expiry_duration
392 }
393
394 fn check_capability_compatibility(
395 &self,
396 node_a: &DependencyNode,
397 node_b: &DependencyNode,
398 issues: &mut Vec<CompatibilityIssue>,
399 ) -> SklResult<()> {
400 for dependency in &node_a.dependencies {
402 if dependency.component_type == node_b.component_type {
403 }
406 }
407 Ok(())
408 }
409
410 fn calculate_compatibility_score(
411 &self,
412 node_a: &DependencyNode,
413 node_b: &DependencyNode,
414 ) -> f64 {
415 1.0 }
418}
419
420#[derive(Debug)]
422pub struct DependencyGraph {
423 pub nodes: HashMap<String, DependencyNode>,
425 pub edges: HashMap<String, Vec<String>>,
427}
428
429impl DependencyGraph {
430 #[must_use]
431 pub fn new() -> Self {
432 Self {
433 nodes: HashMap::new(),
434 edges: HashMap::new(),
435 }
436 }
437
438 pub fn add_node(&mut self, component_id: String, node: DependencyNode) {
439 self.nodes.insert(component_id, node);
440 }
441
442 pub fn add_edge(&mut self, from: String, to: String) {
443 self.edges.entry(from).or_default().push(to);
444 }
445}
446
447#[derive(Debug, Clone)]
449pub struct DependencyNode {
450 pub component_id: String,
452 pub component_type: String,
454 pub version: String,
456 pub dependencies: Vec<ComponentDependency>,
458 pub resolved_dependencies: HashMap<String, String>,
460 pub dependency_state: DependencyState,
462}
463
464#[derive(Debug, Clone, PartialEq, Eq)]
466pub enum DependencyState {
467 Unresolved,
469 Resolved,
471 Conflicted,
473 PartiallyResolved,
475}
476
477pub struct VersionConstraintSolver {
479 constraint_operators: HashMap<String, Box<dyn Fn(&str, &str) -> bool + Send + Sync>>,
481}
482
483impl VersionConstraintSolver {
484 #[must_use]
485 pub fn new() -> Self {
486 let mut operators = HashMap::new();
487
488 operators.insert(
490 "=".to_string(),
491 Box::new(|version: &str, constraint: &str| version == constraint)
492 as Box<dyn Fn(&str, &str) -> bool + Send + Sync>,
493 );
494
495 operators.insert(
496 ">=".to_string(),
497 Box::new(|version: &str, constraint: &str| {
498 Self::compare_versions(version, constraint) >= 0
499 }) as Box<dyn Fn(&str, &str) -> bool + Send + Sync>,
500 );
501
502 Self {
503 constraint_operators: operators,
504 }
505 }
506
507 pub fn solve_constraints(
508 &self,
509 constraints: Vec<VersionConstraint>,
510 ) -> SklResult<HashMap<String, String>> {
511 let mut resolved_versions = HashMap::new();
512 let mut component_constraints: HashMap<String, Vec<String>> = HashMap::new();
513
514 for constraint in constraints {
516 component_constraints
517 .entry(constraint.component_type.clone())
518 .or_default()
519 .push(constraint.constraint);
520 }
521
522 for (component_type, version_constraints) in component_constraints {
524 let resolved_version = self.resolve_version_constraints(version_constraints)?;
525 resolved_versions.insert(component_type, resolved_version);
526 }
527
528 Ok(resolved_versions)
529 }
530
531 #[must_use]
532 pub fn is_version_compatible(&self, version: &str, constraint: &str) -> bool {
533 VersionConstraintSolver::compare_versions(version, constraint) >= 0
536 }
537
538 fn resolve_version_constraints(&self, constraints: Vec<String>) -> SklResult<String> {
539 Ok(constraints
542 .into_iter()
543 .max()
544 .unwrap_or_else(|| "1.0.0".to_string()))
545 }
546
547 fn compare_versions(version_a: &str, version_b: &str) -> i32 {
548 let parts_a: Vec<u32> = version_a
549 .split('.')
550 .filter_map(|s| s.parse().ok())
551 .collect();
552 let parts_b: Vec<u32> = version_b
553 .split('.')
554 .filter_map(|s| s.parse().ok())
555 .collect();
556
557 for i in 0..std::cmp::max(parts_a.len(), parts_b.len()) {
558 let a = parts_a.get(i).copied().unwrap_or(0);
559 let b = parts_b.get(i).copied().unwrap_or(0);
560
561 match a.cmp(&b) {
562 std::cmp::Ordering::Less => return -1,
563 std::cmp::Ordering::Greater => return 1,
564 std::cmp::Ordering::Equal => {}
565 }
566 }
567
568 0
569 }
570}
571
572impl std::fmt::Debug for VersionConstraintSolver {
573 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
574 f.debug_struct("VersionConstraintSolver")
575 .field(
576 "constraint_operators",
577 &format!("<{} operators>", self.constraint_operators.len()),
578 )
579 .finish()
580 }
581}
582
583#[derive(Debug, Clone)]
585pub struct VersionConstraint {
586 pub component_type: String,
588 pub constraint: String,
590 pub required_by: String,
592}
593
594#[derive(Debug)]
596pub struct DependencyInjectionRegistry {
597 providers: HashMap<std::any::TypeId, Box<dyn std::any::Any + Send + Sync>>,
599 provider_metadata: HashMap<String, ProviderMetadata>,
601}
602
603impl DependencyInjectionRegistry {
604 #[must_use]
605 pub fn new() -> Self {
606 Self {
607 providers: HashMap::new(),
608 provider_metadata: HashMap::new(),
609 }
610 }
611
612 pub fn register_provider<T: 'static>(
613 &mut self,
614 type_id: std::any::TypeId,
615 provider: Box<dyn DependencyProvider<T>>,
616 ) -> SklResult<()> {
617 self.providers.insert(type_id, Box::new(provider));
618 Ok(())
619 }
620
621 #[must_use]
622 pub fn get_provider(&self, component_type: &str) -> Option<&dyn std::any::Any> {
623 None
626 }
627}
628
629pub trait DependencyProvider<T>: Send + Sync {
631 fn inject(&self, component: &mut dyn PluggableComponent) -> SklResult<()>;
633
634 fn metadata(&self) -> ProviderMetadata;
636}
637
638#[derive(Debug, Clone)]
640pub struct ProviderMetadata {
641 pub name: String,
643 pub provided_type: String,
645 pub version: String,
647}
648
649#[derive(Debug, Clone)]
651pub struct ResolutionResult {
652 pub component_id: String,
654 pub resolution_order: Vec<String>,
656 pub resolved_versions: HashMap<String, String>,
658 pub resolution_time: std::time::Instant,
660 pub dependency_conflicts: Vec<DependencyConflict>,
662 pub optional_dependencies: Vec<String>,
664}
665
666#[derive(Debug, Clone)]
668pub struct DependencyConflict {
669 pub components: Vec<String>,
671 pub conflict_type: ConflictType,
673 pub description: String,
675}
676
677#[derive(Debug, Clone)]
679pub enum ConflictType {
680 VersionConflict,
682 CapabilityConflict,
684 ResourceConflict,
686 CircularDependency,
688}
689
690#[derive(Debug, Clone)]
692pub struct CircularDependency {
693 pub cycle_components: Vec<String>,
695 pub detection_time: std::time::Instant,
697}
698
699#[derive(Debug, Clone)]
701pub struct CompatibilityResult {
702 pub compatible: bool,
704 pub issues: Vec<CompatibilityIssue>,
706 pub compatibility_score: f64,
708}
709
710#[derive(Debug, Clone)]
712pub struct CompatibilityIssue {
713 pub issue_type: CompatibilityIssueType,
715 pub component_a: String,
717 pub component_b: String,
719 pub description: String,
721}
722
723#[derive(Debug, Clone)]
725pub enum CompatibilityIssueType {
726 VersionMismatch,
728 MissingCapability,
730 ResourceConflict,
732 ConfigurationConflict,
734}
735
736#[derive(Debug, Clone)]
738pub struct DependencyResolutionConfig {
739 pub enable_circular_detection: bool,
741 pub max_resolution_depth: usize,
743 pub cache_expiry_duration: std::time::Duration,
745 pub allow_version_downgrades: bool,
747 pub strict_version_matching: bool,
749}
750
751impl Default for DependencyResolutionConfig {
752 fn default() -> Self {
753 Self {
754 enable_circular_detection: true,
755 max_resolution_depth: 50,
756 cache_expiry_duration: std::time::Duration::from_secs(300),
757 allow_version_downgrades: false,
758 strict_version_matching: false,
759 }
760 }
761}
762
763#[derive(Debug, Clone)]
765pub struct DependencyStatistics {
766 pub total_components: u64,
768 pub total_dependencies: u64,
770 pub resolution_attempts: u64,
772 pub successful_resolutions: u64,
774 pub cache_hits: u64,
776 pub cache_misses: u64,
778 pub circular_dependencies_detected: u64,
780 pub average_resolution_time: std::time::Duration,
782}
783
784impl DependencyStatistics {
785 #[must_use]
786 pub fn new() -> Self {
787 Self {
788 total_components: 0,
789 total_dependencies: 0,
790 resolution_attempts: 0,
791 successful_resolutions: 0,
792 cache_hits: 0,
793 cache_misses: 0,
794 circular_dependencies_detected: 0,
795 average_resolution_time: std::time::Duration::from_secs(0),
796 }
797 }
798
799 #[must_use]
801 pub fn cache_hit_rate(&self) -> f64 {
802 if self.resolution_attempts == 0 {
803 0.0
804 } else {
805 self.cache_hits as f64 / self.resolution_attempts as f64
806 }
807 }
808
809 #[must_use]
811 pub fn resolution_success_rate(&self) -> f64 {
812 if self.resolution_attempts == 0 {
813 0.0
814 } else {
815 self.successful_resolutions as f64 / self.resolution_attempts as f64
816 }
817 }
818}
819
820#[derive(Debug, Error)]
822pub enum DependencyError {
823 #[error("Circular dependency detected: {cycle:?}")]
824 CircularDependency { cycle: Vec<String> },
825
826 #[error("Version constraint conflict: {0}")]
827 VersionConstraintConflict(String),
828
829 #[error("Dependency not found: {0}")]
830 DependencyNotFound(String),
831
832 #[error("Resolution failed: {0}")]
833 ResolutionFailed(String),
834
835 #[error("Injection failed: {0}")]
836 InjectionFailed(String),
837}
838
839impl From<DependencyError> for SklearsError {
840 fn from(err: DependencyError) -> Self {
841 SklearsError::InvalidInput(err.to_string())
842 }
843}
844
845impl Default for DependencyResolver {
846 fn default() -> Self {
847 Self::new()
848 }
849}
850
851impl Default for DependencyGraph {
852 fn default() -> Self {
853 Self::new()
854 }
855}
856
857impl Default for VersionConstraintSolver {
858 fn default() -> Self {
859 Self::new()
860 }
861}
862
863impl Default for DependencyInjectionRegistry {
864 fn default() -> Self {
865 Self::new()
866 }
867}
868
869impl Default for DependencyStatistics {
870 fn default() -> Self {
871 Self::new()
872 }
873}
874
875#[allow(non_snake_case)]
876#[cfg(test)]
877mod tests {
878 use super::*;
879
880 #[test]
881 fn test_dependency_resolver_creation() {
882 let resolver = DependencyResolver::new();
883 let stats = resolver.get_statistics();
884 assert_eq!(stats.total_components, 0);
885 assert_eq!(stats.total_dependencies, 0);
886 }
887
888 #[test]
889 fn test_version_constraint_solver() {
890 let solver = VersionConstraintSolver::new();
891
892 assert!(solver.is_version_compatible("1.2.0", "1.0.0"));
893 assert!(!solver.is_version_compatible("1.0.0", "1.2.0"));
894 assert!(solver.is_version_compatible("1.0.0", "1.0.0"));
895 }
896
897 #[test]
898 fn test_dependency_graph() {
899 let mut graph = DependencyGraph::new();
900
901 let node = DependencyNode {
902 component_id: "comp1".to_string(),
903 component_type: "type1".to_string(),
904 version: "1.0.0".to_string(),
905 dependencies: Vec::new(),
906 resolved_dependencies: HashMap::new(),
907 dependency_state: DependencyState::Unresolved,
908 };
909
910 graph.add_node("comp1".to_string(), node);
911 graph.add_edge("comp1".to_string(), "comp2".to_string());
912
913 assert!(graph.nodes.contains_key("comp1"));
914 assert_eq!(graph.edges.get("comp1").unwrap().len(), 1);
915 }
916
917 #[test]
918 fn test_circular_dependency_detection() {
919 let resolver = DependencyResolver::new();
920
921 let dep1 = ComponentDependency {
923 component_type: "comp2".to_string(),
924 version_requirement: "1.0.0".to_string(),
925 optional: false,
926 required_capabilities: Vec::new(),
927 };
928
929 let dep2 = ComponentDependency {
930 component_type: "comp1".to_string(),
931 version_requirement: "1.0.0".to_string(),
932 optional: false,
933 required_capabilities: Vec::new(),
934 };
935
936 resolver
937 .add_component_dependencies("comp1", "type1", "1.0.0", vec![dep1])
938 .unwrap();
939 resolver
940 .add_component_dependencies("comp2", "type2", "1.0.0", vec![dep2])
941 .unwrap();
942
943 let circular_deps = resolver.detect_circular_dependencies().unwrap();
944 assert!(!circular_deps.is_empty());
945 }
946
947 #[test]
948 fn test_topological_sort() {
949 let resolver = DependencyResolver::new();
950
951 let dep1 = ComponentDependency {
953 component_type: "comp2".to_string(),
954 version_requirement: "1.0.0".to_string(),
955 optional: false,
956 required_capabilities: Vec::new(),
957 };
958
959 let dep2 = ComponentDependency {
960 component_type: "comp3".to_string(),
961 version_requirement: "1.0.0".to_string(),
962 optional: false,
963 required_capabilities: Vec::new(),
964 };
965
966 resolver
967 .add_component_dependencies("comp1", "type1", "1.0.0", vec![dep1])
968 .unwrap();
969 resolver
970 .add_component_dependencies("comp2", "type2", "1.0.0", vec![dep2])
971 .unwrap();
972 resolver
973 .add_component_dependencies("comp3", "type3", "1.0.0", vec![])
974 .unwrap();
975
976 let order = resolver.topological_sort("comp1").unwrap();
977
978 assert_eq!(order.len(), 3);
980 assert_eq!(order[0], "comp3");
981 assert_eq!(order[1], "comp2");
982 assert_eq!(order[2], "comp1");
983 }
984}