shadow_crypt_shell/encryption/
validation.rs

1use std::path::PathBuf;
2
3use crate::{
4    encryption::{cli::CliArgs, file::EncryptionInputFile},
5    errors::{WorkflowError, WorkflowResult},
6};
7
8pub struct ValidEncryptionArgs {
9    pub files: Vec<EncryptionInputFile>,
10    pub test_mode: bool,
11}
12
13pub fn validate_input(input: CliArgs) -> WorkflowResult<ValidEncryptionArgs> {
14    ensure_not_empty(&input)?;
15
16    let validated_files: Vec<EncryptionInputFile> = input
17        .input_files
18        .iter()
19        .map(PathBuf::from)
20        .map(ensure_exists)
21        .map(ensure_is_regular_file)
22        .map(create_input_file)
23        .collect::<WorkflowResult<Vec<EncryptionInputFile>>>()?;
24
25    Ok(ValidEncryptionArgs {
26        files: validated_files,
27        test_mode: input.test_mode,
28    })
29}
30
31fn ensure_not_empty(input: &CliArgs) -> WorkflowResult<()> {
32    if input.input_files.is_empty() {
33        return Err(WorkflowError::UserInput(
34            "No input files provided".to_string(),
35        ));
36    }
37    Ok(())
38}
39
40fn ensure_exists(path: PathBuf) -> WorkflowResult<PathBuf> {
41    if !path.exists() {
42        return Err(WorkflowError::UserInput(format!(
43            "Input file does not exist: {}",
44            path.display()
45        )));
46    }
47    Ok(path)
48}
49
50fn ensure_is_regular_file(path: WorkflowResult<PathBuf>) -> WorkflowResult<PathBuf> {
51    if let Ok(path) = &path
52        && !path.is_file()
53    {
54        return Err(WorkflowError::UserInput(format!(
55            "Input path is not a file: {}",
56            path.display()
57        )));
58    }
59    path
60}
61
62fn create_input_file(path: WorkflowResult<PathBuf>) -> WorkflowResult<EncryptionInputFile> {
63    let path = path?;
64    let name: String = path
65        .file_name()
66        .and_then(|n| n.to_str())
67        .ok_or_else(|| {
68            WorkflowError::UserInput(format!("Invalid filename for path: {}", path.display()))
69        })?
70        .to_string();
71    let size: u64 = path
72        .metadata()
73        .map_err(|_| {
74            WorkflowError::UserInput(format!(
75                "Unable to read metadata for file: {}",
76                path.display()
77            ))
78        })?
79        .len();
80
81    Ok(EncryptionInputFile {
82        path,
83        filename: name,
84        size,
85    })
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91    use std::io::Write;
92    use tempfile::NamedTempFile;
93
94    #[test]
95    fn test_validate_input_no_files() {
96        let input = CliArgs {
97            input_files: vec![],
98            test_mode: false,
99        };
100        let result = validate_input(input);
101        assert!(result.is_err());
102        if let Err(WorkflowError::UserInput(msg)) = result {
103            assert_eq!(msg, "No input files provided");
104        } else {
105            panic!("Expected UserInput error");
106        }
107    }
108
109    #[test]
110    fn test_validate_input_file_does_not_exist() {
111        let input = CliArgs {
112            input_files: vec!["nonexistent_file.txt".to_string()],
113            test_mode: false,
114        };
115        let result = validate_input(input);
116        assert!(result.is_err());
117        if let Err(WorkflowError::UserInput(msg)) = result {
118            assert!(msg.contains("Input file does not exist"));
119        } else {
120            panic!("Expected UserInput error");
121        }
122    }
123
124    #[test]
125    fn test_validate_input_path_is_directory() {
126        let temp_dir = tempfile::tempdir().unwrap();
127        let input = CliArgs {
128            input_files: vec![temp_dir.path().to_str().unwrap().to_string()],
129            test_mode: false,
130        };
131        let result = validate_input(input);
132        assert!(result.is_err());
133        if let Err(WorkflowError::UserInput(msg)) = result {
134            assert!(msg.contains("Input path is not a file"));
135        } else {
136            panic!("Expected UserInput error");
137        }
138    }
139
140    #[test]
141    fn test_validate_input_valid_file() {
142        let mut temp_file = NamedTempFile::new().unwrap();
143        let content = b"Hello, world!";
144        temp_file.write_all(content).unwrap();
145        let file_path = temp_file.path().to_path_buf();
146
147        let input = CliArgs {
148            input_files: vec![file_path.to_str().unwrap().to_string()],
149            test_mode: true,
150        };
151        let result = validate_input(input);
152        assert!(result.is_ok());
153        let valid_args = result.unwrap();
154        assert_eq!(valid_args.files.len(), 1);
155        assert!(valid_args.test_mode);
156        let file = &valid_args.files[0];
157        assert_eq!(file.path, file_path);
158        assert_eq!(
159            file.filename,
160            file_path.file_name().unwrap().to_str().unwrap()
161        );
162        assert_eq!(file.size, content.len() as u64);
163    }
164
165    #[test]
166    fn test_validate_input_multiple_files() {
167        let mut temp_file1 = NamedTempFile::new().unwrap();
168        temp_file1.write_all(b"File 1").unwrap();
169        let path1 = temp_file1.path().to_path_buf();
170
171        let mut temp_file2 = NamedTempFile::new().unwrap();
172        temp_file2.write_all(b"File 2 content").unwrap();
173        let path2 = temp_file2.path().to_path_buf();
174
175        let input = CliArgs {
176            input_files: vec![
177                path1.to_str().unwrap().to_string(),
178                path2.to_str().unwrap().to_string(),
179            ],
180            test_mode: false,
181        };
182        let result = validate_input(input);
183        assert!(result.is_ok());
184        let valid_args = result.unwrap();
185        assert_eq!(valid_args.files.len(), 2);
186        assert!(!valid_args.test_mode);
187
188        // Check first file
189        let file1 = &valid_args.files[0];
190        assert_eq!(file1.path, path1);
191        assert_eq!(file1.filename, path1.file_name().unwrap().to_str().unwrap());
192        assert_eq!(file1.size, 6); // "File 1".len()
193
194        // Check second file
195        let file2 = &valid_args.files[1];
196        assert_eq!(file2.path, path2);
197        assert_eq!(file2.filename, path2.file_name().unwrap().to_str().unwrap());
198        assert_eq!(file2.size, 14); // "File 2 content".len()
199    }
200}