rgen_cli_lib/cmds/
hazard.rs1use 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_templates_directory(&mut hazards);
13
14 check_rdf_files(&mut hazards);
16
17 check_configuration(&mut hazards);
19
20 check_security_hazards(&mut hazards);
22
23 check_performance_hazards(&mut hazards);
25
26 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 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 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 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}