walkdir/
error.rs

1use std::error;
2use std::fmt;
3use std::io;
4use std::path::{Path, PathBuf};
5
6use 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)]
28pub struct Error {
29    depth: usize,
30    inner: ErrorInner,
31}
32
33#[derive(Debug)]
34enum ErrorInner {
35    Io { path: Option<PathBuf>, err: io::Error },
36    Loop { ancestor: PathBuf, child: PathBuf },
37}
38
39impl 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 {
168            depth: depth,
169            inner: ErrorInner::Io { path: Some(pb), err: err },
170        }
171    }
172
173    pub(crate) fn from_entry(dent: &DirEntry, err: io::Error) -> Self {
174        Error {
175            depth: dent.depth(),
176            inner: ErrorInner::Io {
177                path: Some(dent.path().to_path_buf()),
178                err: err,
179            },
180        }
181    }
182
183    pub(crate) fn from_io(depth: usize, err: io::Error) -> Self {
184        Error { depth: depth, inner: ErrorInner::Io { path: None, err: err } }
185    }
186
187    pub(crate) fn from_loop(
188        depth: usize,
189        ancestor: &Path,
190        child: &Path,
191    ) -> Self {
192        Error {
193            depth: depth,
194            inner: ErrorInner::Loop {
195                ancestor: ancestor.to_path_buf(),
196                child: child.to_path_buf(),
197            },
198        }
199    }
200}
201
202impl error::Error for Error {
203    #[allow(deprecated)]
204    fn description(&self) -> &str {
205        match self.inner {
206            ErrorInner::Io { ref err, .. } => err.description(),
207            ErrorInner::Loop { .. } => "file system loop found",
208        }
209    }
210
211    fn cause(&self) -> Option<&dyn error::Error> {
212        self.source()
213    }
214
215    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
216        match self.inner {
217            ErrorInner::Io { ref err, .. } => Some(err),
218            ErrorInner::Loop { .. } => None,
219        }
220    }
221}
222
223impl fmt::Display for Error {
224    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
225        match self.inner {
226            ErrorInner::Io { path: None, ref err } => err.fmt(f),
227            ErrorInner::Io { path: Some(ref path), ref err } => write!(
228                f,
229                "IO error for operation on {}: {}",
230                path.display(),
231                err
232            ),
233            ErrorInner::Loop { ref ancestor, ref child } => write!(
234                f,
235                "File system loop found: \
236                 {} points to an ancestor {}",
237                child.display(),
238                ancestor.display()
239            ),
240        }
241    }
242}
243
244impl From<Error> for io::Error {
245    /// Convert the [`Error`] to an [`io::Error`], preserving the original
246    /// [`Error`] as the ["inner error"]. Note that this also makes the display
247    /// of the error include the context.
248    ///
249    /// This is different from [`into_io_error`] which returns the original
250    /// [`io::Error`].
251    ///
252    /// [`Error`]: struct.Error.html
253    /// [`io::Error`]: https://doc.rust-lang.org/stable/std/io/struct.Error.html
254    /// ["inner error"]: https://doc.rust-lang.org/std/io/struct.Error.html#method.into_inner
255    /// [`into_io_error`]: struct.WalkDir.html#method.into_io_error
256    fn from(walk_err: Error) -> io::Error {
257        let kind = match walk_err {
258            Error { inner: ErrorInner::Io { ref err, .. }, .. } => err.kind(),
259            Error { inner: ErrorInner::Loop { .. }, .. } => {
260                io::ErrorKind::Other
261            }
262        };
263        io::Error::new(kind, walk_err)
264    }
265}