Skip to main content

actr_cli/core/components/
dependency_resolver.rs

1use actr_config::ManifestConfig;
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: &ManifestConfig) -> 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
33                    .service
34                    .as_ref()
35                    .map(|service| service.name.clone())
36                    .unwrap_or_else(|| dependency.alias.clone()),
37                actr_type: dependency.actr_type.clone(),
38                fingerprint: dependency
39                    .service
40                    .as_ref()
41                    .map(|service| service.fingerprint.clone()),
42            })
43            .collect();
44
45        Ok(specs)
46    }
47
48    async fn resolve_dependencies(
49        &self,
50        specs: &[DependencySpec],
51        service_details: &[ServiceDetails],
52    ) -> Result<Vec<ResolvedDependency>> {
53        let mut resolved = Vec::with_capacity(specs.len());
54
55        for spec in specs {
56            // Find matching service details
57            let matching_details = service_details.iter().find(|details| {
58                details.info.name == spec.name
59                    || details.info.actr_type.to_string_repr() == spec.name
60                    || spec
61                        .actr_type
62                        .as_ref()
63                        .is_some_and(|ty| details.info.actr_type == *ty)
64            });
65
66            let (fingerprint, proto_files) = match matching_details {
67                Some(details) => (
68                    details.info.fingerprint.clone(),
69                    details.proto_files.clone(),
70                ),
71                None => (spec.fingerprint.clone().unwrap_or_default(), Vec::new()),
72            };
73
74            resolved.push(ResolvedDependency {
75                spec: spec.clone(),
76                fingerprint,
77                proto_files,
78            });
79        }
80
81        Ok(resolved)
82    }
83
84    async fn check_conflicts(&self, deps: &[ResolvedDependency]) -> Result<Vec<ConflictReport>> {
85        let mut conflicts = Vec::new();
86
87        for i in 0..deps.len() {
88            for j in (i + 1)..deps.len() {
89                // Conflict if same alias is used
90                if deps[i].spec.alias == deps[j].spec.alias {
91                    // Same alias is always a conflict if they point to different things
92                    if deps[i].spec.name != deps[j].spec.name
93                        || deps[i].fingerprint != deps[j].fingerprint
94                    {
95                        conflicts.push(ConflictReport {
96                            dependency_a: deps[i].spec.alias.clone(),
97                            dependency_b: deps[j].spec.alias.clone(),
98                            conflict_type: ConflictType::VersionConflict,
99                            description: format!(
100                                "Dependency alias '{}' is duplicated with different targets",
101                                deps[i].spec.alias
102                            ),
103                        });
104                        continue;
105                    }
106                }
107
108                // Conflict if same package name has different fingerprints
109                if deps[i].spec.name != deps[j].spec.name {
110                    continue;
111                }
112
113                if !deps[i].fingerprint.is_empty()
114                    && !deps[j].fingerprint.is_empty()
115                    && deps[i].fingerprint != deps[j].fingerprint
116                {
117                    conflicts.push(ConflictReport {
118                        dependency_a: format!("{} ({})", deps[i].spec.name, deps[i].spec.alias),
119                        dependency_b: format!("{} ({})", deps[j].spec.name, deps[j].spec.alias),
120                        conflict_type: ConflictType::FingerprintMismatch,
121                        description: format!(
122                            "Dependency {} has conflicting fingerprints",
123                            deps[i].spec.name
124                        ),
125                    });
126                }
127            }
128        }
129
130        Ok(conflicts)
131    }
132
133    async fn build_dependency_graph(&self, deps: &[ResolvedDependency]) -> Result<DependencyGraph> {
134        let nodes: Vec<String> = deps.iter().map(|d| d.spec.alias.clone()).collect();
135
136        Ok(DependencyGraph {
137            nodes,
138            edges: Vec::new(),
139            has_cycles: false,
140        })
141    }
142}