voa/
utils.rs

1//! Utilities for library and CLI.
2
3use std::{
4    fmt::Display,
5    path::{Path, PathBuf},
6    str::FromStr,
7};
8
9use voa_core::identifiers::Purpose;
10
11use crate::Error;
12
13/// Directory or regular file.
14#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
15pub enum DirOrFileType {
16    /// A directory.
17    Dir,
18    /// A regular file.
19    File,
20}
21
22/// A path that is guaranteed to be a directory or regular file.
23///
24/// Wraps a [`PathBuf`] and a [`DirOrFileType`] which indicates whether a directory or regular file
25/// is targeted.
26#[derive(Clone, Debug, Eq, PartialEq)]
27pub struct DirOrFile {
28    path: PathBuf,
29    /// The type of path (either a directory or a regular file).
30    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    /// Creates a [`DirOrFile`] from [`PathBuf`].
43    ///
44    /// # Errors
45    ///
46    /// Returns an error if `value` represents neither a directory, nor a regular file.
47    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    /// Creates a [`DirOrFile`] from [`Path`] reference.
68    ///
69    /// # Errors
70    ///
71    /// Returns an error if `value` represents neither a directory, nor a regular file.
72    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    /// Creates a [`DirOrFile`] from a string slice.
81    ///
82    /// # Errors
83    ///
84    /// Returns an error if `value` represents neither a directory, nor a regular file.
85    fn from_str(s: &str) -> Result<Self, Self::Err> {
86        Self::try_from(PathBuf::from(s))
87    }
88}
89
90/// A path that is guaranteed to be a regular file.
91#[derive(Clone, Debug, Eq, Hash, PartialEq)]
92pub struct RegularFile(PathBuf);
93
94impl AsRef<Path> for RegularFile {
95    /// Returns a reference to the wrapped path.
96    fn as_ref(&self) -> &Path {
97        &self.0
98    }
99}
100
101impl TryFrom<PathBuf> for RegularFile {
102    type Error = Error;
103
104    /// Creates a [`RegularFile`] from a [`PathBuf`].
105    ///
106    /// # Errors
107    ///
108    /// Returns an error if `value` does not represent a regular file.
109    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    /// Creates a [`RegularFile`] from a string slice.
122    ///
123    /// # Errors
124    ///
125    /// Returns an error if `s` does not represent a regular file.
126    fn from_str(s: &str) -> Result<Self, Self::Err> {
127        Self::try_from(PathBuf::from(s))
128    }
129}
130
131/// A wrapper for a [`Purpose`] that is guaranteed to be for an artifact verifier.
132#[derive(Clone, Debug)]
133pub struct ArtifactVerifierPurpose(Purpose);
134
135impl ArtifactVerifierPurpose {
136    /// Creates a new [`ArtifactVerifierPurpose`].
137    ///
138    /// # Errors
139    ///
140    /// Returns an error if the provided `purpose` represents the trust anchor mode.
141    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    /// Returns a reference to the wrapped [`Purpose`].
152    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}