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