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}