1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
//! Conflict detection service for version and path conflicts.
//!
//! This module provides high-level orchestration for conflict detection,
//! wrapping the lower-level ConflictDetector functionality.
use anyhow::Result;
use std::collections::HashMap;
use crate::core::ResourceType;
use crate::lockfile::ResourceId;
use crate::manifest::{DetailedDependency, ResourceDependency};
use crate::utils::EMPTY_VARIANT_INPUTS_HASH;
use crate::version::conflict::VersionConflict;
use super::types::DependencyKey;
/// Conflict detection service.
///
/// This service provides high-level methods for detecting version conflicts
/// and path conflicts in dependencies.
pub struct ConflictService;
impl ConflictService {
/// Create a new conflict service.
pub fn new() -> Self {
Self
}
/// Detect version conflicts in the provided dependencies.
///
/// # Arguments
///
/// * `dependencies` - Map of dependencies by (type, name) key
///
/// # Returns
///
/// A vector of detected version conflicts
pub fn detect_version_conflicts(
&mut self,
dependencies: &HashMap<DependencyKey, DetailedDependency>,
) -> Result<Vec<VersionConflict>> {
let mut conflicts = Vec::new();
// Group dependencies by (type, path, source, tool) to find version conflicts
let mut grouped: HashMap<(ResourceType, String, String, String), Vec<_>> = HashMap::new();
for (key, dep) in dependencies {
let source = dep.source.clone().unwrap_or_default();
let tool = dep.tool.clone().unwrap_or_default();
let group_key = (
key.0, // resource_type
dep.path.clone(),
source,
tool,
);
grouped.entry(group_key).or_default().push((key, dep));
}
// Check each group for version conflicts
for ((resource_type, path, source, tool), deps) in grouped {
if deps.len() > 1 {
// Multiple versions of the same resource
let mut conflicting_requirements = Vec::new();
for (key, dep) in &deps {
let requirement = dep.version.clone().unwrap_or_else(|| "latest".to_string());
conflicting_requirements.push(
crate::version::conflict::ConflictingRequirement {
required_by: format!("{}/{}", key.0, key.1), // resource_type, name
requirement,
resolved_sha: String::new(), // No SHA available at this stage
resolved_version: None,
parent_version_constraint: None,
parent_resolved_sha: None,
},
);
}
// Create ResourceId for the conflicting resource
let resource_id = ResourceId::new(
path.clone(),
if source.is_empty() {
None
} else {
Some(source.clone())
},
if tool.is_empty() {
None
} else {
Some(tool.clone())
},
resource_type,
EMPTY_VARIANT_INPUTS_HASH.clone(),
);
conflicts.push(VersionConflict {
resource: resource_id,
conflicting_requirements,
});
}
}
Ok(conflicts)
}
/// Detect path conflicts in the provided dependencies.
///
/// # Arguments
///
/// * `dependencies` - Map of dependencies by (type, name) key
///
/// # Returns
///
/// A vector of detected path conflicts
pub fn detect_path_conflicts(
dependencies: &HashMap<DependencyKey, DetailedDependency>,
) -> Vec<(String, Vec<String>)> {
let mut conflicts = Vec::new();
let mut install_paths: HashMap<String, Vec<String>> = HashMap::new();
// Group dependencies by install path
for (key, dep) in dependencies {
let install_path = format!("{}/{}", key.0, key.1); // resource_type, name
install_paths.entry(install_path.clone()).or_default().push(format!(
"{}/{} (from {})",
key.0,
key.1,
dep.source.as_deref().unwrap_or("local")
));
}
// Find paths with multiple dependencies
for (path, deps) in install_paths {
if deps.len() > 1 {
conflicts.push((path, deps));
}
}
conflicts
}
/// Check if a dependency conflicts with existing dependencies.
///
/// # Arguments
///
/// * `dependencies` - Existing dependencies
/// * `new_dep` - New dependency to check
/// * `new_key` - Key for the new dependency
///
/// # Returns
///
/// True if there's a conflict, false otherwise
pub fn has_conflict(
&mut self,
dependencies: &HashMap<DependencyKey, DetailedDependency>,
new_dep: &ResourceDependency,
new_key: &DependencyKey,
) -> bool {
// For ResourceDependency, we need to extract the path and source info
let (new_path, new_source, new_tool) = match new_dep {
ResourceDependency::Simple(path) => (path, None, None),
ResourceDependency::Detailed(details) => {
(&details.path, details.source.as_deref(), details.tool.as_deref())
}
};
// Check for version conflicts
for (key, dep) in dependencies {
if key.0 == new_key.0 // resource_type
&& key.1 != new_key.1 // name
&& dep.path == *new_path
&& dep.source == new_source.map(String::from)
&& dep.tool == new_tool.map(String::from)
{
return true;
}
}
false
}
}
impl Default for ConflictService {
fn default() -> Self {
Self::new()
}
}