1use std::{
4 fmt::Display,
5 path::{Path, PathBuf},
6 str::FromStr,
7};
8
9use voa_core::identifiers::Purpose;
10
11use crate::Error;
12
13#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
15pub enum DirOrFileType {
16 Dir,
18 File,
20}
21
22#[derive(Clone, Debug, Eq, PartialEq)]
27pub struct DirOrFile {
28 path: PathBuf,
29 pub typ: DirOrFileType,
31}
32
33impl AsRef<Path> for DirOrFile {
34 fn as_ref(&self) -> &Path {
35 &self.path
36 }
37}
38
39impl TryFrom<PathBuf> for DirOrFile {
40 type Error = Error;
41
42 fn try_from(value: PathBuf) -> Result<Self, Self::Error> {
48 if value.is_dir() {
49 Ok(Self {
50 path: value,
51 typ: DirOrFileType::Dir,
52 })
53 } else if value.is_file() {
54 Ok(Self {
55 path: value,
56 typ: DirOrFileType::File,
57 })
58 } else {
59 Err(crate::Error::PathIsNotDirOrFile { path: value })
60 }
61 }
62}
63
64impl TryFrom<&Path> for DirOrFile {
65 type Error = Error;
66
67 fn try_from(value: &Path) -> Result<Self, Self::Error> {
73 Self::try_from(value.to_path_buf())
74 }
75}
76
77impl FromStr for DirOrFile {
78 type Err = Error;
79
80 fn from_str(s: &str) -> Result<Self, Self::Err> {
86 Self::try_from(PathBuf::from(s))
87 }
88}
89
90#[derive(Clone, Debug, Eq, Hash, PartialEq)]
92pub struct RegularFile(PathBuf);
93
94impl AsRef<Path> for RegularFile {
95 fn as_ref(&self) -> &Path {
97 &self.0
98 }
99}
100
101impl TryFrom<PathBuf> for RegularFile {
102 type Error = Error;
103
104 fn try_from(value: PathBuf) -> Result<Self, Self::Error> {
110 if !value.is_file() {
111 return Err(Error::PathIsNotAFile { path: value });
112 }
113
114 Ok(Self(value))
115 }
116}
117
118impl FromStr for RegularFile {
119 type Err = Error;
120
121 fn from_str(s: &str) -> Result<Self, Self::Err> {
127 Self::try_from(PathBuf::from(s))
128 }
129}
130
131#[derive(Clone, Debug)]
133pub struct ArtifactVerifierPurpose(Purpose);
134
135impl ArtifactVerifierPurpose {
136 pub fn new(purpose: Purpose) -> Result<Self, Error> {
142 if purpose.is_trust_anchor() {
143 return Err(Error::PurposeIsATrustAnchor { purpose });
144 }
145
146 Ok(Self(purpose))
147 }
148}
149
150impl AsRef<Purpose> for ArtifactVerifierPurpose {
151 fn as_ref(&self) -> &Purpose {
153 &self.0
154 }
155}
156
157impl Display for ArtifactVerifierPurpose {
158 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
159 write!(f, "{}", self.0)
160 }
161}
162
163impl FromStr for ArtifactVerifierPurpose {
164 type Err = Error;
165
166 fn from_str(s: &str) -> Result<Self, Self::Err> {
167 Self::new(Purpose::from_str(s)?)
168 }
169}
170
171#[cfg(test)]
172mod tests {
173 use tempfile::{NamedTempFile, TempDir};
174 use testresult::TestResult;
175
176 use super::*;
177
178 #[test]
179 fn dir_or_file_from_str_is_dir() -> TestResult {
180 let temp = TempDir::new()?;
181 let Some(path) = temp.path().to_str() else {
182 panic!("Could not convert temporary dir to string slice");
183 };
184
185 let dir = DirOrFile::from_str(path)?;
186 assert_eq!(dir.typ, DirOrFileType::Dir);
187 assert_eq!(dir.as_ref(), temp.path());
188 Ok(())
189 }
190
191 #[test]
192 fn dir_or_file_from_str_is_file() -> TestResult {
193 let temp = NamedTempFile::new()?;
194 let Some(path) = temp.path().to_str() else {
195 panic!("Could not convert temporary dir to string slice");
196 };
197
198 let dir = DirOrFile::from_str(path)?;
199 assert_eq!(dir.typ, DirOrFileType::File);
200 assert_eq!(dir.as_ref(), temp.path());
201 Ok(())
202 }
203
204 #[test]
205 #[cfg(target_os = "linux")]
206 fn dir_or_file_from_str_fails_on_not_a_dir_or_a_file() -> TestResult {
207 let result = DirOrFile::from_str("/dev/urandom");
208 match result {
209 Ok(path) => {
210 panic!("Succeeded to create a DirOrFile from {path:?} but should have failed");
211 }
212 Err(Error::PathIsNotDirOrFile { .. }) => {}
213 Err(error) => {
214 panic!("Should have returned Error::PathIsNotDirOrFile, but returned: {error}");
215 }
216 }
217 Ok(())
218 }
219
220 #[test]
221 fn regular_file_from_str_succeeds() -> TestResult {
222 let temp = NamedTempFile::new()?;
223 let Some(path) = temp.path().to_str() else {
224 panic!("Could not convert temporary dir to string slice");
225 };
226
227 let file = RegularFile::from_str(path)?;
228 assert_eq!(file.as_ref(), temp.path());
229 Ok(())
230 }
231
232 #[test]
233 fn regular_file_from_str_fails_on_dir() -> TestResult {
234 let temp = TempDir::new()?;
235 let Some(path) = temp.path().to_str() else {
236 panic!("Could not convert temporary dir to string slice");
237 };
238
239 let result = RegularFile::from_str(path);
240 match result {
241 Ok(path) => {
242 panic!("Succeeded to create a RegularFile from {path:?} but should have failed");
243 }
244 Err(Error::PathIsNotAFile { .. }) => {}
245 Err(error) => {
246 panic!("Should have returned Error::PathIsNotAFile, but returned: {error}");
247 }
248 }
249 Ok(())
250 }
251
252 #[test]
253 fn artifact_verifier_purpose_from_str_succeeds() -> TestResult {
254 let artifact_verifier_purpose = ArtifactVerifierPurpose::from_str("some-purpose")?;
255 let _purpose = artifact_verifier_purpose.as_ref();
256 println!("{artifact_verifier_purpose}");
257
258 Ok(())
259 }
260
261 #[test]
262 fn artifact_verifier_purpose_from_str_fails() -> TestResult {
263 match ArtifactVerifierPurpose::from_str("trust-anchor-some-purpose") {
264 Err(Error::PurposeIsATrustAnchor { .. }) => {}
265 Ok(purpose) => panic!(
266 "Should have failed with Error::PurposeIsATrustAnchor but succeeded instead: {purpose}"
267 ),
268 Err(error) => panic!(
269 "Should have failed with Error::PurposeIsATrustAnchor but failed differently: {error}"
270 ),
271 }
272 Ok(())
273 }
274}