cargo_autodd/dependency_manager/
reporter.rs

1use std::collections::HashMap;
2use std::fs;
3use std::path::PathBuf;
4
5use anyhow::Result;
6use semver::Version;
7use toml_edit::{DocumentMut, Item};
8
9use crate::dependency_manager::updater::DependencyUpdater;
10use crate::models::CrateReference;
11
12pub struct DependencyReporter {
13    project_root: PathBuf,
14    cargo_toml: PathBuf,
15    updater: DependencyUpdater,
16}
17
18impl DependencyReporter {
19    pub fn new(project_root: PathBuf) -> Self {
20        let cargo_toml = project_root.join("Cargo.toml");
21        let updater = DependencyUpdater::new(project_root.clone());
22        Self {
23            project_root,
24            cargo_toml,
25            updater,
26        }
27    }
28
29    pub fn generate_dependency_report(
30        &self,
31        crate_refs: &HashMap<String, CrateReference>,
32    ) -> Result<()> {
33        let content = fs::read_to_string(&self.cargo_toml)?;
34        let doc = content.parse::<DocumentMut>()?;
35
36        println!("\nDependency Usage Report");
37        println!("=====================\n");
38
39        if let Some(Item::Table(deps)) = doc.get("dependencies") {
40            for (name, dep) in deps.iter() {
41                println!("📦 {}", name);
42
43                if let Some(version) = self.updater.get_dependency_version(dep) {
44                    println!("  Version: {}", version);
45
46                    match self.updater.get_latest_version(name) {
47                        Ok(latest) => {
48                            if let Ok(needs_update) = self.check_version(&version, &latest) {
49                                if needs_update {
50                                    println!("  ⚠️ Update available: {} -> {}", version, latest);
51                                } else {
52                                    println!("  ✅ Up to date");
53                                }
54                            }
55                        }
56                        Err(e) => {
57                            println!("  ⚠️ Failed to check latest version: {}", e);
58                        }
59                    }
60                }
61
62                if let Some(crate_ref) = crate_refs.get(name) {
63                    println!("  Used in {} file(s)", crate_ref.usage_count());
64                    println!("  Usage locations:");
65                    for path in &crate_ref.used_in {
66                        if let Ok(relative) = path.strip_prefix(&self.project_root) {
67                            println!("    - {}", relative.display());
68                        }
69                    }
70                } else {
71                    println!("  ⚠️ Warning: No usage detected in the project");
72                }
73                println!();
74            }
75        }
76
77        Ok(())
78    }
79
80    pub fn generate_security_report(&self) -> Result<()> {
81        println!("\nDependency Security Report");
82        println!("========================\n");
83
84        let outdated = self.check_security()?;
85
86        if outdated.is_empty() {
87            println!("✅ All dependencies are up to date.");
88            return Ok(());
89        }
90
91        println!("⚠️ The following dependencies have updates available:\n");
92
93        for (name, version_info) in outdated {
94            println!("📦 {}", name);
95            println!("  Version update available: {}", version_info);
96            println!();
97        }
98
99        println!("Note: For a complete security audit, please use:");
100        println!("  cargo audit");
101        println!("  https://github.com/rustsec/rustsec\n");
102
103        Ok(())
104    }
105
106    fn check_security(&self) -> Result<Vec<(String, String)>> {
107        let content = fs::read_to_string(&self.cargo_toml)?;
108        let doc = content.parse::<DocumentMut>()?;
109        let mut outdated = Vec::new();
110
111        if let Some(Item::Table(deps)) = doc.get("dependencies") {
112            for (name, dep) in deps.iter() {
113                if let Some(version) = self.updater.get_dependency_version(dep) {
114                    if let Ok(latest) = self.updater.get_latest_version(name) {
115                        if let Ok(true) = self.check_version(&version, &latest) {
116                            outdated.push((name.to_string(), format!("{} -> {}", version, latest)));
117                        }
118                    }
119                }
120            }
121        }
122
123        Ok(outdated)
124    }
125
126    pub fn check_version(&self, version: &str, latest: &str) -> Result<bool> {
127        let current = Version::parse(version.trim_start_matches('^'))?;
128        let latest_ver = Version::parse(latest.trim_start_matches('^'))?;
129        Ok(latest_ver > current)
130    }
131}
132
133#[cfg(test)]
134mod tests {
135    use super::*;
136    use std::fs::File;
137    use std::io::Write;
138    use tempfile::TempDir;
139
140    fn create_test_environment() -> Result<(TempDir, PathBuf)> {
141        let temp_dir = TempDir::new()?;
142        let cargo_toml = temp_dir.path().join("Cargo.toml");
143
144        let content = r#"
145[package]
146name = "test-package"
147version = "0.1.0"
148edition = "2021"
149
150[dependencies]
151serde = "1.0"
152tokio = "1.0"
153"#;
154        let mut file = File::create(&cargo_toml)?;
155        writeln!(file, "{}", content)?;
156
157        Ok((temp_dir, cargo_toml))
158    }
159
160    #[test]
161    fn test_generate_dependency_report() -> Result<()> {
162        let (temp_dir, _) = create_test_environment()?;
163        let reporter = DependencyReporter::new(temp_dir.path().to_path_buf());
164
165        let mut crate_refs = HashMap::new();
166        let mut serde_ref = CrateReference::new("serde".to_string());
167        serde_ref.add_usage(temp_dir.path().join("src/main.rs"));
168        crate_refs.insert("serde".to_string(), serde_ref);
169
170        reporter.generate_dependency_report(&crate_refs)?;
171        Ok(())
172    }
173
174    #[test]
175    fn test_generate_security_report() -> Result<()> {
176        let (temp_dir, _) = create_test_environment()?;
177        let reporter = DependencyReporter::new(temp_dir.path().to_path_buf());
178        reporter.generate_security_report()?;
179        Ok(())
180    }
181}