actr_cli/core/components/
dependency_resolver.rs1use 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 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 if deps[i].spec.alias == deps[j].spec.alias {
91 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 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}