ricecoder_github/managers/
dependency_operations.rs

1//! Dependency Operations
2//!
3//! Additional operations for dependency management including verification and security tracking.
4
5use super::dependency_manager::{Dependency, DependencyError, DependencyUpdateSuggestion, VulnerabilitySeverity};
6use std::collections::HashMap;
7
8/// Result of dependency pinning operation
9#[derive(Debug, Clone)]
10pub struct DependencyPinningResult {
11    /// Dependencies that were pinned
12    pub pinned_dependencies: Vec<String>,
13    /// Pinning configuration
14    pub pinning_config: HashMap<String, String>,
15}
16
17/// Configuration for dependency pinning
18#[derive(Debug, Clone)]
19pub struct PinningConfig {
20    /// Whether to pin major versions
21    pub pin_major: bool,
22    /// Whether to pin minor versions
23    pub pin_minor: bool,
24    /// Whether to pin patch versions
25    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/// Result of build verification
39#[derive(Debug, Clone)]
40pub struct BuildVerificationResult {
41    /// Whether the build succeeded
42    pub success: bool,
43    /// Build output
44    pub output: String,
45    /// Any errors encountered
46    pub errors: Vec<String>,
47    /// Build duration in seconds
48    pub duration_secs: u64,
49}
50
51/// Dependency operations handler
52#[derive(Debug, Clone)]
53pub struct DependencyOperations;
54
55impl DependencyOperations {
56    /// Verifies that a dependency update doesn't break the build
57    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    /// Applies dependency pinning configuration
69    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    /// Generates a security report for dependencies
98    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    /// Checks if a dependency update is safe
146    pub fn is_update_safe(
147        _current_version: &str,
148        _new_version: &str,
149        vulnerabilities_count: usize,
150    ) -> Result<bool, DependencyError> {
151        // Update is safe if there are no critical vulnerabilities
152        Ok(vulnerabilities_count == 0)
153    }
154
155    /// Generates update recommendations
156    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/// Information about a vulnerability
192#[derive(Debug, Clone)]
193pub struct VulnerabilityInfo {
194    /// Name of the affected dependency
195    pub dependency_name: String,
196    /// CVE identifier
197    pub cve_id: String,
198    /// Severity level
199    pub severity: VulnerabilitySeverity,
200    /// Description
201    pub description: String,
202}
203
204/// Security report for dependencies
205#[derive(Debug, Clone)]
206pub struct SecurityReport {
207    /// Total number of vulnerabilities
208    pub total_vulnerabilities: usize,
209    /// Critical vulnerabilities
210    pub critical_vulnerabilities: Vec<VulnerabilityInfo>,
211    /// High severity vulnerabilities
212    pub high_vulnerabilities: Vec<VulnerabilityInfo>,
213    /// Medium severity vulnerabilities
214    pub medium_vulnerabilities: Vec<VulnerabilityInfo>,
215    /// Low severity vulnerabilities
216    pub low_vulnerabilities: Vec<VulnerabilityInfo>,
217    /// Overall risk score (0-100)
218    pub risk_score: f64,
219}
220
221/// Update recommendation for a dependency
222#[derive(Debug, Clone)]
223pub struct UpdateRecommendation {
224    /// Name of the dependency
225    pub dependency_name: String,
226    /// Current version
227    pub current_version: String,
228    /// Recommended version
229    pub recommended_version: String,
230    /// Reason for the recommendation
231    pub reason: String,
232    /// Priority of the update
233    pub priority: UpdatePriority,
234}
235
236/// Priority level for an update
237#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
238pub enum UpdatePriority {
239    /// Low priority
240    Low,
241    /// Medium priority
242    Medium,
243    /// High priority
244    High,
245    /// Critical priority
246    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}