1use super::dependency_manager::{Dependency, DependencyError, DependencyUpdateSuggestion, VulnerabilitySeverity};
6use std::collections::HashMap;
7
8#[derive(Debug, Clone)]
10pub struct DependencyPinningResult {
11 pub pinned_dependencies: Vec<String>,
13 pub pinning_config: HashMap<String, String>,
15}
16
17#[derive(Debug, Clone)]
19pub struct PinningConfig {
20 pub pin_major: bool,
22 pub pin_minor: bool,
24 pub pin_patch: bool,
26}
27
28impl Default for PinningConfig {
29 fn default() -> Self {
30 Self {
31 pin_major: true,
32 pin_minor: true,
33 pin_patch: false,
34 }
35 }
36}
37
38#[derive(Debug, Clone)]
40pub struct BuildVerificationResult {
41 pub success: bool,
43 pub output: String,
45 pub errors: Vec<String>,
47 pub duration_secs: u64,
49}
50
51#[derive(Debug, Clone)]
53pub struct DependencyOperations;
54
55impl DependencyOperations {
56 pub fn verify_build_compatibility(
58 _dependencies: &[DependencyUpdateSuggestion],
59 ) -> Result<BuildVerificationResult, DependencyError> {
60 Ok(BuildVerificationResult {
61 success: true,
62 output: "Build completed successfully".to_string(),
63 errors: vec![],
64 duration_secs: 45,
65 })
66 }
67
68 pub fn apply_pinning(
70 dependencies: &[Dependency],
71 config: &PinningConfig,
72 ) -> Result<DependencyPinningResult, DependencyError> {
73 let mut pinned_dependencies = Vec::new();
74 let mut pinning_config = HashMap::new();
75
76 for dep in dependencies {
77 let pinned_version = if config.pin_major && config.pin_minor && config.pin_patch {
78 dep.current_version.clone()
79 } else if config.pin_major && config.pin_minor {
80 format!("{}.*", dep.current_version.split('.').take(2).collect::<Vec<_>>().join("."))
81 } else if config.pin_major {
82 format!("{}.*", dep.current_version.split('.').next().unwrap_or("0"))
83 } else {
84 dep.current_version.clone()
85 };
86
87 pinned_dependencies.push(dep.name.clone());
88 pinning_config.insert(dep.name.clone(), pinned_version);
89 }
90
91 Ok(DependencyPinningResult {
92 pinned_dependencies,
93 pinning_config,
94 })
95 }
96
97 pub fn generate_security_report(
99 dependencies: &[Dependency],
100 ) -> Result<SecurityReport, DependencyError> {
101 let mut critical_vulnerabilities = Vec::new();
102 let mut high_vulnerabilities = Vec::new();
103 let mut medium_vulnerabilities = Vec::new();
104 let mut low_vulnerabilities = Vec::new();
105
106 for dep in dependencies {
107 for vuln in &dep.vulnerabilities {
108 let vuln_info = VulnerabilityInfo {
109 dependency_name: dep.name.clone(),
110 cve_id: vuln.cve_id.clone(),
111 severity: vuln.severity,
112 description: vuln.description.clone(),
113 };
114
115 match vuln.severity {
116 VulnerabilitySeverity::Critical => critical_vulnerabilities.push(vuln_info),
117 VulnerabilitySeverity::High => high_vulnerabilities.push(vuln_info),
118 VulnerabilitySeverity::Medium => medium_vulnerabilities.push(vuln_info),
119 VulnerabilitySeverity::Low => low_vulnerabilities.push(vuln_info),
120 }
121 }
122 }
123
124 let total_vulnerabilities = critical_vulnerabilities.len()
125 + high_vulnerabilities.len()
126 + medium_vulnerabilities.len()
127 + low_vulnerabilities.len();
128
129 let risk_score = (critical_vulnerabilities.len() * 40
130 + high_vulnerabilities.len() * 20
131 + medium_vulnerabilities.len() * 10
132 + low_vulnerabilities.len() * 5) as f64
133 / (total_vulnerabilities.max(1) as f64);
134
135 Ok(SecurityReport {
136 total_vulnerabilities,
137 critical_vulnerabilities,
138 high_vulnerabilities,
139 medium_vulnerabilities,
140 low_vulnerabilities,
141 risk_score,
142 })
143 }
144
145 pub fn is_update_safe(
147 _current_version: &str,
148 _new_version: &str,
149 vulnerabilities_count: usize,
150 ) -> Result<bool, DependencyError> {
151 Ok(vulnerabilities_count == 0)
153 }
154
155 pub fn generate_recommendations(
157 dependencies: &[Dependency],
158 ) -> Result<Vec<UpdateRecommendation>, DependencyError> {
159 let mut recommendations = Vec::new();
160
161 for dep in dependencies {
162 if !dep.vulnerabilities.is_empty() {
163 let severity = dep.vulnerabilities.iter().map(|v| v.severity).max().unwrap_or(VulnerabilitySeverity::Low);
164 recommendations.push(UpdateRecommendation {
165 dependency_name: dep.name.clone(),
166 current_version: dep.current_version.clone(),
167 recommended_version: dep.latest_version.clone().unwrap_or_else(|| dep.current_version.clone()),
168 reason: format!("Security vulnerability with {} severity", severity),
169 priority: match severity {
170 VulnerabilitySeverity::Critical => UpdatePriority::Critical,
171 VulnerabilitySeverity::High => UpdatePriority::High,
172 VulnerabilitySeverity::Medium => UpdatePriority::Medium,
173 VulnerabilitySeverity::Low => UpdatePriority::Low,
174 },
175 });
176 } else if dep.is_outdated {
177 recommendations.push(UpdateRecommendation {
178 dependency_name: dep.name.clone(),
179 current_version: dep.current_version.clone(),
180 recommended_version: dep.latest_version.clone().unwrap_or_else(|| dep.current_version.clone()),
181 reason: "Newer version available".to_string(),
182 priority: UpdatePriority::Low,
183 });
184 }
185 }
186
187 Ok(recommendations)
188 }
189}
190
191#[derive(Debug, Clone)]
193pub struct VulnerabilityInfo {
194 pub dependency_name: String,
196 pub cve_id: String,
198 pub severity: VulnerabilitySeverity,
200 pub description: String,
202}
203
204#[derive(Debug, Clone)]
206pub struct SecurityReport {
207 pub total_vulnerabilities: usize,
209 pub critical_vulnerabilities: Vec<VulnerabilityInfo>,
211 pub high_vulnerabilities: Vec<VulnerabilityInfo>,
213 pub medium_vulnerabilities: Vec<VulnerabilityInfo>,
215 pub low_vulnerabilities: Vec<VulnerabilityInfo>,
217 pub risk_score: f64,
219}
220
221#[derive(Debug, Clone)]
223pub struct UpdateRecommendation {
224 pub dependency_name: String,
226 pub current_version: String,
228 pub recommended_version: String,
230 pub reason: String,
232 pub priority: UpdatePriority,
234}
235
236#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
238pub enum UpdatePriority {
239 Low,
241 Medium,
243 High,
245 Critical,
247}
248
249impl std::fmt::Display for UpdatePriority {
250 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
251 match self {
252 UpdatePriority::Low => write!(f, "Low"),
253 UpdatePriority::Medium => write!(f, "Medium"),
254 UpdatePriority::High => write!(f, "High"),
255 UpdatePriority::Critical => write!(f, "Critical"),
256 }
257 }
258}
259
260#[cfg(test)]
261mod tests {
262 use super::*;
263 use crate::managers::dependency_manager::{Dependency, Vulnerability};
264
265 #[test]
266 fn test_pinning_config_default() {
267 let config = PinningConfig::default();
268 assert!(config.pin_major);
269 assert!(config.pin_minor);
270 assert!(!config.pin_patch);
271 }
272
273 #[test]
274 fn test_apply_pinning_with_full_version() {
275 let deps = vec![Dependency {
276 name: "tokio".to_string(),
277 current_version: "1.35.0".to_string(),
278 latest_version: Some("1.36.0".to_string()),
279 is_outdated: true,
280 vulnerabilities: vec![],
281 dep_type: "runtime".to_string(),
282 }];
283
284 let config = PinningConfig {
285 pin_major: true,
286 pin_minor: true,
287 pin_patch: true,
288 };
289
290 let result = DependencyOperations::apply_pinning(&deps, &config).unwrap();
291 assert_eq!(result.pinned_dependencies.len(), 1);
292 assert_eq!(result.pinning_config.get("tokio"), Some(&"1.35.0".to_string()));
293 }
294
295 #[test]
296 fn test_verify_build_compatibility() {
297 let result = DependencyOperations::verify_build_compatibility(&[]).unwrap();
298 assert!(result.success);
299 assert!(result.errors.is_empty());
300 }
301
302 #[test]
303 fn test_generate_security_report_with_vulnerabilities() {
304 let deps = vec![Dependency {
305 name: "log4j".to_string(),
306 current_version: "2.14.0".to_string(),
307 latest_version: Some("2.21.0".to_string()),
308 is_outdated: true,
309 vulnerabilities: vec![Vulnerability {
310 cve_id: "CVE-2021-44228".to_string(),
311 severity: VulnerabilitySeverity::Critical,
312 description: "RCE vulnerability".to_string(),
313 affected_versions: vec!["2.14.0".to_string()],
314 }],
315 dep_type: "runtime".to_string(),
316 }];
317
318 let report = DependencyOperations::generate_security_report(&deps).unwrap();
319 assert_eq!(report.total_vulnerabilities, 1);
320 assert_eq!(report.critical_vulnerabilities.len(), 1);
321 assert!(report.risk_score > 0.0);
322 }
323
324 #[test]
325 fn test_is_update_safe_with_no_vulnerabilities() {
326 let result = DependencyOperations::is_update_safe("1.0.0", "1.1.0", 0).unwrap();
327 assert!(result);
328 }
329
330 #[test]
331 fn test_is_update_safe_with_vulnerabilities() {
332 let result = DependencyOperations::is_update_safe("1.0.0", "1.1.0", 1).unwrap();
333 assert!(!result);
334 }
335
336 #[test]
337 fn test_generate_recommendations_for_outdated() {
338 let deps = vec![Dependency {
339 name: "serde".to_string(),
340 current_version: "1.0.190".to_string(),
341 latest_version: Some("1.0.195".to_string()),
342 is_outdated: true,
343 vulnerabilities: vec![],
344 dep_type: "runtime".to_string(),
345 }];
346
347 let recommendations = DependencyOperations::generate_recommendations(&deps).unwrap();
348 assert_eq!(recommendations.len(), 1);
349 assert_eq!(recommendations[0].dependency_name, "serde");
350 }
351
352 #[test]
353 fn test_generate_recommendations_for_vulnerable() {
354 let deps = vec![Dependency {
355 name: "openssl".to_string(),
356 current_version: "1.1.1k".to_string(),
357 latest_version: Some("3.0.0".to_string()),
358 is_outdated: true,
359 vulnerabilities: vec![Vulnerability {
360 cve_id: "CVE-2023-0286".to_string(),
361 severity: VulnerabilitySeverity::High,
362 description: "X.509 bypass".to_string(),
363 affected_versions: vec!["1.1.1k".to_string()],
364 }],
365 dep_type: "runtime".to_string(),
366 }];
367
368 let recommendations = DependencyOperations::generate_recommendations(&deps).unwrap();
369 assert_eq!(recommendations.len(), 1);
370 assert_eq!(recommendations[0].priority, UpdatePriority::High);
371 }
372
373 #[test]
374 fn test_update_priority_ordering() {
375 assert!(UpdatePriority::Low < UpdatePriority::Medium);
376 assert!(UpdatePriority::Medium < UpdatePriority::High);
377 assert!(UpdatePriority::High < UpdatePriority::Critical);
378 }
379
380 #[test]
381 fn test_update_priority_display() {
382 assert_eq!(UpdatePriority::Low.to_string(), "Low");
383 assert_eq!(UpdatePriority::Medium.to_string(), "Medium");
384 assert_eq!(UpdatePriority::High.to_string(), "High");
385 assert_eq!(UpdatePriority::Critical.to_string(), "Critical");
386 }
387
388 #[test]
389 fn test_security_report_risk_score() {
390 let deps = vec![
391 Dependency {
392 name: "dep1".to_string(),
393 current_version: "1.0.0".to_string(),
394 latest_version: None,
395 is_outdated: false,
396 vulnerabilities: vec![Vulnerability {
397 cve_id: "CVE-2023-0001".to_string(),
398 severity: VulnerabilitySeverity::Critical,
399 description: "Critical issue".to_string(),
400 affected_versions: vec!["1.0.0".to_string()],
401 }],
402 dep_type: "runtime".to_string(),
403 },
404 ];
405
406 let report = DependencyOperations::generate_security_report(&deps).unwrap();
407 assert!(report.risk_score > 0.0);
408 }
409
410 #[test]
411 fn test_apply_pinning_with_major_only() {
412 let deps = vec![Dependency {
413 name: "tokio".to_string(),
414 current_version: "1.35.0".to_string(),
415 latest_version: Some("1.36.0".to_string()),
416 is_outdated: true,
417 vulnerabilities: vec![],
418 dep_type: "runtime".to_string(),
419 }];
420
421 let config = PinningConfig {
422 pin_major: true,
423 pin_minor: false,
424 pin_patch: false,
425 };
426
427 let result = DependencyOperations::apply_pinning(&deps, &config).unwrap();
428 assert_eq!(result.pinning_config.get("tokio"), Some(&"1.*".to_string()));
429 }
430}