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