actr_cli/core/components/
dependency_resolver.rs

1use actr_config::Config;
2use anyhow::Result;
3use async_trait::async_trait;
4
5use super::{
6    ConflictReport, ConflictType, DependencyGraph, DependencyResolver, DependencySpec,
7    ResolvedDependency, ServiceDetails,
8};
9
10pub struct DefaultDependencyResolver;
11
12impl DefaultDependencyResolver {
13    pub fn new() -> Self {
14        Self
15    }
16}
17
18impl Default for DefaultDependencyResolver {
19    fn default() -> Self {
20        Self::new()
21    }
22}
23
24#[async_trait]
25impl DependencyResolver for DefaultDependencyResolver {
26    async fn resolve_spec(&self, config: &Config) -> Result<Vec<DependencySpec>> {
27        let specs: Vec<DependencySpec> = config
28            .dependencies
29            .iter()
30            .map(|dependency| DependencySpec {
31                alias: dependency.alias.clone(),
32                name: dependency.name.clone(),
33                actr_type: dependency.actr_type.clone(),
34                fingerprint: dependency.fingerprint.clone(),
35            })
36            .collect();
37
38        Ok(specs)
39    }
40
41    async fn resolve_dependencies(
42        &self,
43        specs: &[DependencySpec],
44        service_details: &[ServiceDetails],
45    ) -> Result<Vec<ResolvedDependency>> {
46        let mut resolved = Vec::with_capacity(specs.len());
47
48        for spec in specs {
49            // Find matching service details
50            let matching_details = service_details
51                .iter()
52                .find(|details| details.info.name == spec.name);
53
54            let (fingerprint, proto_files) = match matching_details {
55                Some(details) => (
56                    details.info.fingerprint.clone(),
57                    details.proto_files.clone(),
58                ),
59                None => (spec.fingerprint.clone().unwrap_or_default(), Vec::new()),
60            };
61
62            resolved.push(ResolvedDependency {
63                spec: spec.clone(),
64                fingerprint,
65                proto_files,
66            });
67        }
68
69        Ok(resolved)
70    }
71
72    async fn check_conflicts(&self, deps: &[ResolvedDependency]) -> Result<Vec<ConflictReport>> {
73        let mut conflicts = Vec::new();
74
75        for i in 0..deps.len() {
76            for j in (i + 1)..deps.len() {
77                // Conflict if same alias is used
78                if deps[i].spec.alias == deps[j].spec.alias {
79                    // Same alias is always a conflict if they point to different things
80                    if deps[i].spec.name != deps[j].spec.name
81                        || deps[i].fingerprint != deps[j].fingerprint
82                    {
83                        conflicts.push(ConflictReport {
84                            dependency_a: deps[i].spec.alias.clone(),
85                            dependency_b: deps[j].spec.alias.clone(),
86                            conflict_type: ConflictType::VersionConflict,
87                            description: format!(
88                                "Dependency alias '{}' is duplicated with different targets",
89                                deps[i].spec.alias
90                            ),
91                        });
92                        continue;
93                    }
94                }
95
96                // Conflict if same package name has different fingerprints
97                if deps[i].spec.name != deps[j].spec.name {
98                    continue;
99                }
100
101                if !deps[i].fingerprint.is_empty()
102                    && !deps[j].fingerprint.is_empty()
103                    && deps[i].fingerprint != deps[j].fingerprint
104                {
105                    conflicts.push(ConflictReport {
106                        dependency_a: format!("{} ({})", deps[i].spec.name, deps[i].spec.alias),
107                        dependency_b: format!("{} ({})", deps[j].spec.name, deps[j].spec.alias),
108                        conflict_type: ConflictType::FingerprintMismatch,
109                        description: format!(
110                            "Dependency {} has conflicting fingerprints",
111                            deps[i].spec.name
112                        ),
113                    });
114                }
115            }
116        }
117
118        Ok(conflicts)
119    }
120
121    async fn build_dependency_graph(&self, deps: &[ResolvedDependency]) -> Result<DependencyGraph> {
122        let nodes: Vec<String> = deps.iter().map(|d| d.spec.alias.clone()).collect();
123
124        Ok(DependencyGraph {
125            nodes,
126            edges: Vec::new(),
127            has_cycles: false,
128        })
129    }
130}