A custom OS for the xteink x4 ebook reader
1// unified error type for plump
2//
3// single Copy type carrying ErrorKind (what) and a &'static str
4// source tag (where); smol-epub boundary converts via From impls
5
6use core::fmt;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9#[non_exhaustive]
10pub enum ErrorKind {
11 // storage / sd card
12 NoCard,
13 OpenVolume,
14 OpenDir,
15 OpenFile,
16 ReadFailed,
17 WriteFailed,
18 SeekFailed,
19 DeleteFailed,
20 DirFull,
21 NotFound,
22
23 // data / parsing
24 ParseFailed,
25 InvalidData,
26 BadEncoding,
27
28 // resources
29 OutOfMemory,
30 BufferTooSmall,
31
32 // network (upload)
33 NetworkIo,
34 Protocol,
35
36 // catch-all
37 Other,
38}
39
40impl ErrorKind {
41 pub const fn as_str(self) -> &'static str {
42 match self {
43 Self::NoCard => "no sd card",
44 Self::OpenVolume => "open volume failed",
45 Self::OpenDir => "open dir failed",
46 Self::OpenFile => "open file failed",
47 Self::ReadFailed => "read failed",
48 Self::WriteFailed => "write failed",
49 Self::SeekFailed => "seek failed",
50 Self::DeleteFailed => "delete failed",
51 Self::DirFull => "directory full",
52 Self::NotFound => "not found",
53 Self::ParseFailed => "parse failed",
54 Self::InvalidData => "invalid data",
55 Self::BadEncoding => "bad encoding",
56 Self::OutOfMemory => "out of memory",
57 Self::BufferTooSmall => "buffer too small",
58 Self::NetworkIo => "network error",
59 Self::Protocol => "protocol error",
60 Self::Other => "error",
61 }
62 }
63
64 pub const fn is_storage(self) -> bool {
65 matches!(
66 self,
67 Self::NoCard
68 | Self::OpenVolume
69 | Self::OpenDir
70 | Self::OpenFile
71 | Self::ReadFailed
72 | Self::WriteFailed
73 | Self::SeekFailed
74 | Self::DeleteFailed
75 | Self::DirFull
76 | Self::NotFound
77 )
78 }
79}
80
81impl fmt::Display for ErrorKind {
82 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83 f.write_str(self.as_str())
84 }
85}
86
87// one discriminant byte + one &'static str pointer; cheap to copy
88// source is module_path!() or a short caller-supplied tag
89#[derive(Clone, Copy)]
90pub struct Error {
91 kind: ErrorKind,
92 source: &'static str,
93}
94
95impl Error {
96 #[inline]
97 pub const fn new(kind: ErrorKind, source: &'static str) -> Self {
98 Self { kind, source }
99 }
100
101 #[inline]
102 pub const fn from_kind(kind: ErrorKind) -> Self {
103 Self { kind, source: "" }
104 }
105
106 pub const NO_CARD: Self = Self::from_kind(ErrorKind::NoCard);
107 pub const OPEN_VOLUME: Self = Self::from_kind(ErrorKind::OpenVolume);
108 pub const OPEN_DIR: Self = Self::from_kind(ErrorKind::OpenDir);
109 pub const OPEN_FILE: Self = Self::from_kind(ErrorKind::OpenFile);
110 pub const READ_FAILED: Self = Self::from_kind(ErrorKind::ReadFailed);
111 pub const WRITE_FAILED: Self = Self::from_kind(ErrorKind::WriteFailed);
112 pub const SEEK_FAILED: Self = Self::from_kind(ErrorKind::SeekFailed);
113 pub const DELETE_FAILED: Self = Self::from_kind(ErrorKind::DeleteFailed);
114 pub const DIR_FULL: Self = Self::from_kind(ErrorKind::DirFull);
115 pub const NOT_FOUND: Self = Self::from_kind(ErrorKind::NotFound);
116}
117
118impl Error {
119 #[inline]
120 pub const fn kind(&self) -> ErrorKind {
121 self.kind
122 }
123
124 #[inline]
125 pub const fn source_tag(&self) -> &'static str {
126 self.source
127 }
128
129 #[inline]
130 pub const fn with_source(self, source: &'static str) -> Self {
131 Self {
132 kind: self.kind,
133 source,
134 }
135 }
136
137 #[inline]
138 pub const fn with_kind(self, kind: ErrorKind) -> Self {
139 Self {
140 kind,
141 source: self.source,
142 }
143 }
144
145 #[inline]
146 pub const fn has_source(&self) -> bool {
147 !self.source.is_empty()
148 }
149
150 #[inline]
151 pub const fn is_storage(&self) -> bool {
152 self.kind.is_storage()
153 }
154
155 #[inline]
156 pub const fn as_str(&self) -> &'static str {
157 self.kind.as_str()
158 }
159}
160
161impl fmt::Debug for Error {
162 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
163 if self.source.is_empty() {
164 write!(f, "Error({:?})", self.kind)
165 } else {
166 write!(f, "Error({:?} @ {:?})", self.kind, self.source)
167 }
168 }
169}
170
171impl fmt::Display for Error {
172 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
173 if self.source.is_empty() {
174 f.write_str(self.kind.as_str())
175 } else {
176 write!(f, "{} [{}]", self.kind.as_str(), self.source)
177 }
178 }
179}
180
181// equality is semantic: kind only, source is diagnostic
182impl PartialEq for Error {
183 #[inline]
184 fn eq(&self, other: &Self) -> bool {
185 self.kind == other.kind
186 }
187}
188
189impl Eq for Error {}
190
191// wrap &'static str (smol-epub returns) into Error; well-known
192// strings map to the appropriate kind, rest becomes Other
193impl From<&'static str> for Error {
194 #[inline]
195 fn from(msg: &'static str) -> Self {
196 let kind = match msg {
197 "read failed" | "read local header failed" => ErrorKind::ReadFailed,
198 "write failed" => ErrorKind::WriteFailed,
199 "read error" | "read error during upload" => ErrorKind::NetworkIo,
200 "no sd card" => ErrorKind::NoCard,
201 "not found" | "OPF not found" | "no filename in upload" => ErrorKind::NotFound,
202 "too small" | "CD truncated" | "cache file too small" => ErrorKind::InvalidData,
203 "CD too large" | "OOM for cached image" => ErrorKind::OutOfMemory,
204 "bad OPF path" | "bad encoding" | "filename encoding error" => ErrorKind::BadEncoding,
205 "parse failed" | "no title in OPF" => ErrorKind::ParseFailed,
206 "boundary too long"
207 | "part headers too large"
208 | "invalid filename"
209 | "upload incomplete"
210 | "connection closed during headers" => ErrorKind::Protocol,
211 _ => ErrorKind::Other,
212 };
213 Self { kind, source: msg }
214 }
215}
216
217// project back to &'static str for the smol-epub trait boundary
218impl From<Error> for &'static str {
219 #[inline]
220 fn from(e: Error) -> &'static str {
221 if e.source.is_empty() {
222 e.kind.as_str()
223 } else {
224 e.source
225 }
226 }
227}
228
229// ergonomic source tagging on Result<T, Error> and
230// Result<T, &'static str> (smol-epub returns)
231pub trait ResultExt<T> {
232 fn source(self, src: &'static str) -> Result<T>;
233 fn map_kind(self, kind: ErrorKind, src: &'static str) -> Result<T>;
234}
235
236impl<T> ResultExt<T> for Result<T> {
237 #[inline]
238 fn source(self, src: &'static str) -> Result<T> {
239 self.map_err(|e| e.with_source(src))
240 }
241
242 #[inline]
243 fn map_kind(self, kind: ErrorKind, src: &'static str) -> Result<T> {
244 self.map_err(|_| Error::new(kind, src))
245 }
246}
247
248impl<T> ResultExt<T> for core::result::Result<T, &'static str> {
249 #[inline]
250 fn source(self, src: &'static str) -> Result<T> {
251 self.map_err(|msg| Error::from(msg).with_source(src))
252 }
253
254 #[inline]
255 fn map_kind(self, kind: ErrorKind, src: &'static str) -> Result<T> {
256 self.map_err(|_| Error::new(kind, src))
257 }
258}
259
260// create an Error with module_path!() as source
261// err!(ReadFailed)
262// err!(OpenFile, "epub_init_zip")
263#[macro_export]
264macro_rules! err {
265 ($kind:ident) => {
266 $crate::error::Error::new($crate::error::ErrorKind::$kind, module_path!())
267 };
268 ($kind:ident, $src:expr) => {
269 $crate::error::Error::new($crate::error::ErrorKind::$kind, $src)
270 };
271}
272
273// map any Result<T, _> into an Error of the given kind
274// or_err!(mgr.read(file, buf).await, ReadFailed)
275#[macro_export]
276macro_rules! or_err {
277 ($result:expr, $kind:ident) => {
278 ($result)
279 .map_err(|_| $crate::error::Error::new($crate::error::ErrorKind::$kind, module_path!()))
280 };
281 ($result:expr, $kind:ident, $src:expr) => {
282 ($result).map_err(|_| $crate::error::Error::new($crate::error::ErrorKind::$kind, $src))
283 };
284}
285
286pub type Result<T> = core::result::Result<T, Error>;