Skip to main content

hadris_io/
error.rs

1//! Portable, allocation-free I/O errors.
2
3use core::fmt::{self, Display};
4
5/// Portable error classification. This is a superset of `embedded_io::ErrorKind`
6/// and retains the `std::io` conditions Hadris needs for helper operations.
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8#[non_exhaustive]
9pub enum ErrorKind {
10    /// An entity was not found.
11    NotFound,
12    /// An operation lacked sufficient permissions.
13    PermissionDenied,
14    /// A connection attempt was refused.
15    ConnectionRefused,
16    /// A connection was reset by its peer.
17    ConnectionReset,
18    /// A connection was aborted locally.
19    ConnectionAborted,
20    /// The endpoint is not connected.
21    NotConnected,
22    /// The requested address is already in use.
23    AddrInUse,
24    /// The requested address is unavailable.
25    AddrNotAvailable,
26    /// A write targeted a closed pipe or connection.
27    BrokenPipe,
28    /// An entity already exists.
29    AlreadyExists,
30    /// The operation would block.
31    WouldBlock,
32    /// An input parameter was invalid.
33    InvalidInput,
34    /// Input data was malformed.
35    InvalidData,
36    /// The operation timed out.
37    TimedOut,
38    /// A write produced no progress.
39    WriteZero,
40    /// The operation was interrupted and may be retried.
41    Interrupted,
42    /// Input ended before the requested data was read.
43    UnexpectedEof,
44    /// The requested operation is unsupported.
45    Unsupported,
46    /// The operation could not allocate required memory.
47    OutOfMemory,
48    /// An error without a more specific portable classification.
49    Other,
50}
51
52impl ErrorKind {
53    /// Return this already-normalized error kind.
54    pub const fn kind(&self) -> Self {
55        *self
56    }
57}
58
59impl From<embedded_io::ErrorKind> for ErrorKind {
60    fn from(kind: embedded_io::ErrorKind) -> Self {
61        use embedded_io::ErrorKind as E;
62        match kind {
63            E::NotFound => Self::NotFound,
64            E::PermissionDenied => Self::PermissionDenied,
65            E::ConnectionRefused => Self::ConnectionRefused,
66            E::ConnectionReset => Self::ConnectionReset,
67            E::ConnectionAborted => Self::ConnectionAborted,
68            E::NotConnected => Self::NotConnected,
69            E::AddrInUse => Self::AddrInUse,
70            E::AddrNotAvailable => Self::AddrNotAvailable,
71            E::BrokenPipe => Self::BrokenPipe,
72            E::AlreadyExists => Self::AlreadyExists,
73            E::InvalidInput => Self::InvalidInput,
74            E::InvalidData => Self::InvalidData,
75            E::TimedOut => Self::TimedOut,
76            E::WriteZero => Self::WriteZero,
77            E::Interrupted => Self::Interrupted,
78            E::Unsupported => Self::Unsupported,
79            E::OutOfMemory => Self::OutOfMemory,
80            _ => Self::Other,
81        }
82    }
83}
84
85impl From<ErrorKind> for embedded_io::ErrorKind {
86    fn from(kind: ErrorKind) -> Self {
87        use embedded_io::ErrorKind as E;
88        match kind {
89            ErrorKind::NotFound => E::NotFound,
90            ErrorKind::PermissionDenied => E::PermissionDenied,
91            ErrorKind::ConnectionRefused => E::ConnectionRefused,
92            ErrorKind::ConnectionReset => E::ConnectionReset,
93            ErrorKind::ConnectionAborted => E::ConnectionAborted,
94            ErrorKind::NotConnected => E::NotConnected,
95            ErrorKind::AddrInUse => E::AddrInUse,
96            ErrorKind::AddrNotAvailable => E::AddrNotAvailable,
97            ErrorKind::BrokenPipe => E::BrokenPipe,
98            ErrorKind::AlreadyExists => E::AlreadyExists,
99            ErrorKind::InvalidInput => E::InvalidInput,
100            ErrorKind::InvalidData => E::InvalidData,
101            ErrorKind::TimedOut => E::TimedOut,
102            ErrorKind::WriteZero => E::WriteZero,
103            ErrorKind::Interrupted => E::Interrupted,
104            ErrorKind::Unsupported => E::Unsupported,
105            ErrorKind::OutOfMemory => E::OutOfMemory,
106            ErrorKind::WouldBlock | ErrorKind::UnexpectedEof | ErrorKind::Other => E::Other,
107        }
108    }
109}
110
111impl Display for ErrorKind {
112    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
113        write!(f, "{self:?}")
114    }
115}
116
117impl core::error::Error for ErrorKind {}
118
119impl embedded_io::Error for ErrorKind {
120    fn kind(&self) -> embedded_io::ErrorKind {
121        (*self).into()
122    }
123}
124
125/// An error produced either by an underlying I/O object or by a Hadris helper.
126#[derive(Debug, Clone, PartialEq, Eq)]
127#[non_exhaustive]
128pub enum Error<E = ErrorKind> {
129    /// Error returned by the underlying reader, writer, or seeker.
130    Source(E),
131    /// Error synthesized by Hadris, with optional allocation-free context.
132    Context {
133        /// Portable classification of the error.
134        kind: ErrorKind,
135        /// Static diagnostic context.
136        message: Option<&'static str>,
137    },
138}
139
140impl<E> Error<E> {
141    /// Wrap an error returned by the underlying I/O object.
142    pub const fn from_source(source: E) -> Self {
143        Self::Source(source)
144    }
145
146    /// Construct a Hadris-generated error without additional context.
147    pub const fn from_kind(kind: ErrorKind) -> Self {
148        Self::Context {
149            kind,
150            message: None,
151        }
152    }
153
154    /// Construct a Hadris-generated error with static context.
155    pub const fn new(kind: ErrorKind, message: &'static str) -> Self {
156        Self::Context {
157            kind,
158            message: Some(message),
159        }
160    }
161
162    /// Construct an `Other` error with static context.
163    pub const fn other(message: &'static str) -> Self {
164        Self::new(ErrorKind::Other, message)
165    }
166
167    /// Borrow the underlying source, if present.
168    pub const fn source_ref(&self) -> Option<&E> {
169        match self {
170            Self::Source(source) => Some(source),
171            Self::Context { .. } => None,
172        }
173    }
174
175    /// Consume the error and return its underlying source, if present.
176    pub fn into_source(self) -> Option<E> {
177        match self {
178            Self::Source(source) => Some(source),
179            Self::Context { .. } => None,
180        }
181    }
182}
183
184impl<E: embedded_io::Error> Error<E> {
185    /// Return the portable error kind.
186    pub fn kind(&self) -> ErrorKind {
187        match self {
188            Self::Source(source) => source.kind().into(),
189            Self::Context { kind, .. } => *kind,
190        }
191    }
192
193    /// Erase the concrete source while retaining its normalized kind.
194    pub fn erase(self) -> Error<ErrorKind> {
195        match self {
196            Self::Source(source) => Error::Source(source.kind().into()),
197            Self::Context { kind, message } => Error::Context { kind, message },
198        }
199    }
200}
201
202impl<E: embedded_io::Error> embedded_io::Error for Error<E> {
203    fn kind(&self) -> embedded_io::ErrorKind {
204        Error::<E>::kind(self).into()
205    }
206}
207
208impl<E: Display> Display for Error<E> {
209    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
210        match self {
211            Self::Source(source) => Display::fmt(source, f),
212            Self::Context {
213                kind,
214                message: Some(message),
215            } => {
216                write!(f, "{kind:?}: {message}")
217            }
218            Self::Context {
219                kind,
220                message: None,
221            } => write!(f, "{kind:?}"),
222        }
223    }
224}
225
226impl<E: core::error::Error> core::error::Error for Error<E> {}
227
228#[cfg(feature = "std")]
229impl From<std::io::Error> for Error<std::io::Error> {
230    fn from(error: std::io::Error) -> Self {
231        Self::Source(error)
232    }
233}
234
235#[cfg(feature = "std")]
236impl From<Error<std::io::Error>> for std::io::Error {
237    fn from(error: Error<std::io::Error>) -> Self {
238        match error {
239            Error::Source(source) => source,
240            Error::Context { kind, message } => match message {
241                Some(message) => std::io::Error::new(std::io::ErrorKind::from(kind), message),
242                None => std::io::Error::from(std::io::ErrorKind::from(kind)),
243            },
244        }
245    }
246}
247
248/// Result returned by Hadris helpers and filesystem operations.
249pub type Result<T, E = ErrorKind> = core::result::Result<T, Error<E>>;
250
251#[cfg(feature = "std")]
252impl From<ErrorKind> for std::io::ErrorKind {
253    fn from(kind: ErrorKind) -> Self {
254        let embedded = embedded_io::ErrorKind::from(kind);
255        embedded.into()
256    }
257}