dynamic_cli/validator/
file_validator.rs1use crate::error::{Result, ValidationError};
29use std::path::Path;
30
31pub fn validate_file_exists(path: &Path, arg_name: &str) -> Result<()> {
65 if !path.exists() {
68 return Err(ValidationError::FileNotFound {
69 path: path.to_path_buf(),
70 arg_name: arg_name.to_string(),
71 }
72 .into());
73 }
74
75 Ok(())
76}
77
78pub fn validate_file_extension(path: &Path, arg_name: &str, expected: &[String]) -> Result<()> {
128 let extension = path
130 .extension()
131 .and_then(|ext| ext.to_str())
132 .map(|ext| ext.to_lowercase());
133
134 let ext = match extension {
136 Some(e) => e,
137 None => {
138 return Err(ValidationError::InvalidExtension {
140 arg_name: arg_name.to_string(),
141 path: path.to_path_buf(),
142 expected: expected.to_vec(),
143 }
144 .into());
145 }
146 };
147
148 let is_valid = expected.iter().any(|allowed| allowed.to_lowercase() == ext);
151
152 if !is_valid {
153 return Err(ValidationError::InvalidExtension {
154 arg_name: arg_name.to_string(),
155 path: path.to_path_buf(),
156 expected: expected.to_vec(),
157 }
158 .into());
159 }
160
161 Ok(())
162}
163
164#[cfg(test)]
165mod tests {
166 use super::*;
167 use std::fs::File;
168 use std::io::Write;
169 use tempfile::TempDir;
170
171 fn create_temp_file(dir: &TempDir, name: &str, content: &str) -> std::path::PathBuf {
173 let file_path = dir.path().join(name);
174 let mut file = File::create(&file_path).unwrap();
175 file.write_all(content.as_bytes()).unwrap();
176 file_path
177 }
178
179 #[test]
184 fn test_validate_file_exists_valid_file() {
185 let temp_dir = TempDir::new().unwrap();
186 let file_path = create_temp_file(&temp_dir, "test.txt", "content");
187
188 let result = validate_file_exists(&file_path, "test_file");
189 assert!(result.is_ok());
190 }
191
192 #[test]
193 fn test_validate_file_exists_valid_directory() {
194 let temp_dir = TempDir::new().unwrap();
195
196 let result = validate_file_exists(temp_dir.path(), "test_dir");
198 assert!(result.is_ok());
199 }
200
201 #[test]
202 fn test_validate_file_exists_nonexistent() {
203 let temp_dir = TempDir::new().unwrap();
204 let nonexistent = temp_dir.path().join("does_not_exist.txt");
205
206 let result = validate_file_exists(&nonexistent, "missing_file");
207
208 assert!(result.is_err());
209 match result.unwrap_err() {
210 crate::error::DynamicCliError::Validation(ValidationError::FileNotFound {
211 path,
212 arg_name,
213 }) => {
214 assert_eq!(arg_name, "missing_file");
215 assert_eq!(path, nonexistent);
216 }
217 other => panic!("Expected FileNotFound error, got {:?}", other),
218 }
219 }
220
221 #[test]
222 fn test_validate_file_exists_relative_path() {
223 let temp_dir = TempDir::new().unwrap();
224 let file_path = create_temp_file(&temp_dir, "relative.txt", "content");
225
226 let relative = std::path::Path::new(file_path.file_name().unwrap());
228
229 let original_dir = std::env::current_dir().unwrap();
231 std::env::set_current_dir(temp_dir.path()).unwrap();
232
233 let result = validate_file_exists(relative, "relative_file");
234 assert!(result.is_ok());
235
236 std::env::set_current_dir(original_dir).unwrap();
238 }
239
240 #[test]
245 fn test_validate_file_extension_valid_single() {
246 let path = Path::new("config.yaml");
247 let allowed = vec!["yaml".to_string()];
248
249 let result = validate_file_extension(path, "config", &allowed);
250 assert!(result.is_ok());
251 }
252
253 #[test]
254 fn test_validate_file_extension_valid_multiple() {
255 let path = Path::new("data.csv");
256 let allowed = vec!["csv".to_string(), "tsv".to_string(), "txt".to_string()];
257
258 let result = validate_file_extension(path, "data_file", &allowed);
259 assert!(result.is_ok());
260 }
261
262 #[test]
263 fn test_validate_file_extension_case_insensitive() {
264 let path1 = Path::new("config.YAML");
266 let allowed = vec!["yaml".to_string()];
267
268 assert!(validate_file_extension(path1, "config", &allowed).is_ok());
269
270 let path2 = Path::new("config.YaML");
272 assert!(validate_file_extension(path2, "config", &allowed).is_ok());
273
274 let path3 = Path::new("config.yaml");
276 let allowed_upper = vec!["YAML".to_string()];
277 assert!(validate_file_extension(path3, "config", &allowed_upper).is_ok());
278 }
279
280 #[test]
281 fn test_validate_file_extension_invalid() {
282 let path = Path::new("document.txt");
283 let allowed = vec!["yaml".to_string(), "yml".to_string()];
284
285 let result = validate_file_extension(path, "doc", &allowed);
286
287 assert!(result.is_err());
288 match result.unwrap_err() {
289 crate::error::DynamicCliError::Validation(ValidationError::InvalidExtension {
290 arg_name,
291 path: error_path,
292 expected,
293 }) => {
294 assert_eq!(arg_name, "doc");
295 assert_eq!(error_path, path);
296 assert_eq!(expected, allowed);
297 }
298 other => panic!("Expected InvalidExtension error, got {:?}", other),
299 }
300 }
301
302 #[test]
303 fn test_validate_file_extension_no_extension() {
304 let path = Path::new("makefile");
305 let allowed = vec!["txt".to_string()];
306
307 let result = validate_file_extension(path, "build_file", &allowed);
308
309 assert!(result.is_err());
310 match result.unwrap_err() {
311 crate::error::DynamicCliError::Validation(ValidationError::InvalidExtension {
312 ..
313 }) => {
314 }
316 other => panic!("Expected InvalidExtension error, got {:?}", other),
317 }
318 }
319
320 #[test]
321 fn test_validate_file_extension_hidden_file_with_extension() {
322 let path = Path::new(".hidden.txt");
325 let allowed = vec!["txt".to_string()];
326
327 let result = validate_file_extension(path, "hidden_file", &allowed);
328 assert!(result.is_ok());
329 }
330
331 #[test]
332 fn test_validate_file_extension_hidden_file_no_extension() {
333 let path = Path::new(".gitignore");
336 let allowed = vec!["txt".to_string()];
337
338 let result = validate_file_extension(path, "git_file", &allowed);
339 assert!(result.is_err());
341 }
342
343 #[test]
344 fn test_validate_file_extension_multiple_dots() {
345 let path = Path::new("archive.tar.gz");
346 let allowed = vec!["gz".to_string()];
347
348 let result = validate_file_extension(path, "archive", &allowed);
350 assert!(result.is_ok());
351 }
352
353 #[test]
354 fn test_validate_file_extension_empty_allowed_list() {
355 let path = Path::new("file.txt");
356 let allowed: Vec<String> = vec![];
357
358 let result = validate_file_extension(path, "file", &allowed);
359 assert!(result.is_err());
360 }
361
362 #[test]
363 fn test_validate_file_extension_with_leading_dot() {
364 let path = Path::new("config.yaml");
367
368 let allowed = vec!["yaml".to_string()];
371
372 let result = validate_file_extension(path, "config", &allowed);
373 assert!(result.is_ok());
374 }
375
376 #[test]
381 fn test_validate_both_file_and_extension() {
382 let temp_dir = TempDir::new().unwrap();
383 let file_path = create_temp_file(&temp_dir, "config.yaml", "key: value");
384
385 let result1 = validate_file_exists(&file_path, "config_file");
387 assert!(result1.is_ok());
388
389 let allowed = vec!["yaml".to_string(), "yml".to_string()];
391 let result2 = validate_file_extension(&file_path, "config_file", &allowed);
392 assert!(result2.is_ok());
393 }
394
395 #[test]
396 fn test_validate_wrong_extension_existing_file() {
397 let temp_dir = TempDir::new().unwrap();
398 let file_path = create_temp_file(&temp_dir, "data.txt", "some data");
399
400 assert!(validate_file_exists(&file_path, "data_file").is_ok());
402
403 let allowed = vec!["csv".to_string()];
405 let result = validate_file_extension(&file_path, "data_file", &allowed);
406 assert!(result.is_err());
407 }
408
409 #[test]
410 fn test_validate_extension_nonexistent_file() {
411 let path = Path::new("nonexistent.yaml");
413 let allowed = vec!["yaml".to_string()];
414
415 let result = validate_file_extension(path, "config", &allowed);
417 assert!(result.is_ok());
418
419 let result2 = validate_file_exists(path, "config");
421 assert!(result2.is_err());
422 }
423}