1use std::{
4 path::{Path, PathBuf},
5 str::FromStr,
6};
7
8use crate::Error;
9
10#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
12pub enum DirOrFileType {
13 Dir,
15 File,
17}
18
19#[derive(Clone, Debug, Eq, PartialEq)]
24pub struct DirOrFile {
25 path: PathBuf,
26 pub typ: DirOrFileType,
28}
29
30impl AsRef<Path> for DirOrFile {
31 fn as_ref(&self) -> &Path {
32 &self.path
33 }
34}
35
36impl TryFrom<PathBuf> for DirOrFile {
37 type Error = Error;
38
39 fn try_from(value: PathBuf) -> Result<Self, Self::Error> {
45 if value.is_dir() {
46 Ok(Self {
47 path: value,
48 typ: DirOrFileType::Dir,
49 })
50 } else if value.is_file() {
51 Ok(Self {
52 path: value,
53 typ: DirOrFileType::File,
54 })
55 } else {
56 Err(crate::Error::PathIsNotDirOrFile { path: value })
57 }
58 }
59}
60
61impl TryFrom<&Path> for DirOrFile {
62 type Error = Error;
63
64 fn try_from(value: &Path) -> Result<Self, Self::Error> {
70 Self::try_from(value.to_path_buf())
71 }
72}
73
74impl FromStr for DirOrFile {
75 type Err = Error;
76
77 fn from_str(s: &str) -> Result<Self, Self::Err> {
83 Self::try_from(PathBuf::from(s))
84 }
85}
86
87#[derive(Clone, Debug, Eq, Hash, PartialEq)]
89pub struct RegularFile(PathBuf);
90
91impl AsRef<Path> for RegularFile {
92 fn as_ref(&self) -> &Path {
94 &self.0
95 }
96}
97
98impl TryFrom<PathBuf> for RegularFile {
99 type Error = Error;
100
101 fn try_from(value: PathBuf) -> Result<Self, Self::Error> {
107 if !value.is_file() {
108 return Err(Error::PathIsNotAFile { path: value });
109 }
110
111 Ok(Self(value))
112 }
113}
114
115impl FromStr for RegularFile {
116 type Err = Error;
117
118 fn from_str(s: &str) -> Result<Self, Self::Err> {
124 Self::try_from(PathBuf::from(s))
125 }
126}
127
128#[cfg(test)]
129mod tests {
130 use tempfile::{NamedTempFile, TempDir};
131 use testresult::TestResult;
132
133 use super::*;
134
135 #[test]
136 fn dir_or_file_from_str_is_dir() -> TestResult {
137 let temp = TempDir::new()?;
138 let Some(path) = temp.path().to_str() else {
139 return Err("Could not convert temporary dir to string slice".into());
140 };
141
142 let dir = DirOrFile::from_str(path)?;
143 assert_eq!(dir.typ, DirOrFileType::Dir);
144 assert_eq!(dir.as_ref(), temp.path());
145 Ok(())
146 }
147
148 #[test]
149 fn dir_or_file_from_str_is_file() -> TestResult {
150 let temp = NamedTempFile::new()?;
151 let Some(path) = temp.path().to_str() else {
152 return Err("Could not convert temporary dir to string slice".into());
153 };
154
155 let dir = DirOrFile::from_str(path)?;
156 assert_eq!(dir.typ, DirOrFileType::File);
157 assert_eq!(dir.as_ref(), temp.path());
158 Ok(())
159 }
160
161 #[test]
162 #[cfg(target_os = "linux")]
163 fn dir_or_file_from_str_fails_on_not_a_dir_or_a_file() -> TestResult {
164 let result = DirOrFile::from_str("/dev/urandom");
165 match result {
166 Ok(path) => {
167 return Err(format!(
168 "Succeeded to create a DirOrFile from {path:?} but should have failed"
169 )
170 .into());
171 }
172 Err(Error::PathIsNotDirOrFile { .. }) => {}
173 Err(error) => {
174 return Err(format!(
175 "Should have returned Error::PathIsNotDirOrFile, but returned: {error}"
176 )
177 .into());
178 }
179 }
180 Ok(())
181 }
182
183 #[test]
184 fn regular_file_from_str_succeeds() -> TestResult {
185 let temp = NamedTempFile::new()?;
186 let Some(path) = temp.path().to_str() else {
187 return Err("Could not convert temporary dir to string slice".into());
188 };
189
190 let file = RegularFile::from_str(path)?;
191 assert_eq!(file.as_ref(), temp.path());
192 Ok(())
193 }
194
195 #[test]
196 fn regular_file_from_str_fails_on_dir() -> TestResult {
197 let temp = TempDir::new()?;
198 let Some(path) = temp.path().to_str() else {
199 return Err("Could not convert temporary dir to string slice".into());
200 };
201
202 let result = RegularFile::from_str(path);
203 match result {
204 Ok(path) => {
205 return Err(format!(
206 "Succeeded to create a RegularFile from {path:?} but should have failed"
207 )
208 .into());
209 }
210 Err(Error::PathIsNotAFile { .. }) => {}
211 Err(error) => {
212 return Err(format!(
213 "Should have returned Error::PathIsNotAFile, but returned: {error}"
214 )
215 .into());
216 }
217 }
218 Ok(())
219 }
220}