mail_template/
base_dir.rs

1use std::{
2    path::{Path, PathBuf},
3    ops::{Deref, DerefMut},
4    env, io
5};
6
7use serde::{
8    ser::{Serialize, Serializer},
9    de::{Deserialize, Deserializer}
10};
11
12#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
13pub struct CwdBaseDir(PathBuf);
14
15impl CwdBaseDir {
16
17    /// Creates a new `CwdBaseDir` instance containing exactly the given path.
18    pub fn new_unchanged(path: PathBuf) -> Self {
19        CwdBaseDir(path)
20    }
21
22    /// Creates a `CwdBaseDir` from a path by prefixing the path with the
23    /// current working dir if it's relative.
24    ///
25    /// If the path is not relative it's directly used.
26    ///
27    /// # Os state side effects
28    ///
29    /// As this function accesses the current working directory (CWD) it's
30    /// not pure as the CWD can be changed (e.g. by `std::env::set_current_dir`).
31    ///
32    /// # Error
33    ///
34    /// As getting the CWD can fail this function can fail with a I/O Error, too.
35    pub fn from_path<P>(path: P) -> Result<Self, io::Error>
36        where P: AsRef<Path> + Into<PathBuf>
37    {
38        let path =
39            if path.as_ref().is_absolute() {
40                path.into()
41            } else {
42                let mut cwd = env::current_dir()?;
43                cwd.push(path.as_ref());
44                cwd
45            };
46
47        Ok(CwdBaseDir(path))
48    }
49
50    /// Turns this path into a `PathBuf` by stripping the current working dir
51    /// if it starts with it.
52    ///
53    /// If this path does not start with the CWD it's returned directly.
54    ///
55    /// # Os state side effects
56    ///
57    /// As this function used the  current working dir (CWD) it is affected
58    /// by any function changing the CWD as a side effect.
59    ///
60    /// # Error
61    ///
62    /// Accessing the current working dir can fail, as such this function
63    /// can fail.
64    pub fn to_base_path(&self) -> Result<&Path, io::Error> {
65        let cwd = env::current_dir()?;
66        self.strip_prefix(&cwd)
67            .or_else(|_err_does_not_has_that_prefix| {
68                Ok(&self)
69            })
70    }
71
72    /// Turns this instance into the `PathBuf` it dereferences to.
73    pub fn into_inner_with_prefix(self) -> PathBuf {
74        let CwdBaseDir(path) = self;
75        path
76    }
77}
78
79impl Deref for CwdBaseDir {
80    type Target = PathBuf;
81
82    fn deref(&self) -> &Self::Target {
83        &self.0
84    }
85}
86
87impl DerefMut for CwdBaseDir {
88    fn deref_mut(&mut self) -> &mut Self::Target {
89        &mut self.0
90    }
91}
92
93impl AsRef<Path> for CwdBaseDir {
94    fn as_ref(&self) -> &Path {
95        &self.0
96    }
97}
98
99
100
101impl<'de> Deserialize<'de> for CwdBaseDir {
102    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
103        where D: Deserializer<'de>
104    {
105        use serde::de::Error;
106        let path_buf = PathBuf::deserialize(deserializer)?;
107        Self::from_path(path_buf)
108            .map_err(|err| D::Error::custom(err))
109    }
110}
111
112impl Serialize for CwdBaseDir {
113    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
114        where S: Serializer,
115    {
116        use serde::ser::Error;
117        let path = self.to_base_path()
118            .map_err(|err| S::Error::custom(err))?;
119
120        path.serialize(serializer)
121    }
122}
123
124
125#[cfg(test)]
126mod tests {
127    use super::*;
128
129    #[test]
130    fn from_path_does_not_affect_absolute_paths() {
131        let path = Path::new("/the/dog");
132        let base_dir = CwdBaseDir::from_path(path).unwrap();
133        assert_eq!(&*base_dir, Path::new("/the/dog"))
134    }
135
136    #[test]
137    fn from_path_prefixes_with_cwd() {
138        let cwd = env::current_dir().unwrap();
139        let expected = cwd.join("./the/dog");
140
141        let base_dir = CwdBaseDir::from_path("./the/dog").unwrap();
142        assert_eq!(&*base_dir, &expected);
143    }
144
145    #[test]
146    fn to_base_path_removes_cwd_prefix() {
147        let cwd = env::current_dir().unwrap();
148        let dir = cwd.join("hy/there");
149        let base_dir = CwdBaseDir::new_unchanged(dir);
150        let path = base_dir.to_base_path().unwrap();
151        assert_eq!(path, Path::new("hy/there"));
152    }
153}