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}