voa/
utils.rs

1//! Utilities for library and CLI.
2
3use std::{
4    path::{Path, PathBuf},
5    str::FromStr,
6};
7
8use crate::Error;
9
10/// Directory or regular file.
11#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
12pub enum DirOrFileType {
13    /// A directory.
14    Dir,
15    /// A regular file.
16    File,
17}
18
19/// A path that is guaranteed to be a directory or regular file.
20///
21/// Wraps a [`PathBuf`] and a [`DirOrFileType`] which indicates whether a directory or regular file
22/// is targeted.
23#[derive(Clone, Debug, Eq, PartialEq)]
24pub struct DirOrFile {
25    path: PathBuf,
26    /// The type of path (either a directory or a regular file).
27    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    /// Creates a [`DirOrFile`] from [`PathBuf`].
40    ///
41    /// # Errors
42    ///
43    /// Returns an error if `value` represents neither a directory, nor a regular file.
44    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    /// Creates a [`DirOrFile`] from [`Path`] reference.
65    ///
66    /// # Errors
67    ///
68    /// Returns an error if `value` represents neither a directory, nor a regular file.
69    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    /// Creates a [`DirOrFile`] from a string slice.
78    ///
79    /// # Errors
80    ///
81    /// Returns an error if `value` represents neither a directory, nor a regular file.
82    fn from_str(s: &str) -> Result<Self, Self::Err> {
83        Self::try_from(PathBuf::from(s))
84    }
85}
86
87#[cfg(test)]
88mod tests {
89    use tempfile::{NamedTempFile, TempDir};
90    use testresult::TestResult;
91
92    use super::*;
93
94    #[test]
95    fn dir_or_file_from_str_is_dir() -> TestResult {
96        let temp = TempDir::new()?;
97        let Some(path) = temp.path().to_str() else {
98            return Err("Could not convert temporary dir to string slice".into());
99        };
100
101        let dir = DirOrFile::from_str(path)?;
102        assert_eq!(dir.typ, DirOrFileType::Dir);
103        assert_eq!(dir.as_ref(), temp.path());
104        Ok(())
105    }
106
107    #[test]
108    fn dir_or_file_from_str_is_file() -> TestResult {
109        let temp = NamedTempFile::new()?;
110        let Some(path) = temp.path().to_str() else {
111            return Err("Could not convert temporary dir to string slice".into());
112        };
113
114        let dir = DirOrFile::from_str(path)?;
115        assert_eq!(dir.typ, DirOrFileType::File);
116        assert_eq!(dir.as_ref(), temp.path());
117        Ok(())
118    }
119
120    #[test]
121    #[cfg(target_os = "linux")]
122    fn dir_or_file_from_str_fails_on_not_a_dir_or_a_file() -> TestResult {
123        let result = DirOrFile::from_str("/dev/urandom");
124        match result {
125            Ok(path) => {
126                return Err(format!(
127                    "Succeeded to create a DirOrFile from {path:?} but should have failed"
128                )
129                .into());
130            }
131            Err(Error::PathIsNotDirOrFile { .. }) => {}
132            Err(error) => {
133                return Err(format!(
134                    "Should have returned Error::PathIsNotDirOrFile, but returned: {error}"
135                )
136                .into());
137            }
138        }
139        Ok(())
140    }
141}