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 suggestion: Some("Check that the file exists and the path is correct.".to_string()),
72 }
73 .into());
74 }
75
76 Ok(())
77}
78
79pub fn validate_file_extension(path: &Path, arg_name: &str, expected: &[String]) -> Result<()> {
129 let extension = path
131 .extension()
132 .and_then(|ext| ext.to_str())
133 .map(|ext| ext.to_lowercase());
134
135 let ext = match extension {
137 Some(e) => e,
138 None => {
139 return Err(ValidationError::InvalidExtension {
141 arg_name: arg_name.to_string(),
142 path: path.to_path_buf(),
143 expected: expected.to_vec(),
144 }
145 .into());
146 }
147 };
148
149 let is_valid = expected.iter().any(|allowed| allowed.to_lowercase() == ext);
152
153 if !is_valid {
154 return Err(ValidationError::InvalidExtension {
155 arg_name: arg_name.to_string(),
156 path: path.to_path_buf(),
157 expected: expected.to_vec(),
158 }
159 .into());
160 }
161
162 Ok(())
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168 use std::fs::File;
169 use std::io::Write;
170 use tempfile::TempDir;
171
172 fn create_temp_file(dir: &TempDir, name: &str, content: &str) -> std::path::PathBuf {
174 let file_path = dir.path().join(name);
175 let mut file = File::create(&file_path).unwrap();
176 file.write_all(content.as_bytes()).unwrap();
177 file_path
178 }
179
180 #[test]
185 fn test_validate_file_exists_valid_file() {
186 let temp_dir = TempDir::new().unwrap();
187 let file_path = create_temp_file(&temp_dir, "test.txt", "content");
188
189 let result = validate_file_exists(&file_path, "test_file");
190 assert!(result.is_ok());
191 }
192
193 #[test]
194 fn test_validate_file_exists_valid_directory() {
195 let temp_dir = TempDir::new().unwrap();
196
197 let result = validate_file_exists(temp_dir.path(), "test_dir");
199 assert!(result.is_ok());
200 }
201
202 #[test]
203 fn test_validate_file_exists_nonexistent() {
204 let temp_dir = TempDir::new().unwrap();
205 let nonexistent = temp_dir.path().join("does_not_exist.txt");
206
207 let result = validate_file_exists(&nonexistent, "missing_file");
208
209 assert!(result.is_err());
210 match result.unwrap_err() {
211 crate::error::DynamicCliError::Validation(ValidationError::FileNotFound {
212 path,
213 arg_name,
214 ..
215 }) => {
216 assert_eq!(arg_name, "missing_file");
217 assert_eq!(path, nonexistent);
218 }
219 other => panic!("Expected FileNotFound error, got {:?}", other),
220 }
221 }
222
223 #[test]
224 fn test_validate_file_exists_relative_path() {
225 let temp_dir = TempDir::new().unwrap();
226 let file_path = create_temp_file(&temp_dir, "relative.txt", "content");
227
228 let relative = std::path::Path::new(file_path.file_name().unwrap());
230
231 let original_dir = std::env::current_dir().unwrap();
233 std::env::set_current_dir(temp_dir.path()).unwrap();
234
235 let result = validate_file_exists(relative, "relative_file");
236 assert!(result.is_ok());
237
238 std::env::set_current_dir(original_dir).unwrap();
240 }
241
242 #[test]
247 fn test_validate_file_extension_valid_single() {
248 let path = Path::new("config.yaml");
249 let allowed = vec!["yaml".to_string()];
250
251 let result = validate_file_extension(path, "config", &allowed);
252 assert!(result.is_ok());
253 }
254
255 #[test]
256 fn test_validate_file_extension_valid_multiple() {
257 let path = Path::new("data.csv");
258 let allowed = vec!["csv".to_string(), "tsv".to_string(), "txt".to_string()];
259
260 let result = validate_file_extension(path, "data_file", &allowed);
261 assert!(result.is_ok());
262 }
263
264 #[test]
265 fn test_validate_file_extension_case_insensitive() {
266 let path1 = Path::new("config.YAML");
268 let allowed = vec!["yaml".to_string()];
269
270 assert!(validate_file_extension(path1, "config", &allowed).is_ok());
271
272 let path2 = Path::new("config.YaML");
274 assert!(validate_file_extension(path2, "config", &allowed).is_ok());
275
276 let path3 = Path::new("config.yaml");
278 let allowed_upper = vec!["YAML".to_string()];
279 assert!(validate_file_extension(path3, "config", &allowed_upper).is_ok());
280 }
281
282 #[test]
283 fn test_validate_file_extension_invalid() {
284 let path = Path::new("document.txt");
285 let allowed = vec!["yaml".to_string(), "yml".to_string()];
286
287 let result = validate_file_extension(path, "doc", &allowed);
288
289 assert!(result.is_err());
290 match result.unwrap_err() {
291 crate::error::DynamicCliError::Validation(ValidationError::InvalidExtension {
292 arg_name,
293 path: error_path,
294 expected,
295 }) => {
296 assert_eq!(arg_name, "doc");
297 assert_eq!(error_path, path);
298 assert_eq!(expected, allowed);
299 }
300 other => panic!("Expected InvalidExtension error, got {:?}", other),
301 }
302 }
303
304 #[test]
305 fn test_validate_file_extension_no_extension() {
306 let path = Path::new("makefile");
307 let allowed = vec!["txt".to_string()];
308
309 let result = validate_file_extension(path, "build_file", &allowed);
310
311 assert!(result.is_err());
312 match result.unwrap_err() {
313 crate::error::DynamicCliError::Validation(ValidationError::InvalidExtension {
314 ..
315 }) => {
316 }
318 other => panic!("Expected InvalidExtension error, got {:?}", other),
319 }
320 }
321
322 #[test]
323 fn test_validate_file_extension_hidden_file_with_extension() {
324 let path = Path::new(".hidden.txt");
327 let allowed = vec!["txt".to_string()];
328
329 let result = validate_file_extension(path, "hidden_file", &allowed);
330 assert!(result.is_ok());
331 }
332
333 #[test]
334 fn test_validate_file_extension_hidden_file_no_extension() {
335 let path = Path::new(".gitignore");
338 let allowed = vec!["txt".to_string()];
339
340 let result = validate_file_extension(path, "git_file", &allowed);
341 assert!(result.is_err());
343 }
344
345 #[test]
346 fn test_validate_file_extension_multiple_dots() {
347 let path = Path::new("archive.tar.gz");
348 let allowed = vec!["gz".to_string()];
349
350 let result = validate_file_extension(path, "archive", &allowed);
352 assert!(result.is_ok());
353 }
354
355 #[test]
356 fn test_validate_file_extension_empty_allowed_list() {
357 let path = Path::new("file.txt");
358 let allowed: Vec<String> = vec![];
359
360 let result = validate_file_extension(path, "file", &allowed);
361 assert!(result.is_err());
362 }
363
364 #[test]
365 fn test_validate_file_extension_with_leading_dot() {
366 let path = Path::new("config.yaml");
369
370 let allowed = vec!["yaml".to_string()];
373
374 let result = validate_file_extension(path, "config", &allowed);
375 assert!(result.is_ok());
376 }
377
378 #[test]
383 fn test_validate_both_file_and_extension() {
384 let temp_dir = TempDir::new().unwrap();
385 let file_path = create_temp_file(&temp_dir, "config.yaml", "key: value");
386
387 let result1 = validate_file_exists(&file_path, "config_file");
389 assert!(result1.is_ok());
390
391 let allowed = vec!["yaml".to_string(), "yml".to_string()];
393 let result2 = validate_file_extension(&file_path, "config_file", &allowed);
394 assert!(result2.is_ok());
395 }
396
397 #[test]
398 fn test_validate_wrong_extension_existing_file() {
399 let temp_dir = TempDir::new().unwrap();
400 let file_path = create_temp_file(&temp_dir, "data.txt", "some data");
401
402 assert!(validate_file_exists(&file_path, "data_file").is_ok());
404
405 let allowed = vec!["csv".to_string()];
407 let result = validate_file_extension(&file_path, "data_file", &allowed);
408 assert!(result.is_err());
409 }
410
411 #[test]
412 fn test_validate_extension_nonexistent_file() {
413 let path = Path::new("nonexistent.yaml");
415 let allowed = vec!["yaml".to_string()];
416
417 let result = validate_file_extension(path, "config", &allowed);
419 assert!(result.is_ok());
420
421 let result2 = validate_file_exists(path, "config");
423 assert!(result2.is_err());
424 }
425}