mirror of
https://github.com/nushell/nushell.git
synced 2025-08-12 13:17:36 +02:00
More precise ErrorKind::NotFound
errors (#15149)
In this PR, the two new variants for `ErrorKind`, `FileNotFound` and `DirectoryNotFound` with a nice `not_found_as` method for the `ErrorKind` to easily specify the `NotFound` errors. I also updated some places where I could of think of with these new variants and the message for `NotFound` is no longer "Entity not found" but "Not found" to be less strange. closes #15142 closes #15055
This commit is contained in:
@ -1,6 +1,7 @@
|
||||
use miette::{Diagnostic, LabeledSpan, SourceSpan};
|
||||
use std::{
|
||||
fmt::Display,
|
||||
error::Error as StdError,
|
||||
fmt::{self, Display, Formatter},
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use thiserror::Error;
|
||||
@ -37,11 +38,7 @@ use super::{location::Location, ShellError};
|
||||
///
|
||||
/// # let span = Span::test_data();
|
||||
/// let path = PathBuf::from("/some/missing/file");
|
||||
/// let error = IoError::new(
|
||||
/// std::io::ErrorKind::NotFound,
|
||||
/// span,
|
||||
/// path
|
||||
/// );
|
||||
/// let error = IoError::new(std::io::ErrorKind::NotFound, span, path);
|
||||
/// println!("Error: {:?}", error);
|
||||
/// ```
|
||||
///
|
||||
@ -87,9 +84,8 @@ use super::{location::Location, ShellError};
|
||||
/// This approach ensures clarity about where such container transfers occur.
|
||||
/// All other I/O errors should be handled using the provided constructors for `IoError`.
|
||||
/// This way, the code explicitly indicates when and where a `ShellError` transfer might happen.
|
||||
#[derive(Debug, Clone, Error, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[non_exhaustive]
|
||||
#[error("I/O error")]
|
||||
pub struct IoError {
|
||||
/// The type of the underlying I/O error.
|
||||
///
|
||||
@ -133,6 +129,9 @@ pub struct IoError {
|
||||
pub enum ErrorKind {
|
||||
Std(std::io::ErrorKind),
|
||||
NotAFile,
|
||||
// use these variants in cases where we know precisely whether a file or directory was expected
|
||||
FileNotFound,
|
||||
DirectoryNotFound,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Error, Diagnostic)]
|
||||
@ -269,8 +268,8 @@ impl IoError {
|
||||
///
|
||||
/// # Examples
|
||||
/// ```rust
|
||||
/// use std::path::PathBuf;
|
||||
/// use nu_protocol::shell_error::io::IoError;
|
||||
/// use std::path::PathBuf;
|
||||
///
|
||||
/// let error = IoError::new_internal_with_path(
|
||||
/// std::io::ErrorKind::NotFound,
|
||||
@ -309,15 +308,30 @@ impl IoError {
|
||||
}
|
||||
}
|
||||
|
||||
impl StdError for IoError {}
|
||||
impl Display for IoError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
match self.kind {
|
||||
ErrorKind::Std(std::io::ErrorKind::NotFound) => write!(f, "Not found"),
|
||||
ErrorKind::FileNotFound => write!(f, "File not found"),
|
||||
ErrorKind::DirectoryNotFound => write!(f, "Directory not found"),
|
||||
_ => write!(f, "I/O error"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ErrorKind {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
ErrorKind::Std(std::io::ErrorKind::NotFound) => write!(f, "Not found"),
|
||||
ErrorKind::Std(error_kind) => {
|
||||
let msg = error_kind.to_string();
|
||||
let (first, rest) = msg.split_at(1);
|
||||
write!(f, "{}{}", first.to_uppercase(), rest)
|
||||
}
|
||||
ErrorKind::NotAFile => write!(f, "Not a file"),
|
||||
ErrorKind::FileNotFound => write!(f, "File not found"),
|
||||
ErrorKind::DirectoryNotFound => write!(f, "Directory not found"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -352,15 +366,28 @@ impl Diagnostic for IoError {
|
||||
kind => code.push_str(&kind.to_string().to_lowercase().replace(" ", "_")),
|
||||
},
|
||||
ErrorKind::NotAFile => code.push_str("not_a_file"),
|
||||
ErrorKind::FileNotFound => code.push_str("file_not_found"),
|
||||
ErrorKind::DirectoryNotFound => code.push_str("directory_not_found"),
|
||||
}
|
||||
|
||||
Some(Box::new(code))
|
||||
}
|
||||
|
||||
fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
|
||||
let make_msg = |path: &Path| {
|
||||
let path = format!("'{}'", path.display());
|
||||
match self.kind {
|
||||
ErrorKind::NotAFile => format!("{path} is not a file"),
|
||||
ErrorKind::Std(std::io::ErrorKind::NotFound)
|
||||
| ErrorKind::FileNotFound
|
||||
| ErrorKind::DirectoryNotFound => format!("{path} does not exist"),
|
||||
_ => format!("The error occurred at {path}"),
|
||||
}
|
||||
};
|
||||
|
||||
self.path
|
||||
.as_ref()
|
||||
.map(|path| format!("The error occurred at '{}'", path.display()))
|
||||
.map(|path| make_msg(path))
|
||||
.map(|s| Box::new(s) as Box<dyn std::fmt::Display>)
|
||||
}
|
||||
|
||||
@ -417,3 +444,75 @@ impl From<ErrorKind> for std::io::ErrorKind {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// More specific variants of [`NotFound`](std::io::ErrorKind).
|
||||
///
|
||||
/// Use these to define how a `NotFound` error maps to our custom [`ErrorKind`].
|
||||
pub enum NotFound {
|
||||
/// Map into [`FileNotFound`](ErrorKind::FileNotFound).
|
||||
File,
|
||||
/// Map into [`DirectoryNotFound`](ErrorKind::DirectoryNotFound).
|
||||
Directory,
|
||||
}
|
||||
|
||||
/// Extension trait for working with [`std::io::ErrorKind`].
|
||||
pub trait ErrorKindExt {
|
||||
/// Map [`NotFound`](std::io::ErrorKind) variants into more precise variants.
|
||||
///
|
||||
/// The OS doesn't know when an entity was not found whether it was meant to be a file or a
|
||||
/// directory or something else.
|
||||
/// But sometimes we, the application, know what we expect and with this method, we can further
|
||||
/// specify it.
|
||||
///
|
||||
/// # Examples
|
||||
/// Reading a file.
|
||||
/// If the file isn't found, return [`FileNotFound`](ErrorKind::FileNotFound).
|
||||
/// ```rust
|
||||
/// # use nu_protocol::{
|
||||
/// # shell_error::io::{ErrorKind, ErrorKindExt, IoError, NotFound},
|
||||
/// # ShellError, Span,
|
||||
/// # };
|
||||
/// # use std::{fs, path::PathBuf};
|
||||
/// #
|
||||
/// # fn example() -> Result<(), ShellError> {
|
||||
/// # let span = Span::test_data();
|
||||
/// let a_file = PathBuf::from("scripts/ellie.nu");
|
||||
/// let ellie = fs::read_to_string(&a_file).map_err(|err| {
|
||||
/// ShellError::Io(IoError::new(
|
||||
/// err.kind().not_found_as(NotFound::File),
|
||||
/// span,
|
||||
/// a_file,
|
||||
/// ))
|
||||
/// })?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// #
|
||||
/// # assert!(matches!(
|
||||
/// # example(),
|
||||
/// # Err(ShellError::Io(IoError {
|
||||
/// # kind: ErrorKind::FileNotFound,
|
||||
/// # ..
|
||||
/// # }))
|
||||
/// # ));
|
||||
/// ```
|
||||
fn not_found_as(self, kind: NotFound) -> ErrorKind;
|
||||
}
|
||||
|
||||
impl ErrorKindExt for std::io::ErrorKind {
|
||||
fn not_found_as(self, kind: NotFound) -> ErrorKind {
|
||||
match (kind, self) {
|
||||
(NotFound::File, Self::NotFound) => ErrorKind::FileNotFound,
|
||||
(NotFound::Directory, Self::NotFound) => ErrorKind::DirectoryNotFound,
|
||||
_ => ErrorKind::Std(self),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ErrorKindExt for ErrorKind {
|
||||
fn not_found_as(self, kind: NotFound) -> ErrorKind {
|
||||
match self {
|
||||
Self::Std(std_kind) => std_kind.not_found_as(kind),
|
||||
_ => self,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user