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