ggen_cli_lib/cmds/hook/
validate.rs1use clap::Args;
21use ggen_utils::error::Result;
22use serde::{Deserialize, Serialize};
23use std::fs;
24use std::path::PathBuf;
25
26#[derive(Args, Debug)]
27pub struct ValidateArgs {
28 pub name: String,
30
31 #[arg(long)]
33 pub json: bool,
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize)]
37pub struct ValidationResult {
38 pub valid: bool,
39 pub hook_name: String,
40 pub errors: Vec<String>,
41 pub warnings: Vec<String>,
42 pub checks: Vec<ValidationCheck>,
43}
44
45#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct ValidationCheck {
47 pub name: String,
48 pub passed: bool,
49 pub message: String,
50}
51
52async fn validate_hook_configuration(hook_name: &str) -> Result<ValidationResult> {
54 let mut result = ValidationResult {
55 valid: true,
56 hook_name: hook_name.to_string(),
57 errors: Vec::new(),
58 warnings: Vec::new(),
59 checks: Vec::new(),
60 };
61
62 let hook_dir = PathBuf::from(".ggen").join("hooks");
64 let hook_file = hook_dir.join(format!("{}.toml", hook_name));
65
66 if !hook_file.exists() {
67 result.valid = false;
68 result.errors.push(format!(
69 "Hook configuration file not found: {}",
70 hook_file.display()
71 ));
72 return Ok(result);
73 }
74
75 result.checks.push(ValidationCheck {
76 name: "Configuration file exists".to_string(),
77 passed: true,
78 message: format!("Found: {}", hook_file.display()),
79 });
80
81 match fs::read_to_string(&hook_file) {
83 Ok(content) => match toml::from_str::<toml::Value>(&content) {
84 Ok(_) => {
85 result.checks.push(ValidationCheck {
86 name: "Valid TOML syntax".to_string(),
87 passed: true,
88 message: "Configuration file is valid TOML".to_string(),
89 });
90 }
91 Err(e) => {
92 result.valid = false;
93 result.errors.push(format!("Invalid TOML syntax: {}", e));
94 return Ok(result);
95 }
96 },
97 Err(e) => {
98 result.valid = false;
99 result.errors.push(format!("Cannot read hook file: {}", e));
100 return Ok(result);
101 }
102 }
103
104 Ok(result)
105}
106
107pub async fn run(args: &ValidateArgs) -> Result<()> {
109 if args.name.trim().is_empty() {
111 return Err(ggen_utils::error::Error::new("Hook name cannot be empty"));
112 }
113
114 println!("🔍 Validating hook '{}'...", args.name);
115
116 let result = validate_hook_configuration(&args.name).await?;
118
119 if args.json {
120 let json = serde_json::to_string_pretty(&result).map_err(|e| {
121 ggen_utils::error::Error::new_fmt(format_args!("JSON serialization failed: {}", e))
122 })?;
123 println!("{}", json);
124 return Ok(());
125 }
126
127 println!("\nValidation Results:");
129 println!();
130
131 for check in &result.checks {
132 let icon = if check.passed { "✅" } else { "❌" };
133 println!("{} {}", icon, check.name);
134 println!(" {}", check.message);
135 println!();
136 }
137
138 if !result.warnings.is_empty() {
139 println!("⚠️ Warnings:");
140 for warning in &result.warnings {
141 println!(" - {}", warning);
142 }
143 println!();
144 }
145
146 if !result.errors.is_empty() {
147 println!("❌ Errors:");
148 for error in &result.errors {
149 println!(" - {}", error);
150 }
151 println!();
152 return Err(ggen_utils::error::Error::new_fmt(format_args!(
153 "Hook '{}' validation failed",
154 args.name
155 )));
156 }
157
158 println!("✅ Hook '{}' is valid and ready to use!", args.name);
159
160 Ok(())
161}
162
163#[allow(dead_code)]
165fn is_valid_hook_name(name: &str) -> bool {
166 if name.is_empty() || name.len() > 50 {
167 return false;
168 }
169
170 name.chars()
172 .all(|c| c.is_alphanumeric() || c == '-' || c == '_' || c == '.')
173}
174
175#[cfg(test)]
176mod tests {
177 use super::*;
178
179 #[tokio::test]
180 #[ignore] async fn test_validate_hook_basic() {
182 let args = ValidateArgs {
183 name: "test-hook".to_string(),
184 json: false,
185 };
186 let result = run(&args).await;
187 assert!(result.is_ok());
188 }
189
190 #[tokio::test]
191 async fn test_validate_hook_empty_name() {
192 let args = ValidateArgs {
193 name: "".to_string(),
194 json: false,
195 };
196 let result = run(&args).await;
197 assert!(result.is_err());
198 }
199
200 #[tokio::test]
201 async fn test_validate_hook_json_output() {
202 let args = ValidateArgs {
203 name: "test-hook".to_string(),
204 json: true,
205 };
206 let result = run(&args).await;
207 assert!(result.is_ok());
208 }
209}