mycelium_util/io/
error.rs

1use crate::error;
2use core::fmt;
3/// A specialized [`Result`] type for I/O operations.
4///
5/// This type is broadly used across [`mycelium_util::io`] for any operation which may
6/// produce an error.
7///
8/// This typedef is generally used to avoid writing out [`io::Error`] directly and
9/// is otherwise a direct mapping to [`Result`].
10///
11/// While usual Rust style is to import types directly, aliases of [`Result`]
12/// often are not, to make it easier to distinguish between them. [`Result`] is
13/// generally assumed to be [`core::result::Result`][`Result`], and so users of this alias
14/// will generally use `io::Result` instead of shadowing the [prelude]'s import
15/// of [`core::result::Result`][`Result`].
16///
17/// [`mycelium_util::io`]: crate::io
18/// [`io::Error`]: Error
19/// [`Result`]: core::result::Result
20/// [prelude]: core::prelude
21pub type Result<T> = core::result::Result<T, self::Error>;
22
23/// The error type for I/O operations of the [`Read`], [`Write`], [`Seek`], and
24/// associated traits.
25///
26/// Errors mostly originate from the underlying OS, but custom instances of
27/// `Error` can be created with crafted error messages and a particular value of
28/// [`ErrorKind`].
29///
30/// [`Read`]: crate::io::Read
31/// [`Write`]: crate::io::Write
32/// [`Seek`]: crate::io::Seek
33#[derive(Debug)]
34pub struct Error<E: error::Error + 'static = &'static str> {
35    kind: ErrorKind,
36    source: Option<E>,
37}
38
39/// A list specifying general categories of I/O error.
40///
41/// This list is intended to grow over time and it is not recommended to
42/// exhaustively match against it.
43///
44/// It is used with the [`io::Error`] type.
45///
46/// [`io::Error`]: Error
47///
48/// # Handling errors and matching on `ErrorKind`
49///
50/// In application code, use `match` for the `ErrorKind` values you are
51/// expecting; use `_` to match "all other errors".
52///
53/// In comprehensive and thorough tests that want to verify that a test doesn't
54/// return any known incorrect error kind, you may want to cut-and-paste the
55/// current full list of errors from here into your test code, and then match
56/// `_` as the correct case. This seems counterintuitive, but it will make your
57/// tests more robust. In particular, if you want to verify that your code does
58/// produce an unrecognized error kind, the robust solution is to check for all
59/// the recognized error kinds and fail in those cases.
60#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
61#[non_exhaustive]
62pub enum ErrorKind {
63    /// An entity was not found, often a file.
64    NotFound,
65    /// The operation lacked the necessary privileges to complete.
66    PermissionDenied,
67    /// The connection was refused by the remote server.
68    ConnectionRefused,
69    /// The connection was reset by the remote server.
70    ConnectionReset,
71    /// The connection was aborted (terminated) by the remote server.
72    ConnectionAborted,
73    /// The network operation failed because it was not connected yet.
74    NotConnected,
75    /// A socket address could not be bound because the address is already in
76    /// use elsewhere.
77    AddrInUse,
78    /// A nonexistent interface was requested or the requested address was not
79    /// local.
80    AddrNotAvailable,
81    /// The operation failed because a pipe was closed.
82    BrokenPipe,
83    /// An entity already exists, often a file.
84    AlreadyExists,
85    /// The operation needs to block to complete, but the blocking operation was
86    /// requested to not occur.
87    WouldBlock,
88    /// A parameter was incorrect.
89    InvalidInput,
90    /// Data not valid for the operation were encountered.
91    ///
92    /// Unlike [`InvalidInput`], this typically means that the operation
93    /// parameters were valid, however the error was caused by malformed
94    /// input data.
95    ///
96    /// For example, a function that reads a file into a string will error with
97    /// `InvalidData` if the file's contents are not valid UTF-8.
98    ///
99    /// [`InvalidInput`]: #variant.InvalidInput
100    InvalidData,
101    /// The I/O operation's timeout expired, causing it to be canceled.
102    TimedOut,
103    /// An error returned when an operation could not be completed because a
104    /// call to [`write`] returned [`Ok(0)`].
105    ///
106    /// This typically means that an operation could only succeed if it wrote a
107    /// particular number of bytes but only a smaller number of bytes could be
108    /// written.
109    ///
110    /// [`write`]: ../../std/io/trait.Write.html#tymethod.write
111    /// [`Ok(0)`]: ../../std/io/type.Result.html
112    WriteZero,
113    /// This operation was interrupted.
114    ///
115    /// Interrupted operations can typically be retried.
116    Interrupted,
117    /// Any I/O error not part of this list.
118    Other,
119
120    /// An error returned when an operation could not be completed because an
121    /// "end of file" was reached prematurely.
122    ///
123    /// This typically means that an operation could only succeed if it read a
124    /// particular number of bytes but only a smaller number of bytes could be
125    /// read.
126    UnexpectedEof,
127}
128
129impl<E: error::Error + 'static> Error<E> {
130    /// Returns a new I/O error with the provided [`ErrorKind`] and `source`
131    /// error.
132    #[must_use]
133    #[inline]
134    pub fn new(kind: ErrorKind, source: E) -> Self {
135        Self {
136            kind,
137            source: Some(source),
138        }
139    }
140
141    /// Returns the [`ErrorKind`] of this error.
142    #[must_use]
143    #[inline]
144    pub fn kind(&self) -> ErrorKind {
145        self.kind
146    }
147}
148
149impl From<ErrorKind> for Error {
150    fn from(kind: ErrorKind) -> Self {
151        Error { kind, source: None }
152    }
153}
154
155impl<E: error::Error + 'static> error::Error for Error<E> {
156    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
157        self.source
158            .as_ref()
159            .map(|e| e as &(dyn error::Error + 'static))
160    }
161}
162
163impl<E: error::Error + 'static> fmt::Display for Error<E> {
164    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
165        if let Some(ref source) = self.source {
166            write!(f, "{}: {}", self.kind.as_str(), source)
167        } else {
168            f.write_str(self.kind.as_str())
169        }
170    }
171}
172
173impl ErrorKind {
174    pub(crate) fn as_str(self) -> &'static str {
175        match self {
176            ErrorKind::NotFound => "entity not found",
177            ErrorKind::PermissionDenied => "permission denied",
178            ErrorKind::ConnectionRefused => "connection refused",
179            ErrorKind::ConnectionReset => "connection reset",
180            ErrorKind::ConnectionAborted => "connection aborted",
181            ErrorKind::NotConnected => "not connected",
182            ErrorKind::AddrInUse => "address in use",
183            ErrorKind::AddrNotAvailable => "address not available",
184            ErrorKind::BrokenPipe => "broken pipe",
185            ErrorKind::AlreadyExists => "entity already exists",
186            ErrorKind::WouldBlock => "operation would block",
187            ErrorKind::InvalidInput => "invalid input parameter",
188            ErrorKind::InvalidData => "invalid data",
189            ErrorKind::TimedOut => "timed out",
190            ErrorKind::WriteZero => "write zero",
191            ErrorKind::Interrupted => "operation interrupted",
192            ErrorKind::Other => "other error",
193            ErrorKind::UnexpectedEof => "unexpected end of file",
194        }
195    }
196}