nu_protocol/errors/shell_error/
io.rs

1#[cfg(doc)] // allow mentioning this in doc comments
2use super::ShellError;
3use miette::{Diagnostic, LabeledSpan, SourceSpan};
4use std::{
5    error::Error as StdError,
6    fmt::{self, Display, Formatter},
7    path::{Path, PathBuf},
8};
9use thiserror::Error;
10
11use crate::Span;
12
13use super::location::Location;
14
15/// Alias for a `Result` with the error type [`ErrorKind`] by default.
16///
17/// This may be used in all situations that would usually return an [`std::io::Error`] but are
18/// already part of the [`nu_protocol`](crate) crate and can therefore interact with
19/// [`shell_error::io`](self) directly.
20///
21/// To make programming inside this module easier, you can pass the `E` type with another error.
22/// This avoids the annoyance of having a shadowed `Result`.
23pub type Result<T, E = ErrorKind> = std::result::Result<T, E>;
24
25/// Represents an I/O error in the [`ShellError::Io`] variant.
26///
27/// This is the central I/O error for the [`ShellError::Io`] variant.
28/// It represents all I/O errors by encapsulating [`ErrorKind`], an extension of
29/// [`std::io::ErrorKind`].
30/// The `span` indicates where the error occurred in user-provided code.
31/// If the error is not tied to user-provided code, the `location` refers to the precise point in
32/// the Rust code where the error originated.
33/// The optional `path` provides the file or directory involved in the error.
34/// If [`ErrorKind`] alone doesn't provide enough detail, additional context can be added to clarify
35/// the issue.
36///
37/// For handling user input errors (e.g., commands), prefer using [`new`](Self::new).
38/// Alternatively, use the [`factory`](Self::factory) method to simplify error creation in repeated
39/// contexts.
40/// For internal errors, use [`new_internal`](Self::new_internal) to include the location in Rust
41/// code where the error originated.
42///
43/// # Examples
44///
45/// ## User Input Error
46/// ```rust
47/// # use nu_protocol::shell_error::io::{IoError, ErrorKind};
48/// # use nu_protocol::Span;
49/// use std::path::PathBuf;
50///
51/// # let span = Span::test_data();
52/// let path = PathBuf::from("/some/missing/file");
53/// let error = IoError::new(ErrorKind::FileNotFound, span, path);
54/// println!("Error: {:?}", error);
55/// ```
56///
57/// ## Internal Error
58/// ```rust
59/// # use nu_protocol::shell_error::io::{IoError, ErrorKind};
60//  #
61/// let error = IoError::new_internal(
62///     ErrorKind::from_std(std::io::ErrorKind::UnexpectedEof),
63///     "Failed to read data from buffer",
64///     nu_protocol::location!()
65/// );
66/// println!("Error: {:?}", error);
67/// ```
68///
69/// ## Using the Factory Method
70/// ```rust
71/// # use nu_protocol::shell_error::io::{IoError, ErrorKind};
72/// # use nu_protocol::{Span, ShellError};
73/// use std::path::PathBuf;
74///
75/// # fn should_return_err() -> Result<(), ShellError> {
76/// # let span = Span::new(50, 60);
77/// let path = PathBuf::from("/some/file");
78/// let from_io_error = IoError::factory(span, Some(path.as_path()));
79///
80/// let content = std::fs::read_to_string(&path).map_err(from_io_error)?;
81/// # Ok(())
82/// # }
83/// #
84/// # assert!(should_return_err().is_err());
85/// ```
86///
87/// # ShellErrorBridge
88///
89/// The [`ShellErrorBridge`](super::bridge::ShellErrorBridge) struct is used to contain a
90/// [`ShellError`] inside a [`std::io::Error`].
91/// This allows seamless transfer of `ShellError` instances where `std::io::Error` is expected.
92/// When a `ShellError` needs to be packed into an I/O context, use this bridge.
93/// Similarly, when handling an I/O error that is expected to contain a `ShellError`,
94/// use the bridge to unpack it.
95///
96/// This approach ensures clarity about where such container transfers occur.
97/// All other I/O errors should be handled using the provided constructors for `IoError`.
98/// This way, the code explicitly indicates when and where a `ShellError` transfer might happen.
99#[derive(Debug, Clone, PartialEq)]
100#[non_exhaustive]
101pub struct IoError {
102    /// The type of the underlying I/O error.
103    ///
104    /// [`std::io::ErrorKind`] provides detailed context about the type of I/O error that occurred
105    /// and is part of [`std::io::Error`].
106    /// If a kind cannot be represented by it, consider adding a new variant to [`ErrorKind`].
107    ///
108    /// Only in very rare cases should [`std::io::Error::other()`] be used, make sure you provide
109    /// `additional_context` to get useful errors in these cases.
110    pub kind: ErrorKind,
111
112    /// The source location of the error.
113    pub span: Span,
114
115    /// The path related to the I/O error, if applicable.
116    ///
117    /// Many I/O errors involve a file or directory path, but operating system error messages
118    /// often don't include the specific path.
119    /// Setting this to [`Some`] allows users to see which path caused the error.
120    pub path: Option<PathBuf>,
121
122    /// Additional details to provide more context about the error.
123    ///
124    /// Only set this field if it adds meaningful context.
125    /// If [`ErrorKind`] already contains all the necessary information, leave this as [`None`].
126    pub additional_context: Option<AdditionalContext>,
127
128    /// The precise location in the Rust code where the error originated.
129    ///
130    /// This field is particularly useful for debugging errors that stem from the Rust
131    /// implementation rather than user-provided Nushell code.
132    /// The original [`Location`] is converted to a string to more easily report the error
133    /// attributing the location.
134    ///
135    /// This value is only used if `span` is [`Span::unknown()`] as most of the time we want to
136    /// refer to user code than the Rust code.
137    pub location: Option<String>,
138}
139
140/// Prevents other crates from constructing certain enum variants directly.
141///
142/// This type is only used to block construction while still allowing pattern matching.
143/// It's not meant to be used for anything else.
144#[derive(Debug, Clone, Copy, PartialEq, Eq)]
145struct Sealed;
146
147#[derive(Debug, Clone, Copy, PartialEq, Eq, Diagnostic)]
148pub enum ErrorKind {
149    /// [`std::io::ErrorKind`] from the standard library.
150    ///
151    /// This variant wraps a standard library error kind and extends our own error enum with it.
152    /// The hidden field prevents other crates, even our own, from constructing this directly.
153    /// Most of the time, you already have a full [`std::io::Error`], so just pass that directly to
154    /// [`IoError::new`] or [`IoError::new_with_additional_context`].
155    /// This allows us to inspect the raw os error of `std::io::Error`s.
156    ///
157    /// Matching is still easy:
158    ///
159    /// ```rust
160    /// # use nu_protocol::shell_error::io::ErrorKind;
161    /// #
162    /// # let err_kind = ErrorKind::from_std(std::io::ErrorKind::NotFound);
163    /// match err_kind {
164    ///     ErrorKind::Std(std::io::ErrorKind::NotFound, ..) => { /* ... */ }
165    ///     _ => {}
166    /// }
167    /// ```
168    ///
169    /// If you want to provide an [`std::io::ErrorKind`] manually, use [`ErrorKind::from_std`].
170    #[allow(private_interfaces)]
171    Std(std::io::ErrorKind, Sealed),
172
173    /// Killing a job process failed.
174    ///
175    /// This error is part [`ShellError::Io`](super::ShellError::Io) instead of
176    /// [`ShellError::Job`](super::ShellError::Job) as this error occurs because some I/O operation
177    /// failed on the OS side.
178    /// And not part of our logic.
179    KillJobProcess,
180
181    NotAFile,
182
183    /// The file or directory is in use by another program.
184    ///
185    /// On Windows, this maps to
186    /// [`ERROR_SHARING_VIOLATION`](::windows::Win32::Foundation::ERROR_SHARING_VIOLATION) and
187    /// prevents access like deletion or modification.
188    #[cfg_attr(not(windows), allow(rustdoc::broken_intra_doc_links))]
189    AlreadyInUse,
190
191    // use these variants in cases where we know precisely whether a file or directory was expected
192    FileNotFound,
193    DirectoryNotFound,
194}
195
196impl ErrorKind {
197    /// Construct an [`ErrorKind`] from a [`std::io::ErrorKind`] without a full [`std::io::Error`].
198    ///
199    /// In most cases, you should use [`IoError::new`] and pass the full [`std::io::Error`] instead.
200    /// This method is only meant for cases where we provide our own io error kinds.
201    pub fn from_std(kind: std::io::ErrorKind) -> Self {
202        Self::Std(kind, Sealed)
203    }
204}
205
206#[derive(Debug, Clone, PartialEq, Eq, Error, Diagnostic)]
207#[error("{0}")]
208pub struct AdditionalContext(String);
209
210impl From<String> for AdditionalContext {
211    fn from(value: String) -> Self {
212        AdditionalContext(value)
213    }
214}
215
216impl IoError {
217    /// Creates a new [`IoError`] with the given kind, span, and optional path.
218    ///
219    /// This constructor should be used in all cases where the combination of the error kind, span,
220    /// and path provides enough information to describe the error clearly.
221    /// For example, errors like "File not found" or "Permission denied" are typically
222    /// self-explanatory when paired with the file path and the location in user-provided
223    /// Nushell code (`span`).
224    ///
225    /// # Constraints
226    /// If `span` is unknown, use:
227    /// - `new_internal` if no path is available.
228    /// - `new_internal_with_path` if a path is available.
229    pub fn new(kind: impl Into<ErrorKind>, span: Span, path: impl Into<Option<PathBuf>>) -> Self {
230        let path = path.into();
231
232        if span == Span::unknown() {
233            debug_assert!(
234                path.is_some(),
235                "for unknown spans with paths, use `new_internal_with_path`"
236            );
237            debug_assert!(
238                path.is_none(),
239                "for unknown spans without paths, use `new_internal`"
240            );
241        }
242
243        Self {
244            kind: kind.into(),
245            span,
246            path,
247            additional_context: None,
248            location: None,
249        }
250    }
251
252    /// Creates a new [`IoError`] with additional context.
253    ///
254    /// Use this constructor when the error kind, span, and path are not sufficient to fully
255    /// explain the error, and additional context can provide meaningful details.
256    /// Avoid redundant context (e.g., "Permission denied" for an error kind of
257    /// [`ErrorKind::PermissionDenied`](std::io::ErrorKind::PermissionDenied)).
258    ///
259    /// # Constraints
260    /// If `span` is unknown, use:
261    /// - `new_internal` if no path is available.
262    /// - `new_internal_with_path` if a path is available.
263    pub fn new_with_additional_context(
264        kind: impl Into<ErrorKind>,
265        span: Span,
266        path: impl Into<Option<PathBuf>>,
267        additional_context: impl ToString,
268    ) -> Self {
269        let path = path.into();
270
271        if span == Span::unknown() {
272            debug_assert!(
273                path.is_some(),
274                "for unknown spans with paths, use `new_internal_with_path`"
275            );
276            debug_assert!(
277                path.is_none(),
278                "for unknown spans without paths, use `new_internal`"
279            );
280        }
281
282        Self {
283            kind: kind.into(),
284            span,
285            path,
286            additional_context: Some(additional_context.to_string().into()),
287            location: None,
288        }
289    }
290
291    /// Creates a new [`IoError`] for internal I/O errors without a user-provided span or path.
292    ///
293    /// This constructor is intended for internal errors in the Rust implementation that still need
294    /// to be reported to the end user.
295    /// Since these errors are not tied to user-provided Nushell code, they generally have no
296    /// meaningful span or path.
297    ///
298    /// Instead, these errors provide:
299    /// - `additional_context`:
300    ///   Details about what went wrong internally.
301    /// - `location`:
302    ///   The location in the Rust code where the error occurred, allowing us to trace and debug
303    ///   the issue.
304    ///   Use the [`nu_protocol::location!`](crate::location) macro to generate the location
305    ///   information.
306    ///
307    /// # Examples
308    /// ```rust
309    /// use nu_protocol::shell_error::{self, io::IoError};
310    ///
311    /// let error = IoError::new_internal(
312    ///     shell_error::io::ErrorKind::from_std(std::io::ErrorKind::UnexpectedEof),
313    ///     "Failed to read from buffer",
314    ///     nu_protocol::location!(),
315    /// );
316    /// ```
317    pub fn new_internal(
318        kind: impl Into<ErrorKind>,
319        additional_context: impl ToString,
320        location: Location,
321    ) -> Self {
322        Self {
323            kind: kind.into(),
324            span: Span::unknown(),
325            path: None,
326            additional_context: Some(additional_context.to_string().into()),
327            location: Some(location.to_string()),
328        }
329    }
330
331    /// Creates a new `IoError` for internal I/O errors with a specific path.
332    ///
333    /// This constructor is similar to [`new_internal`](Self::new_internal) but also includes a
334    /// file or directory path relevant to the error.
335    /// Use this function in rare cases where an internal error involves a specific path, and the
336    /// combination of path and additional context is helpful.
337    ///
338    /// # Examples
339    /// ```rust
340    /// use nu_protocol::shell_error::{self, io::IoError};
341    /// use std::path::PathBuf;
342    ///
343    /// let error = IoError::new_internal_with_path(
344    ///     shell_error::io::ErrorKind::FileNotFound,
345    ///     "Could not find special file",
346    ///     nu_protocol::location!(),
347    ///     PathBuf::from("/some/file"),
348    /// );
349    /// ```
350    pub fn new_internal_with_path(
351        kind: impl Into<ErrorKind>,
352        additional_context: impl ToString,
353        location: Location,
354        path: PathBuf,
355    ) -> Self {
356        Self {
357            kind: kind.into(),
358            span: Span::unknown(),
359            path: path.into(),
360            additional_context: Some(additional_context.to_string().into()),
361            location: Some(location.to_string()),
362        }
363    }
364
365    /// Creates a factory closure for constructing [`IoError`] instances from [`std::io::Error`] values.
366    ///
367    /// This method is particularly useful when you need to handle multiple I/O errors which all
368    /// take the same span and path.
369    /// Instead of calling `.map_err(|err| IoError::new(err, span, path))` every time, you
370    /// can create the factory closure once and pass that into `.map_err`.
371    pub fn factory<'p, P>(span: Span, path: P) -> impl Fn(std::io::Error) -> Self + use<'p, P>
372    where
373        P: Into<Option<&'p Path>>,
374    {
375        let path = path.into();
376        move |err: std::io::Error| IoError::new(err, span, path.map(PathBuf::from))
377    }
378}
379
380impl From<std::io::Error> for ErrorKind {
381    fn from(err: std::io::Error) -> Self {
382        (&err).into()
383    }
384}
385
386impl From<&std::io::Error> for ErrorKind {
387    fn from(err: &std::io::Error) -> Self {
388        #[cfg(windows)]
389        if let Some(raw_os_error) = err.raw_os_error() {
390            use windows::Win32::Foundation;
391
392            #[allow(clippy::single_match, reason = "in the future we can expand here")]
393            match Foundation::WIN32_ERROR(raw_os_error as u32) {
394                Foundation::ERROR_SHARING_VIOLATION => return ErrorKind::AlreadyInUse,
395                _ => {}
396            }
397        }
398
399        #[cfg(debug_assertions)]
400        if err.kind() == std::io::ErrorKind::Other {
401            panic!(
402                "\
403suspicious conversion:
404    tried to convert `std::io::Error` with `std::io::ErrorKind::Other`
405    into `nu_protocol::shell_error::io::ErrorKind`
406
407I/O errors should always be specific, provide more context
408
409{err:#?}\
410            "
411            )
412        }
413
414        ErrorKind::Std(err.kind(), Sealed)
415    }
416}
417
418impl From<nu_system::KillByPidError> for ErrorKind {
419    fn from(value: nu_system::KillByPidError) -> Self {
420        match value {
421            nu_system::KillByPidError::Output(error) => error.into(),
422            nu_system::KillByPidError::KillProcess => ErrorKind::KillJobProcess,
423        }
424    }
425}
426
427impl StdError for IoError {}
428impl Display for IoError {
429    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
430        match self.kind {
431            ErrorKind::Std(std::io::ErrorKind::NotFound, _) => write!(f, "Not found"),
432            ErrorKind::FileNotFound => write!(f, "File not found"),
433            ErrorKind::DirectoryNotFound => write!(f, "Directory not found"),
434            _ => write!(f, "I/O error"),
435        }
436    }
437}
438
439impl Display for ErrorKind {
440    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
441        match self {
442            ErrorKind::Std(std::io::ErrorKind::NotFound, _) => write!(f, "Not found"),
443            ErrorKind::Std(error_kind, _) => {
444                let msg = error_kind.to_string();
445                let (first, rest) = msg.split_at(1);
446                write!(f, "{}{}", first.to_uppercase(), rest)
447            }
448            ErrorKind::KillJobProcess => write!(f, "Killing job process failed"),
449            ErrorKind::NotAFile => write!(f, "Not a file"),
450            ErrorKind::AlreadyInUse => write!(f, "Already in use"),
451            ErrorKind::FileNotFound => write!(f, "File not found"),
452            ErrorKind::DirectoryNotFound => write!(f, "Directory not found"),
453        }
454    }
455}
456
457impl std::error::Error for ErrorKind {}
458
459impl Diagnostic for IoError {
460    fn code<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
461        let mut code = String::from("nu::shell::io::");
462        match self.kind {
463            ErrorKind::Std(error_kind, _) => match error_kind {
464                std::io::ErrorKind::NotFound => code.push_str("not_found"),
465                std::io::ErrorKind::PermissionDenied => code.push_str("permission_denied"),
466                std::io::ErrorKind::ConnectionRefused => code.push_str("connection_refused"),
467                std::io::ErrorKind::ConnectionReset => code.push_str("connection_reset"),
468                std::io::ErrorKind::ConnectionAborted => code.push_str("connection_aborted"),
469                std::io::ErrorKind::NotConnected => code.push_str("not_connected"),
470                std::io::ErrorKind::AddrInUse => code.push_str("addr_in_use"),
471                std::io::ErrorKind::AddrNotAvailable => code.push_str("addr_not_available"),
472                std::io::ErrorKind::BrokenPipe => code.push_str("broken_pipe"),
473                std::io::ErrorKind::AlreadyExists => code.push_str("already_exists"),
474                std::io::ErrorKind::WouldBlock => code.push_str("would_block"),
475                std::io::ErrorKind::InvalidInput => code.push_str("invalid_input"),
476                std::io::ErrorKind::InvalidData => code.push_str("invalid_data"),
477                std::io::ErrorKind::TimedOut => code.push_str("timed_out"),
478                std::io::ErrorKind::WriteZero => code.push_str("write_zero"),
479                std::io::ErrorKind::Interrupted => code.push_str("interrupted"),
480                std::io::ErrorKind::Unsupported => code.push_str("unsupported"),
481                std::io::ErrorKind::UnexpectedEof => code.push_str("unexpected_eof"),
482                std::io::ErrorKind::OutOfMemory => code.push_str("out_of_memory"),
483                std::io::ErrorKind::Other => code.push_str("other"),
484                kind => code.push_str(&kind.to_string().to_lowercase().replace(" ", "_")),
485            },
486            ErrorKind::KillJobProcess => code.push_str("kill_job_process"),
487            ErrorKind::NotAFile => code.push_str("not_a_file"),
488            ErrorKind::AlreadyInUse => code.push_str("already_in_use"),
489            ErrorKind::FileNotFound => code.push_str("file_not_found"),
490            ErrorKind::DirectoryNotFound => code.push_str("directory_not_found"),
491        }
492
493        Some(Box::new(code))
494    }
495
496    fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
497        let make_msg = |path: &Path| {
498            let path = format!("'{}'", path.display());
499            match self.kind {
500                ErrorKind::NotAFile => format!("{path} is not a file"),
501                ErrorKind::AlreadyInUse => {
502                    format!("{path} is already being used by another program")
503                }
504                ErrorKind::Std(std::io::ErrorKind::NotFound, _)
505                | ErrorKind::FileNotFound
506                | ErrorKind::DirectoryNotFound => format!("{path} does not exist"),
507                _ => format!("The error occurred at {path}"),
508            }
509        };
510
511        self.path
512            .as_ref()
513            .map(|path| make_msg(path))
514            .map(|s| Box::new(s) as Box<dyn std::fmt::Display>)
515    }
516
517    fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
518        let span_is_unknown = self.span == Span::unknown();
519        let span = match (span_is_unknown, self.location.as_ref()) {
520            (true, None) => return None,
521            (false, _) => SourceSpan::from(self.span),
522            (true, Some(location)) => SourceSpan::new(0.into(), location.len()),
523        };
524
525        let label = LabeledSpan::new_with_span(Some(self.kind.to_string()), span);
526        Some(Box::new(std::iter::once(label)))
527    }
528
529    fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
530        self.additional_context
531            .as_ref()
532            .map(|ctx| ctx as &dyn Diagnostic)
533    }
534
535    fn source_code(&self) -> Option<&dyn miette::SourceCode> {
536        let span_is_unknown = self.span == Span::unknown();
537        match (span_is_unknown, self.location.as_ref()) {
538            (true, None) | (false, _) => None,
539            (true, Some(location)) => Some(location as &dyn miette::SourceCode),
540        }
541    }
542}
543
544impl From<IoError> for std::io::Error {
545    fn from(value: IoError) -> Self {
546        Self::new(value.kind.into(), value)
547    }
548}
549
550impl From<ErrorKind> for std::io::ErrorKind {
551    fn from(value: ErrorKind) -> Self {
552        match value {
553            ErrorKind::Std(error_kind, _) => error_kind,
554            _ => std::io::ErrorKind::Other,
555        }
556    }
557}
558
559/// More specific variants of [`NotFound`](std::io::ErrorKind).
560///
561/// Use these to define how a `NotFound` error maps to our custom [`ErrorKind`].
562pub enum NotFound {
563    /// Map into [`FileNotFound`](ErrorKind::FileNotFound).
564    File,
565    /// Map into [`DirectoryNotFound`](ErrorKind::DirectoryNotFound).
566    Directory,
567}
568
569/// Extension trait for working with [`std::io::Error`].
570pub trait IoErrorExt {
571    /// Map [`NotFound`](std::io::ErrorKind) variants into more precise variants.
572    ///
573    /// The OS doesn't know when an entity was not found whether it was meant to be a file or a
574    /// directory or something else.
575    /// But sometimes we, the application, know what we expect and with this method, we can further
576    /// specify it.
577    ///
578    /// # Examples
579    /// Reading a file.
580    /// If the file isn't found, return [`FileNotFound`](ErrorKind::FileNotFound).
581    /// ```rust
582    /// # use nu_protocol::{
583    /// #     shell_error::io::{ErrorKind, IoErrorExt, IoError, NotFound},
584    /// #     ShellError, Span,
585    /// # };
586    /// # use std::{fs, path::PathBuf};
587    /// #
588    /// # fn example() -> Result<(), ShellError> {
589    /// #     let span = Span::test_data();
590    /// let a_file = PathBuf::from("scripts/ellie.nu");
591    /// let ellie = fs::read_to_string(&a_file).map_err(|err| {
592    ///     ShellError::Io(IoError::new(
593    ///         err.not_found_as(NotFound::File),
594    ///         span,
595    ///         a_file,
596    ///     ))
597    /// })?;
598    /// #     Ok(())
599    /// # }
600    /// #
601    /// # assert!(matches!(
602    /// #     example(),
603    /// #     Err(ShellError::Io(IoError {
604    /// #         kind: ErrorKind::FileNotFound,
605    /// #         ..
606    /// #     }))
607    /// # ));
608    /// ```
609    fn not_found_as(self, kind: NotFound) -> ErrorKind;
610}
611
612impl IoErrorExt for ErrorKind {
613    fn not_found_as(self, kind: NotFound) -> ErrorKind {
614        match (kind, self) {
615            (NotFound::File, Self::Std(std::io::ErrorKind::NotFound, _)) => ErrorKind::FileNotFound,
616            (NotFound::Directory, Self::Std(std::io::ErrorKind::NotFound, _)) => {
617                ErrorKind::DirectoryNotFound
618            }
619            _ => self,
620        }
621    }
622}
623
624impl IoErrorExt for std::io::Error {
625    fn not_found_as(self, kind: NotFound) -> ErrorKind {
626        ErrorKind::from(self).not_found_as(kind)
627    }
628}
629
630impl IoErrorExt for &std::io::Error {
631    fn not_found_as(self, kind: NotFound) -> ErrorKind {
632        ErrorKind::from(self).not_found_as(kind)
633    }
634}
635
636#[cfg(test)]
637mod assert_not_impl {
638    use super::*;
639
640    /// Assertion that `ErrorKind` does not implement `From<std::io::ErrorKind>`.
641    ///
642    /// This implementation exists only in tests to make sure that no crate,
643    /// including ours, accidentally adds a `From<std::io::ErrorKind>` impl for `ErrorKind`.
644    /// If someone tries, it will fail due to conflicting implementations.
645    ///
646    /// We want to force usage of [`IoError::new`] with a full [`std::io::Error`] instead of
647    /// allowing conversion from just an [`std::io::ErrorKind`].
648    /// That way, we can properly inspect and classify uncategorized I/O errors.
649    impl From<std::io::ErrorKind> for ErrorKind {
650        fn from(_: std::io::ErrorKind) -> Self {
651            unimplemented!("ErrorKind should not implement From<std::io::ErrorKind>")
652        }
653    }
654}