anyhow_std/
path.rs

1use crate::fs::Metadata;
2use crate::fs::ReadDir;
3use anyhow::Context;
4use std::ffi::OsStr;
5use std::fs::{File, Permissions};
6use std::path::{Path, PathBuf};
7
8/// Extend [Path] with [anyhow] methods
9pub trait PathAnyhow {
10    /// Wrap [Path::to_str], providing the path as error context
11    fn to_str_anyhow(&self) -> anyhow::Result<&str>;
12
13    /// Wrap [Path::parent], providing the path as error context
14    fn parent_anyhow(&self) -> anyhow::Result<&Path>;
15
16    /// Wrap [Path::file_name], providing the path as error context
17    fn file_name_anyhow(&self) -> anyhow::Result<&OsStr>;
18
19    /// Wrap [Path::strip_prefix], providing the path and `base` as error context
20    fn strip_prefix_anyhow<P>(&self, base: P) -> anyhow::Result<&Path>
21    where
22        P: AsRef<Path>;
23
24    /// Wrap [Path::file_stem], providing the path as error context
25    fn file_stem_anyhow(&self) -> anyhow::Result<&OsStr>;
26
27    /// Wrap [Path::extension], providing the path as error context
28    fn extension_anyhow(&self) -> anyhow::Result<&OsStr>;
29
30    /// Wrap [Path::metadata], providing the path as error context
31    fn metadata_anyhow(&self) -> anyhow::Result<Metadata>;
32
33    /// Wrap [Path::symlink_metadata], providing the path as error context
34    fn symlink_metadata_anyhow(&self) -> anyhow::Result<Metadata>;
35
36    /// Wrap [Path::canonicalize], providing the path as error context
37    fn canonicalize_anyhow(&self) -> anyhow::Result<PathBuf>;
38
39    /// Wrap [Path::read_link], providing the path as error context
40    fn read_link_anyhow(&self) -> anyhow::Result<PathBuf>;
41
42    /// Wrap [Path::read_dir], providing the path as error context
43    fn read_dir_anyhow(&self) -> anyhow::Result<ReadDir>;
44
45    // Wrappers for std::fs:
46
47    /// Wrap [std::fs::copy] from `self` to `to`, providing `self` and `to` as error context
48    fn copy_anyhow<P>(&self, to: P) -> anyhow::Result<u64>
49    where
50        P: AsRef<Path>;
51
52    /// Wrap [std::fs::create_dir], providing the path as error context
53    fn create_dir_anyhow(&self) -> anyhow::Result<()>;
54
55    /// Wrap [std::fs::create_dir_all], providing the path as error context
56    fn create_dir_all_anyhow(&self) -> anyhow::Result<()>;
57
58    /// Wrap [std::fs::hard_link], providing `self` and `link` as error context
59    fn hard_link_anyhow<P>(&self, link: P) -> anyhow::Result<()>
60    where
61        P: AsRef<Path>;
62
63    /// Wrap [std::fs::read], providing the path as error context
64    fn read_anyhow(&self) -> anyhow::Result<Vec<u8>>;
65
66    /// Wrap [std::fs::read_to_string], providing the path as error context
67    fn read_to_string_anyhow(&self) -> anyhow::Result<String>;
68
69    /// Wrap [std::fs::remove_dir], providing the path as error context
70    fn remove_dir_anyhow(&self) -> anyhow::Result<()>;
71
72    /// Wrap [std::fs::remove_dir_all], providing the path as error context
73    fn remove_dir_all_anyhow(&self) -> anyhow::Result<()>;
74
75    /// Wrap [std::fs::remove_file], providing the path as error context
76    fn remove_file_anyhow(&self) -> anyhow::Result<()>;
77
78    /// Wrap [std::fs::rename], providing `self` and `to` as error context
79    fn rename_anyhow<P>(&self, to: P) -> anyhow::Result<()>
80    where
81        P: AsRef<Path>;
82
83    /// Wrap [std::fs::set_permissions], providing the path as error context
84    fn set_permissions_anyhow(&self, perm: Permissions) -> anyhow::Result<()>;
85
86    /// Toggle read-only permission for the path
87    ///
88    /// This method factors out the complexity of retrieving [std::fs::Permissions], modifying
89    /// them, and then setting them.
90    fn set_readonly_anyhow(&self, readonly: bool) -> anyhow::Result<()>;
91
92    /// Wrap [std::fs::write], providing the path as error context
93    fn write_anyhow<C>(&self, contents: C) -> anyhow::Result<()>
94    where
95        C: AsRef<[u8]>;
96
97    /// Wrap [std::env::set_current_dir], providing the path as error context
98    fn set_to_current_dir_anyhow(&self) -> anyhow::Result<()>;
99
100    // File APIs:
101    /// Open a [File] in read-only mode wrapping [File::open]
102    fn open_file_anyhow(&self) -> anyhow::Result<File>;
103
104    /// Open a [File] in write-only mode wrapping [File::create]
105    fn create_file_anyhow(&self) -> anyhow::Result<File>;
106}
107
108macro_rules! wrap_method {
109    ( $method:ident, $cb:expr, $ret:ty, None: $errordesc:expr ) => {
110        fn $method(&self) -> anyhow::Result<$ret> {
111            let p = self.as_ref();
112            $cb(p)
113                .ok_or_else(|| anyhow::Error::msg($errordesc))
114                .with_context(|| format!("while processing path {:?}", p.display()))
115        }
116    };
117
118    ( $method:ident, $cb:expr, $ret:ty ) => {
119        fn $method(&self) -> anyhow::Result<$ret> {
120            $cb(self).with_context(|| format!("while processing path {:?}", self.display()))
121        }
122    };
123
124    ( $method:ident, $cb:expr, AsRefPath: $arg:ident, $ret:ty ) => {
125        fn $method<Q>(&self, $arg: Q) -> anyhow::Result<$ret>
126        where
127            Q: AsRef<Path>,
128        {
129            let argref = $arg.as_ref();
130            $cb(self, argref)
131                .with_context(|| format!("with {} {:?}", stringify!($arg), argref.display()))
132                .with_context(|| format!("while processing path {:?}", self.display()))
133        }
134    };
135}
136
137impl PathAnyhow for Path {
138    wrap_method!(to_str_anyhow, Path::to_str, &str, None: "invalid UTF8");
139
140    wrap_method!(
141        parent_anyhow,
142        Path::parent,
143        &Path,
144        None: "expected parent directory"
145    );
146
147    wrap_method!(
148        file_name_anyhow,
149        Path::file_name,
150        &OsStr,
151        None: "missing expected filename"
152    );
153
154    wrap_method!(
155        strip_prefix_anyhow,
156        Path::strip_prefix,
157        AsRefPath: prefix,
158        &Path
159    );
160
161    wrap_method!(
162        file_stem_anyhow,
163        Path::file_stem,
164        &OsStr,
165        None: "missing expected filename"
166    );
167
168    wrap_method!(
169        extension_anyhow,
170        Path::extension,
171        &OsStr,
172        None: "missing expected extension"
173    );
174
175    wrap_method!(
176        metadata_anyhow,
177        |p: &Path| p.metadata().map(|md| Metadata::from((md, p.to_path_buf()))),
178        Metadata
179    );
180    wrap_method!(
181        symlink_metadata_anyhow,
182        |p: &Path| p
183            .symlink_metadata()
184            .map(|md| Metadata::from((md, p.to_path_buf()))),
185        Metadata
186    );
187    wrap_method!(canonicalize_anyhow, Path::canonicalize, PathBuf);
188    wrap_method!(read_link_anyhow, Path::read_link, PathBuf);
189    wrap_method!(read_dir_anyhow, ReadDir::from_path, ReadDir);
190    wrap_method!(copy_anyhow, std::fs::copy, AsRefPath: copy_to, u64);
191    wrap_method!(create_dir_anyhow, std::fs::create_dir, ());
192    wrap_method!(create_dir_all_anyhow, std::fs::create_dir_all, ());
193    wrap_method!(hard_link_anyhow, std::fs::hard_link, AsRefPath: link_to, ());
194    wrap_method!(read_anyhow, std::fs::read, Vec<u8>);
195    wrap_method!(read_to_string_anyhow, std::fs::read_to_string, String);
196    wrap_method!(remove_dir_anyhow, std::fs::remove_dir, ());
197    wrap_method!(remove_dir_all_anyhow, std::fs::remove_dir_all, ());
198    wrap_method!(remove_file_anyhow, std::fs::remove_file, ());
199    wrap_method!(rename_anyhow, std::fs::rename, AsRefPath: rename_to, ());
200
201    fn set_permissions_anyhow(&self, perms: Permissions) -> anyhow::Result<()> {
202        std::fs::set_permissions(self, perms.clone())
203            .with_context(|| format!("with permissions {:?}", perms))
204            .with_context(|| format!("while processing path {:?}", self.display()))
205    }
206
207    fn set_readonly_anyhow(&self, readonly: bool) -> anyhow::Result<()> {
208        let mut perms = self.metadata_anyhow()?.permissions();
209        perms.set_readonly(readonly);
210        self.set_permissions_anyhow(perms)
211    }
212
213    fn write_anyhow<C>(&self, contents: C) -> anyhow::Result<()>
214    where
215        C: AsRef<[u8]>,
216    {
217        std::fs::write(self, contents)
218            .with_context(|| format!("while writing to {:?}", self.display()))
219    }
220
221    wrap_method!(set_to_current_dir_anyhow, std::env::set_current_dir, ());
222    wrap_method!(open_file_anyhow, File::open, File);
223    wrap_method!(create_file_anyhow, File::create, File);
224}
225
226#[cfg(test)]
227mod tests;