1 | use std::error; |
2 | use std::fmt; |
3 | use std::io; |
4 | use std::path::{Path, PathBuf}; |
5 | |
6 | use crate::DirEntry; |
7 | |
8 | /// An error produced by recursively walking a directory. |
9 | /// |
10 | /// This error type is a light wrapper around [`std::io::Error`]. In |
11 | /// particular, it adds the following information: |
12 | /// |
13 | /// * The depth at which the error occurred in the file tree, relative to the |
14 | /// root. |
15 | /// * The path, if any, associated with the IO error. |
16 | /// * An indication that a loop occurred when following symbolic links. In this |
17 | /// case, there is no underlying IO error. |
18 | /// |
19 | /// To maintain good ergonomics, this type has a |
20 | /// [`impl From<Error> for std::io::Error`][impl] defined which preserves the original context. |
21 | /// This allows you to use an [`io::Result`] with methods in this crate if you don't care about |
22 | /// accessing the underlying error data in a structured form. |
23 | /// |
24 | /// [`std::io::Error`]: https://doc.rust-lang.org/stable/std/io/struct.Error.html |
25 | /// [`io::Result`]: https://doc.rust-lang.org/stable/std/io/type.Result.html |
26 | /// [impl]: struct.Error.html#impl-From%3CError%3E |
27 | #[derive(Debug)] |
28 | pub struct Error { |
29 | depth: usize, |
30 | inner: ErrorInner, |
31 | } |
32 | |
33 | #[derive(Debug)] |
34 | enum ErrorInner { |
35 | Io { path: Option<PathBuf>, err: io::Error }, |
36 | Loop { ancestor: PathBuf, child: PathBuf }, |
37 | } |
38 | |
39 | impl Error { |
40 | /// Returns the path associated with this error if one exists. |
41 | /// |
42 | /// For example, if an error occurred while opening a directory handle, |
43 | /// the error will include the path passed to [`std::fs::read_dir`]. |
44 | /// |
45 | /// [`std::fs::read_dir`]: https://doc.rust-lang.org/stable/std/fs/fn.read_dir.html |
46 | pub fn path(&self) -> Option<&Path> { |
47 | match self.inner { |
48 | ErrorInner::Io { path: None, .. } => None, |
49 | ErrorInner::Io { path: Some(ref path), .. } => Some(path), |
50 | ErrorInner::Loop { ref child, .. } => Some(child), |
51 | } |
52 | } |
53 | |
54 | /// Returns the path at which a cycle was detected. |
55 | /// |
56 | /// If no cycle was detected, [`None`] is returned. |
57 | /// |
58 | /// A cycle is detected when a directory entry is equivalent to one of |
59 | /// its ancestors. |
60 | /// |
61 | /// To get the path to the child directory entry in the cycle, use the |
62 | /// [`path`] method. |
63 | /// |
64 | /// [`None`]: https://doc.rust-lang.org/stable/std/option/enum.Option.html#variant.None |
65 | /// [`path`]: struct.Error.html#path |
66 | pub fn loop_ancestor(&self) -> Option<&Path> { |
67 | match self.inner { |
68 | ErrorInner::Loop { ref ancestor, .. } => Some(ancestor), |
69 | _ => None, |
70 | } |
71 | } |
72 | |
73 | /// Returns the depth at which this error occurred relative to the root. |
74 | /// |
75 | /// The smallest depth is `0` and always corresponds to the path given to |
76 | /// the [`new`] function on [`WalkDir`]. Its direct descendents have depth |
77 | /// `1`, and their descendents have depth `2`, and so on. |
78 | /// |
79 | /// [`new`]: struct.WalkDir.html#method.new |
80 | /// [`WalkDir`]: struct.WalkDir.html |
81 | pub fn depth(&self) -> usize { |
82 | self.depth |
83 | } |
84 | |
85 | /// Inspect the original [`io::Error`] if there is one. |
86 | /// |
87 | /// [`None`] is returned if the [`Error`] doesn't correspond to an |
88 | /// [`io::Error`]. This might happen, for example, when the error was |
89 | /// produced because a cycle was found in the directory tree while |
90 | /// following symbolic links. |
91 | /// |
92 | /// This method returns a borrowed value that is bound to the lifetime of the [`Error`]. To |
93 | /// obtain an owned value, the [`into_io_error`] can be used instead. |
94 | /// |
95 | /// > This is the original [`io::Error`] and is _not_ the same as |
96 | /// > [`impl From<Error> for std::io::Error`][impl] which contains additional context about the |
97 | /// error. |
98 | /// |
99 | /// # Example |
100 | /// |
101 | /// ```rust,no_run |
102 | /// use std::io; |
103 | /// use std::path::Path; |
104 | /// |
105 | /// use walkdir::WalkDir; |
106 | /// |
107 | /// for entry in WalkDir::new("foo" ) { |
108 | /// match entry { |
109 | /// Ok(entry) => println!("{}" , entry.path().display()), |
110 | /// Err(err) => { |
111 | /// let path = err.path().unwrap_or(Path::new("" )).display(); |
112 | /// println!("failed to access entry {}" , path); |
113 | /// if let Some(inner) = err.io_error() { |
114 | /// match inner.kind() { |
115 | /// io::ErrorKind::InvalidData => { |
116 | /// println!( |
117 | /// "entry contains invalid data: {}" , |
118 | /// inner) |
119 | /// } |
120 | /// io::ErrorKind::PermissionDenied => { |
121 | /// println!( |
122 | /// "Missing permission to read entry: {}" , |
123 | /// inner) |
124 | /// } |
125 | /// _ => { |
126 | /// println!( |
127 | /// "Unexpected error occurred: {}" , |
128 | /// inner) |
129 | /// } |
130 | /// } |
131 | /// } |
132 | /// } |
133 | /// } |
134 | /// } |
135 | /// ``` |
136 | /// |
137 | /// [`None`]: https://doc.rust-lang.org/stable/std/option/enum.Option.html#variant.None |
138 | /// [`io::Error`]: https://doc.rust-lang.org/stable/std/io/struct.Error.html |
139 | /// [`From`]: https://doc.rust-lang.org/stable/std/convert/trait.From.html |
140 | /// [`Error`]: struct.Error.html |
141 | /// [`into_io_error`]: struct.Error.html#method.into_io_error |
142 | /// [impl]: struct.Error.html#impl-From%3CError%3E |
143 | pub fn io_error(&self) -> Option<&io::Error> { |
144 | match self.inner { |
145 | ErrorInner::Io { ref err, .. } => Some(err), |
146 | ErrorInner::Loop { .. } => None, |
147 | } |
148 | } |
149 | |
150 | /// Similar to [`io_error`] except consumes self to convert to the original |
151 | /// [`io::Error`] if one exists. |
152 | /// |
153 | /// [`io_error`]: struct.Error.html#method.io_error |
154 | /// [`io::Error`]: https://doc.rust-lang.org/stable/std/io/struct.Error.html |
155 | pub fn into_io_error(self) -> Option<io::Error> { |
156 | match self.inner { |
157 | ErrorInner::Io { err, .. } => Some(err), |
158 | ErrorInner::Loop { .. } => None, |
159 | } |
160 | } |
161 | |
162 | pub(crate) fn from_path( |
163 | depth: usize, |
164 | pb: PathBuf, |
165 | err: io::Error, |
166 | ) -> Self { |
167 | Error { depth, inner: ErrorInner::Io { path: Some(pb), err } } |
168 | } |
169 | |
170 | pub(crate) fn from_entry(dent: &DirEntry, err: io::Error) -> Self { |
171 | Error { |
172 | depth: dent.depth(), |
173 | inner: ErrorInner::Io { |
174 | path: Some(dent.path().to_path_buf()), |
175 | err, |
176 | }, |
177 | } |
178 | } |
179 | |
180 | pub(crate) fn from_io(depth: usize, err: io::Error) -> Self { |
181 | Error { depth, inner: ErrorInner::Io { path: None, err } } |
182 | } |
183 | |
184 | pub(crate) fn from_loop( |
185 | depth: usize, |
186 | ancestor: &Path, |
187 | child: &Path, |
188 | ) -> Self { |
189 | Error { |
190 | depth, |
191 | inner: ErrorInner::Loop { |
192 | ancestor: ancestor.to_path_buf(), |
193 | child: child.to_path_buf(), |
194 | }, |
195 | } |
196 | } |
197 | } |
198 | |
199 | impl error::Error for Error { |
200 | #[allow (deprecated)] |
201 | fn description(&self) -> &str { |
202 | match self.inner { |
203 | ErrorInner::Io { ref err, .. } => err.description(), |
204 | ErrorInner::Loop { .. } => "file system loop found" , |
205 | } |
206 | } |
207 | |
208 | fn cause(&self) -> Option<&dyn error::Error> { |
209 | self.source() |
210 | } |
211 | |
212 | fn source(&self) -> Option<&(dyn error::Error + 'static)> { |
213 | match self.inner { |
214 | ErrorInner::Io { ref err, .. } => Some(err), |
215 | ErrorInner::Loop { .. } => None, |
216 | } |
217 | } |
218 | } |
219 | |
220 | impl fmt::Display for Error { |
221 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
222 | match self.inner { |
223 | ErrorInner::Io { path: None, ref err } => err.fmt(f), |
224 | ErrorInner::Io { path: Some(ref path), ref err } => write!( |
225 | f, |
226 | "IO error for operation on {}: {}" , |
227 | path.display(), |
228 | err |
229 | ), |
230 | ErrorInner::Loop { ref ancestor, ref child } => write!( |
231 | f, |
232 | "File system loop found: \ |
233 | {} points to an ancestor {}" , |
234 | child.display(), |
235 | ancestor.display() |
236 | ), |
237 | } |
238 | } |
239 | } |
240 | |
241 | impl From<Error> for io::Error { |
242 | /// Convert the [`Error`] to an [`io::Error`], preserving the original |
243 | /// [`Error`] as the ["inner error"]. Note that this also makes the display |
244 | /// of the error include the context. |
245 | /// |
246 | /// This is different from [`into_io_error`] which returns the original |
247 | /// [`io::Error`]. |
248 | /// |
249 | /// [`Error`]: struct.Error.html |
250 | /// [`io::Error`]: https://doc.rust-lang.org/stable/std/io/struct.Error.html |
251 | /// ["inner error"]: https://doc.rust-lang.org/std/io/struct.Error.html#method.into_inner |
252 | /// [`into_io_error`]: struct.WalkDir.html#method.into_io_error |
253 | fn from(walk_err: Error) -> io::Error { |
254 | let kind = match walk_err { |
255 | Error { inner: ErrorInner::Io { ref err, .. }, .. } => err.kind(), |
256 | Error { inner: ErrorInner::Loop { .. }, .. } => { |
257 | io::ErrorKind::Other |
258 | } |
259 | }; |
260 | io::Error::new(kind, walk_err) |
261 | } |
262 | } |
263 | |