rgen_cli_lib/cmds/
hazard.rs

1use colored::*;
2use std::path::Path;
3use walkdir::WalkDir;
4
5pub fn run() -> rgen_utils::error::Result<()> {
6    println!("šŸ” RGen Hazard Report");
7    println!("====================");
8
9    let mut hazards = Vec::new();
10
11    // Check for templates directory
12    check_templates_directory(&mut hazards);
13
14    // Check for RDF files
15    check_rdf_files(&mut hazards);
16
17    // Check for configuration files
18    check_configuration(&mut hazards);
19
20    // Check for potential security issues
21    check_security_hazards(&mut hazards);
22
23    // Check for performance issues
24    check_performance_hazards(&mut hazards);
25
26    // Display results
27    if hazards.is_empty() {
28        println!("āœ… No hazards detected!");
29    } else {
30        println!("\nāš ļø  Found {} potential hazard(s):", hazards.len());
31        for (i, hazard) in hazards.iter().enumerate() {
32            println!(
33                "\n{}. {} - {}",
34                i + 1,
35                hazard.severity.colorize(),
36                hazard.description
37            );
38            if let Some(recommendation) = &hazard.recommendation {
39                println!("   šŸ’” Recommendation: {}", recommendation);
40            }
41        }
42    }
43
44    Ok(())
45}
46
47#[derive(Debug)]
48struct Hazard {
49    severity: Severity,
50    description: String,
51    recommendation: Option<String>,
52}
53
54#[derive(Debug)]
55#[allow(dead_code)]
56enum Severity {
57    Low,
58    Medium,
59    High,
60    Critical,
61}
62
63impl Severity {
64    fn colorize(&self) -> colored::ColoredString {
65        match self {
66            Severity::Low => "LOW".yellow(),
67            Severity::Medium => "MEDIUM".yellow(),
68            Severity::High => "HIGH".red(),
69            Severity::Critical => "CRITICAL".red().bold(),
70        }
71    }
72}
73
74fn check_templates_directory(hazards: &mut Vec<Hazard>) {
75    let templates_dirs = ["templates", "examples"];
76
77    for dir in &templates_dirs {
78        if !Path::new(dir).exists() {
79            hazards.push(Hazard {
80                severity: Severity::Low,
81                description: format!("Templates directory '{}' not found", dir),
82                recommendation: Some(
83                    "Create a templates directory to organize your templates".to_string(),
84                ),
85            });
86        } else {
87            // Check for .tmpl files
88            let mut template_count = 0;
89            for entry in WalkDir::new(dir).into_iter().filter_map(|e| e.ok()) {
90                if let Some(ext) = entry.path().extension() {
91                    if ext == "tmpl" {
92                        template_count += 1;
93                    }
94                }
95            }
96
97            if template_count == 0 {
98                hazards.push(Hazard {
99                    severity: Severity::Medium,
100                    description: format!("No .tmpl files found in '{}' directory", dir),
101                    recommendation: Some("Add template files with .tmpl extension".to_string()),
102                });
103            }
104        }
105    }
106}
107
108fn check_rdf_files(hazards: &mut Vec<Hazard>) {
109    let rdf_dirs = ["graphs", "rdf", "data"];
110    let mut rdf_count = 0;
111
112    for dir in &rdf_dirs {
113        if Path::new(dir).exists() {
114            for entry in WalkDir::new(dir).into_iter().filter_map(|e| e.ok()) {
115                if let Some(ext) = entry.path().extension() {
116                    if matches!(ext.to_str(), Some("ttl" | "rdf" | "xml" | "jsonld")) {
117                        rdf_count += 1;
118                    }
119                }
120            }
121        }
122    }
123
124    if rdf_count == 0 {
125        hazards.push(Hazard {
126            severity: Severity::Medium,
127            description: "No RDF files found in project".to_string(),
128            recommendation: Some("Add RDF files to enable graph-based code generation".to_string()),
129        });
130    }
131}
132
133fn check_configuration(hazards: &mut Vec<Hazard>) {
134    let config_files = ["rgen.toml", "Cargo.toml", "pyproject.toml"];
135
136    for config_file in &config_files {
137        if !Path::new(config_file).exists() {
138            hazards.push(Hazard {
139                severity: Severity::Low,
140                description: format!("Configuration file '{}' not found", config_file),
141                recommendation: Some(
142                    "Consider adding configuration for better project management".to_string(),
143                ),
144            });
145        }
146    }
147}
148
149fn check_security_hazards(hazards: &mut Vec<Hazard>) {
150    // Check for shell hooks in templates
151    for entry in WalkDir::new(".").into_iter().filter_map(|e| e.ok()) {
152        if let Some(ext) = entry.path().extension() {
153            if ext == "tmpl" {
154                if let Ok(content) = std::fs::read_to_string(entry.path()) {
155                    if content.contains("sh_before:") || content.contains("sh_after:") {
156                        hazards.push(Hazard {
157                            severity: Severity::High,
158                            description: format!(
159                                "Template '{}' contains shell hooks",
160                                entry.path().display()
161                            ),
162                            recommendation: Some(
163                                "Review shell hooks for security implications".to_string(),
164                            ),
165                        });
166                    }
167                }
168            }
169        }
170    }
171}
172
173fn check_performance_hazards(hazards: &mut Vec<Hazard>) {
174    // Check for large RDF files
175    for entry in WalkDir::new(".").into_iter().filter_map(|e| e.ok()) {
176        if let Some(ext) = entry.path().extension() {
177            if matches!(ext.to_str(), Some("ttl" | "rdf" | "xml" | "jsonld")) {
178                if let Ok(metadata) = std::fs::metadata(entry.path()) {
179                    let size_mb = metadata.len() as f64 / 1_048_576.0;
180                    if size_mb > 10.0 {
181                        hazards.push(Hazard {
182                            severity: Severity::Medium,
183                            description: format!(
184                                "Large RDF file '{}' ({:.1} MB)",
185                                entry.path().display(),
186                                size_mb
187                            ),
188                            recommendation: Some(
189                                "Consider splitting large RDF files for better performance"
190                                    .to_string(),
191                            ),
192                        });
193                    }
194                }
195            }
196        }
197    }
198}