cargo_autodd/dependency_manager/
reporter.rs1use 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}