agpm_cli/resolver/
conflict_service.rs1use anyhow::Result;
7use std::collections::HashMap;
8
9use crate::core::ResourceType;
10use crate::manifest::{DetailedDependency, ResourceDependency};
11use crate::version::conflict::{ConflictDetector, VersionConflict};
12
13use super::types::DependencyKey;
14
15#[allow(dead_code)] pub struct ConflictService {
21 detector: ConflictDetector,
22}
23
24impl ConflictService {
25 pub fn new() -> Self {
27 Self {
28 detector: ConflictDetector::new(),
29 }
30 }
31
32 pub fn detect_version_conflicts(
42 &mut self,
43 dependencies: &HashMap<DependencyKey, DetailedDependency>,
44 ) -> Result<Vec<VersionConflict>> {
45 let mut conflicts = Vec::new();
46
47 let mut grouped: HashMap<(ResourceType, String, String, String), Vec<_>> = HashMap::new();
49
50 for (key, dep) in dependencies {
51 let source = dep.source.clone().unwrap_or_default();
52 let tool = dep.tool.clone().unwrap_or_default();
53
54 let group_key = (
55 key.0, dep.path.clone(),
57 source,
58 tool,
59 );
60 grouped.entry(group_key).or_default().push((key, dep));
61 }
62
63 for ((resource_type, path, source, _tool), deps) in grouped {
65 if deps.len() > 1 {
66 let mut conflicting_requirements = Vec::new();
68
69 for (key, dep) in &deps {
70 let requirement = dep.version.clone().unwrap_or_else(|| "latest".to_string());
71
72 conflicting_requirements.push(
73 crate::version::conflict::ConflictingRequirement {
74 required_by: format!("{}/{}", key.0, key.1), requirement,
76 resolved_version: None,
77 },
78 );
79 }
80
81 conflicts.push(VersionConflict {
82 resource: format!("{}/{}/{}", resource_type, path, source),
83 conflicting_requirements,
84 });
85 }
86 }
87
88 Ok(conflicts)
89 }
90
91 pub fn detect_path_conflicts(
101 dependencies: &HashMap<DependencyKey, DetailedDependency>,
102 ) -> Vec<(String, Vec<String>)> {
103 let mut conflicts = Vec::new();
104 let mut install_paths: HashMap<String, Vec<String>> = HashMap::new();
105
106 for (key, dep) in dependencies {
108 let install_path = format!("{}/{}", key.0, key.1); install_paths.entry(install_path.clone()).or_default().push(format!(
110 "{}/{} (from {})",
111 key.0,
112 key.1,
113 dep.source.as_deref().unwrap_or("local")
114 ));
115 }
116
117 for (path, deps) in install_paths {
119 if deps.len() > 1 {
120 conflicts.push((path, deps));
121 }
122 }
123
124 conflicts
125 }
126
127 pub fn has_conflict(
139 &mut self,
140 dependencies: &HashMap<DependencyKey, DetailedDependency>,
141 new_dep: &ResourceDependency,
142 new_key: &DependencyKey,
143 ) -> bool {
144 let (new_path, new_source, new_tool) = match new_dep {
146 ResourceDependency::Simple(path) => (path, None, None),
147 ResourceDependency::Detailed(details) => {
148 (&details.path, details.source.as_deref(), details.tool.as_deref())
149 }
150 };
151
152 for (key, dep) in dependencies {
154 if key.0 == new_key.0 && key.1 != new_key.1 && dep.path == *new_path
157 && dep.source == new_source.map(String::from)
158 && dep.tool == new_tool.map(String::from)
159 {
160 return true;
161 }
162 }
163
164 false
165 }
166}
167
168impl Default for ConflictService {
169 fn default() -> Self {
170 Self::new()
171 }
172}