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