A custom OS for the xteink x4 ebook reader
at main 286 lines 8.2 kB view raw
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>;