ggen_cli_lib/validation_lib/
io_validator.rs1use crate::validation_lib::error::{Result, ValidationError};
7use crate::validation_lib::security::{Permission, PermissionModel};
8use std::fs;
9use std::path::Path;
10
11#[derive(Debug)]
13pub struct IoValidator {
14 permission_model: PermissionModel,
16}
17
18impl Default for IoValidator {
19 fn default() -> Self {
20 Self::new()
21 }
22}
23
24impl IoValidator {
25 #[must_use]
27 pub fn new() -> Self {
28 Self {
29 permission_model: PermissionModel::new(),
30 }
31 }
32
33 #[must_use]
35 pub fn with_permissions(permission_model: PermissionModel) -> Self {
36 Self { permission_model }
37 }
38
39 pub fn validate_read(&self, path: &Path) -> Result<()> {
46 self.permission_model
48 .check_permission(path, Permission::Read)?;
49
50 if !path.exists() {
52 return Err(ValidationError::FileNotFound {
53 path: path.display().to_string(),
54 });
55 }
56
57 if !path.is_file() {
59 return Err(ValidationError::InvalidPath {
60 path: path.display().to_string(),
61 reason: "Path is not a file".to_string(),
62 });
63 }
64
65 fs::File::open(path).map_err(|e| ValidationError::ReadFailed {
67 path: path.display().to_string(),
68 reason: e.to_string(),
69 })?;
70
71 Ok(())
72 }
73
74 pub fn validate_write(&self, path: &Path) -> Result<()> {
81 self.permission_model
83 .check_permission(path, Permission::Write)?;
84
85 if let Some(parent) = path.parent() {
87 if !parent.exists() {
88 return Err(ValidationError::InvalidPath {
89 path: path.display().to_string(),
90 reason: format!("Parent directory {} does not exist", parent.display()),
91 });
92 }
93
94 if let Ok(metadata) = fs::metadata(parent) {
96 if metadata.permissions().readonly() {
97 return Err(ValidationError::WriteFailed {
98 path: path.display().to_string(),
99 reason: "Parent directory is read-only".to_string(),
100 });
101 }
102 }
103 }
104
105 Ok(())
106 }
107
108 pub fn validate_reads(&self, paths: &[&Path]) -> Result<Vec<PathValidation>> {
110 Ok(paths
111 .iter()
112 .map(|path| {
113 let result = self.validate_read(path);
114 PathValidation {
115 path: path.to_path_buf(),
116 valid: result.is_ok(),
117 error: result.err(),
118 }
119 })
120 .collect())
121 }
122
123 pub fn validate_writes(&self, paths: &[&Path]) -> Result<Vec<PathValidation>> {
125 Ok(paths
126 .iter()
127 .map(|path| {
128 let result = self.validate_write(path);
129 PathValidation {
130 path: path.to_path_buf(),
131 valid: result.is_ok(),
132 error: result.err(),
133 }
134 })
135 .collect())
136 }
137}
138
139#[derive(Debug)]
141pub struct PathValidation {
142 pub path: std::path::PathBuf,
144 pub valid: bool,
146 pub error: Option<ValidationError>,
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153 use std::fs::File;
154 use std::io::Write;
155 use tempfile::tempdir;
156
157 #[test]
158 fn test_validate_read_existing_file() {
159 let dir = tempdir().expect("Failed to create temp dir");
160 let file_path = dir.path().join("test.txt");
161 let mut file = File::create(&file_path).expect("Failed to create file");
162 writeln!(file, "test content").expect("Failed to write");
163
164 let validator = IoValidator::new();
165 assert!(validator.validate_read(&file_path).is_ok());
166 }
167
168 #[test]
169 fn test_validate_read_missing_file() {
170 let validator = IoValidator::new();
171 let result = validator.validate_read(Path::new("/nonexistent/file.txt"));
172 assert!(result.is_err());
173 assert!(matches!(result, Err(ValidationError::FileNotFound { .. })));
174 }
175
176 #[test]
177 fn test_validate_write_existing_directory() {
178 let dir = tempdir().expect("Failed to create temp dir");
179 let file_path = dir.path().join("output.txt");
180
181 let validator = IoValidator::new();
182 assert!(validator.validate_write(&file_path).is_ok());
183 }
184
185 #[test]
186 fn test_validate_write_missing_parent() {
187 let validator = IoValidator::new();
188 let result = validator.validate_write(Path::new("/nonexistent/dir/file.txt"));
189 assert!(result.is_err());
190 }
191
192 #[test]
193 fn test_batch_read_validation() {
194 let dir = tempdir().expect("Failed to create temp dir");
195 let file1 = dir.path().join("file1.txt");
196 let file2 = dir.path().join("file2.txt");
197
198 File::create(&file1).expect("Failed to create file1");
199 File::create(&file2).expect("Failed to create file2");
200
201 let validator = IoValidator::new();
202 let paths = vec![file1.as_path(), file2.as_path()];
203 let results = validator.validate_reads(&paths);
204
205 assert!(results.is_ok());
206 let validations = results.expect("Validation should succeed");
207 assert_eq!(validations.len(), 2);
208 assert!(validations.iter().all(|v| v.valid));
209 }
210}