mail_template/
path_rebase.rs

1use std::{
2    path::{Path, PathBuf},
3    mem
4};
5
6use mail_core::{IRI, Resource};
7use failure::{Fail, Context};
8
9#[derive(Fail, Debug)]
10#[fail(display = "unsupported path, only paths with following constraint are allowed: {}", _0)]
11pub struct UnsupportedPathError(Context<&'static str>);
12
13impl UnsupportedPathError {
14    pub fn new(violated_constraint: &'static str) -> Self {
15        UnsupportedPathError(Context::new(violated_constraint))
16    }
17}
18
19pub trait PathRebaseable {
20    /// Prefixes path in the type with `base_dir`.
21    ///
22    /// # Error
23    ///
24    /// Some implementors might not support all paths.
25    /// For example a implementor requiring rust string
26    /// compatible paths might return a
27    /// `Err(UnsupportedPathError::new("utf-8"))`.
28    fn rebase_to_include_base_dir(&mut self, base_dir: impl AsRef<Path>)
29        -> Result<(), UnsupportedPathError>;
30
31    /// Removes the `base_dir` prefix.
32    ///
33    /// # Error
34    ///
35    /// Some implementors might not support all paths.
36    /// For example a implementor requiring rust string
37    /// compatible paths might return a
38    /// `Err(UnsupportedPathError::new("utf-8"))`.
39    fn rebase_to_exclude_base_dir(&mut self, base_dir: impl AsRef<Path>)
40        -> Result<(), UnsupportedPathError>;
41}
42
43impl PathRebaseable for PathBuf {
44    fn rebase_to_include_base_dir(&mut self, base_dir: impl AsRef<Path>)
45        -> Result<(), UnsupportedPathError>
46    {
47        let new_path;
48        if self.is_relative() {
49            new_path = base_dir.as_ref().join(&self);
50        } else {
51            return Ok(());
52        }
53        mem::replace(self, new_path);
54        Ok(())
55    }
56
57    fn rebase_to_exclude_base_dir(&mut self, base_dir: impl AsRef<Path>)
58        -> Result<(), UnsupportedPathError>
59    {
60        let new_path;
61        if let Ok(path) = self.strip_prefix(base_dir) {
62            new_path = path.to_owned();
63        } else {
64            return Ok(());
65        }
66        mem::replace(self, new_path);
67        Ok(())
68    }
69}
70
71impl PathRebaseable for IRI {
72    fn rebase_to_include_base_dir(&mut self, base_dir: impl AsRef<Path>)
73        -> Result<(), UnsupportedPathError>
74    {
75        if self.scheme() != "path" {
76            return Ok(());
77        }
78
79        let new_tail = {
80            let path = Path::new(self.tail());
81            if path.is_relative() {
82                base_dir.as_ref().join(path)
83            } else {
84                return Ok(());
85            }
86        };
87
88        let new_tail = new_tail.to_str()
89            .ok_or_else(|| UnsupportedPathError::new("utf-8"))?;
90
91        let new_iri = self.with_tail(new_tail);
92        mem::replace(self, new_iri);
93        Ok(())
94    }
95
96    fn rebase_to_exclude_base_dir(&mut self, base_dir: impl AsRef<Path>)
97        -> Result<(), UnsupportedPathError>
98    {
99        if self.scheme() != "path" {
100            return Ok(());
101        }
102
103        let new_iri = {
104            let path = Path::new(self.tail());
105
106            if let Ok(path) = path.strip_prefix(base_dir) {
107                //UNWRAP_SAFE: we just striped some parts, this can
108                // not make it lose it's string-ness
109                let new_tail = path.to_str().unwrap();
110                self.with_tail(new_tail)
111            } else {
112                return Ok(());
113            }
114        };
115
116        mem::replace(self, new_iri);
117        Ok(())
118    }
119}
120
121impl PathRebaseable for Resource {
122    fn rebase_to_include_base_dir(&mut self, base_dir: impl AsRef<Path>)
123        -> Result<(), UnsupportedPathError>
124    {
125        if let &mut Resource::Source(ref mut source) = self {
126            source.iri.rebase_to_include_base_dir(base_dir)?;
127        }
128        Ok(())
129    }
130
131    fn rebase_to_exclude_base_dir(&mut self, base_dir: impl AsRef<Path>)
132        -> Result<(), UnsupportedPathError>
133    {
134        if let &mut Resource::Source(ref mut source) = self {
135            source.iri.rebase_to_exclude_base_dir(base_dir)?;
136        }
137        Ok(())
138    }
139
140}
141
142
143
144#[cfg(test)]
145mod test {
146    use super::*;
147    use mail_core::Source;
148
149    #[test]
150    fn rebase_on_path() {
151        let mut path = Path::new("/prefix/suffix.yup").to_owned();
152        path.rebase_to_exclude_base_dir("/prefix").unwrap();
153        assert_eq!(path, Path::new("suffix.yup"));
154        path.rebase_to_include_base_dir("./nfix").unwrap();
155        path.rebase_to_include_base_dir("/mfix").unwrap();
156        assert_eq!(path, Path::new("/mfix/nfix/suffix.yup"));
157        path.rebase_to_exclude_base_dir("/wrong").unwrap();
158        assert_eq!(path, Path::new("/mfix/nfix/suffix.yup"));
159    }
160
161    #[test]
162    fn rebase_on_iri() {
163        let mut iri: IRI = "path:/prefix/suffix.yup".parse().unwrap();
164        iri.rebase_to_exclude_base_dir("/prefix").unwrap();
165        assert_eq!(iri.as_str(), "path:suffix.yup");
166        iri.rebase_to_include_base_dir("nfix").unwrap();
167        iri.rebase_to_include_base_dir("/mfix").unwrap();
168        assert_eq!(iri.as_str(), "path:/mfix/nfix/suffix.yup");
169        iri.rebase_to_exclude_base_dir("/wrong").unwrap();
170        assert_eq!(iri.as_str(), "path:/mfix/nfix/suffix.yup");
171    }
172
173    #[test]
174    fn rebase_on_resource() {
175        let mut resource = Resource::Source(Source {
176            iri: "path:abc/def".parse().unwrap(),
177            use_media_type: Default::default(),
178            use_file_name: Default::default()
179        });
180
181        resource.rebase_to_include_base_dir("./abc").unwrap();
182        resource.rebase_to_include_base_dir("/pre").unwrap();
183        resource.rebase_to_exclude_base_dir("/pre").unwrap();
184        resource.rebase_to_exclude_base_dir("abc").unwrap();
185        resource.rebase_to_include_base_dir("abc").unwrap();
186
187        if let Resource::Source(Source { iri, ..}) = resource {
188            assert_eq!(iri.as_str(), "path:abc/abc/def");
189        } else { unreachable!() }
190    }
191}