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}