1use std::path::{Path, PathBuf};
4use thiserror::Error;
5
6#[derive(Error, Debug, Clone)]
8pub enum PathValidationError {
9 #[error("Path not found: {0}")]
10 NotFound(PathBuf),
11
12 #[error("Path is not a file: {0}")]
13 NotAFile(PathBuf),
14
15 #[error("Path is not a directory: {0}")]
16 NotADirectory(PathBuf),
17
18 #[error("Path is not readable: {0}")]
19 NotReadable(PathBuf),
20}
21
22#[derive(Debug, Clone)]
26pub struct ScanTarget {
27 path: PathBuf,
28}
29
30impl ScanTarget {
31 pub fn new(path: impl AsRef<Path>) -> Result<Self, PathValidationError> {
33 let path = path.as_ref().to_path_buf();
34 if !path.exists() {
35 return Err(PathValidationError::NotFound(path));
36 }
37 Ok(Self { path })
38 }
39
40 pub fn file(path: impl AsRef<Path>) -> Result<Self, PathValidationError> {
42 let path = path.as_ref().to_path_buf();
43 if !path.exists() {
44 return Err(PathValidationError::NotFound(path));
45 }
46 if !path.is_file() {
47 return Err(PathValidationError::NotAFile(path));
48 }
49 Ok(Self { path })
50 }
51
52 pub fn directory(path: impl AsRef<Path>) -> Result<Self, PathValidationError> {
54 let path = path.as_ref().to_path_buf();
55 if !path.exists() {
56 return Err(PathValidationError::NotFound(path));
57 }
58 if !path.is_dir() {
59 return Err(PathValidationError::NotADirectory(path));
60 }
61 Ok(Self { path })
62 }
63
64 pub fn unchecked(path: impl AsRef<Path>) -> Self {
69 Self {
70 path: path.as_ref().to_path_buf(),
71 }
72 }
73
74 pub fn path(&self) -> &Path {
76 &self.path
77 }
78
79 pub fn to_path_buf(&self) -> PathBuf {
81 self.path.clone()
82 }
83
84 pub fn into_path_buf(self) -> PathBuf {
86 self.path
87 }
88
89 pub fn is_file(&self) -> bool {
91 self.path.is_file()
92 }
93
94 pub fn is_dir(&self) -> bool {
96 self.path.is_dir()
97 }
98
99 pub fn file_name(&self) -> Option<&std::ffi::OsStr> {
101 self.path.file_name()
102 }
103
104 pub fn parent(&self) -> Option<&Path> {
106 self.path.parent()
107 }
108}
109
110impl AsRef<Path> for ScanTarget {
111 fn as_ref(&self) -> &Path {
112 &self.path
113 }
114}
115
116impl From<ScanTarget> for PathBuf {
117 fn from(target: ScanTarget) -> Self {
118 target.path
119 }
120}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125 use std::fs;
126 use tempfile::tempdir;
127
128 #[test]
129 fn test_scan_target_valid_path() {
130 let dir = tempdir().unwrap();
131 let target = ScanTarget::new(dir.path());
132 assert!(target.is_ok());
133 assert!(target.unwrap().is_dir());
134 }
135
136 #[test]
137 fn test_scan_target_invalid_path() {
138 let result = ScanTarget::new("/nonexistent/path/12345");
139 assert!(result.is_err());
140 assert!(matches!(result, Err(PathValidationError::NotFound(_))));
141 }
142
143 #[test]
144 fn test_scan_target_file() {
145 let dir = tempdir().unwrap();
146 let file_path = dir.path().join("test.txt");
147 fs::write(&file_path, "test").unwrap();
148
149 let target = ScanTarget::file(&file_path);
150 assert!(target.is_ok());
151 assert!(target.unwrap().is_file());
152 }
153
154 #[test]
155 fn test_scan_target_file_on_directory() {
156 let dir = tempdir().unwrap();
157 let result = ScanTarget::file(dir.path());
158 assert!(result.is_err());
159 assert!(matches!(result, Err(PathValidationError::NotAFile(_))));
160 }
161
162 #[test]
163 fn test_scan_target_directory() {
164 let dir = tempdir().unwrap();
165 let target = ScanTarget::directory(dir.path());
166 assert!(target.is_ok());
167 assert!(target.unwrap().is_dir());
168 }
169
170 #[test]
171 fn test_scan_target_directory_on_file() {
172 let dir = tempdir().unwrap();
173 let file_path = dir.path().join("test.txt");
174 fs::write(&file_path, "test").unwrap();
175
176 let result = ScanTarget::directory(&file_path);
177 assert!(result.is_err());
178 assert!(matches!(result, Err(PathValidationError::NotADirectory(_))));
179 }
180
181 #[test]
182 fn test_scan_target_unchecked() {
183 let target = ScanTarget::unchecked("/any/path");
184 assert_eq!(target.path(), Path::new("/any/path"));
185 }
186
187 #[test]
188 fn test_scan_target_into_path_buf() {
189 let dir = tempdir().unwrap();
190 let target = ScanTarget::new(dir.path()).unwrap();
191 let path_buf: PathBuf = target.into();
192 assert_eq!(path_buf, dir.path());
193 }
194
195 #[test]
196 fn test_scan_target_to_path_buf() {
197 let dir = tempdir().unwrap();
198 let target = ScanTarget::new(dir.path()).unwrap();
199 let path_buf = target.to_path_buf();
200 assert_eq!(path_buf, dir.path());
201 }
202
203 #[test]
204 fn test_scan_target_into_path_buf_method() {
205 let dir = tempdir().unwrap();
206 let target = ScanTarget::new(dir.path()).unwrap();
207 let path_buf = target.into_path_buf();
208 assert_eq!(path_buf, dir.path());
209 }
210
211 #[test]
212 fn test_scan_target_file_name() {
213 let dir = tempdir().unwrap();
214 let file_path = dir.path().join("test.txt");
215 fs::write(&file_path, "test").unwrap();
216
217 let target = ScanTarget::file(&file_path).unwrap();
218 assert_eq!(target.file_name().unwrap(), "test.txt");
219 }
220
221 #[test]
222 fn test_scan_target_parent() {
223 let dir = tempdir().unwrap();
224 let file_path = dir.path().join("test.txt");
225 fs::write(&file_path, "test").unwrap();
226
227 let target = ScanTarget::file(&file_path).unwrap();
228 assert_eq!(target.parent().unwrap(), dir.path());
229 }
230
231 #[test]
232 fn test_scan_target_as_ref() {
233 let dir = tempdir().unwrap();
234 let target = ScanTarget::new(dir.path()).unwrap();
235 let path: &Path = target.as_ref();
236 assert_eq!(path, dir.path());
237 }
238
239 #[test]
240 fn test_scan_target_debug() {
241 let dir = tempdir().unwrap();
242 let target = ScanTarget::new(dir.path()).unwrap();
243 let debug_str = format!("{:?}", target);
244 assert!(debug_str.contains("ScanTarget"));
245 }
246
247 #[test]
248 fn test_scan_target_clone() {
249 let dir = tempdir().unwrap();
250 let target = ScanTarget::new(dir.path()).unwrap();
251 let cloned = target.clone();
252 assert_eq!(target.path(), cloned.path());
253 }
254
255 #[test]
256 fn test_path_validation_error_display() {
257 let err = PathValidationError::NotFound(PathBuf::from("/test"));
258 assert!(err.to_string().contains("/test"));
259
260 let err = PathValidationError::NotAFile(PathBuf::from("/test"));
261 assert!(err.to_string().contains("/test"));
262
263 let err = PathValidationError::NotADirectory(PathBuf::from("/test"));
264 assert!(err.to_string().contains("/test"));
265
266 let err = PathValidationError::NotReadable(PathBuf::from("/test"));
267 assert!(err.to_string().contains("/test"));
268 }
269
270 #[test]
271 fn test_path_validation_error_debug() {
272 let err = PathValidationError::NotFound(PathBuf::from("/test"));
273 let debug_str = format!("{:?}", err);
274 assert!(debug_str.contains("NotFound"));
275 }
276
277 #[test]
278 fn test_path_validation_error_clone() {
279 let err = PathValidationError::NotFound(PathBuf::from("/test"));
280 let cloned = err.clone();
281 assert!(matches!(cloned, PathValidationError::NotFound(_)));
282 }
283
284 #[test]
285 fn test_scan_target_file_not_found() {
286 let result = ScanTarget::file("/nonexistent/file.txt");
287 assert!(result.is_err());
288 assert!(matches!(result, Err(PathValidationError::NotFound(_))));
289 }
290
291 #[test]
292 fn test_scan_target_directory_not_found() {
293 let result = ScanTarget::directory("/nonexistent/dir");
294 assert!(result.is_err());
295 assert!(matches!(result, Err(PathValidationError::NotFound(_))));
296 }
297}