1use core::fmt;
21
22#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
29pub struct Position {
30 pub input: u64,
32 pub output: u64,
34}
35
36#[derive(Clone, Copy, Debug, PartialEq, Eq)]
38#[non_exhaustive]
39pub enum ErrorKind {
40 Corrupt(&'static str),
43 Truncated,
47 InvalidParameters,
50 #[non_exhaustive]
52 AllocationFailed {
53 bytes: u64,
55 },
56 #[non_exhaustive]
58 MemoryLimit {
59 required: u64,
61 limit: u64,
63 },
64 Internal(&'static str),
68}
69
70impl ErrorKind {
71 #[inline]
73 #[must_use]
74 pub const fn allocation_failed(bytes: u64) -> Self {
75 Self::AllocationFailed { bytes }
76 }
77
78 #[inline]
80 #[must_use]
81 pub const fn memory_limit(required: u64, limit: u64) -> Self {
82 Self::MemoryLimit { required, limit }
83 }
84}
85
86#[derive(Clone, Copy, Debug, PartialEq, Eq)]
93#[non_exhaustive]
94pub struct Error {
95 pub kind: ErrorKind,
97 pub at: Position,
99}
100
101impl Error {
102 #[inline]
104 #[must_use]
105 pub const fn new(kind: ErrorKind) -> Self {
106 Self {
107 kind,
108 at: Position {
109 input: 0,
110 output: 0,
111 },
112 }
113 }
114
115 #[inline]
117 pub const fn kind(&self) -> ErrorKind {
118 self.kind
119 }
120
121 #[inline]
123 pub const fn position(&self) -> Position {
124 self.at
125 }
126
127 #[inline]
132 pub const fn is_data_error(&self) -> bool {
133 matches!(self.kind, ErrorKind::Corrupt(_) | ErrorKind::Truncated)
134 }
135
136 #[inline]
141 pub const fn is_resource_error(&self) -> bool {
142 matches!(
143 self.kind,
144 ErrorKind::MemoryLimit { .. } | ErrorKind::AllocationFailed { .. }
145 )
146 }
147
148 #[cold]
149 #[inline(never)]
150 pub(crate) const fn corrupt(detail: &'static str) -> Self {
151 Self::new(ErrorKind::Corrupt(detail))
152 }
153
154 #[cold]
155 #[inline(never)]
156 pub(crate) const fn truncated() -> Self {
157 Self::new(ErrorKind::Truncated)
158 }
159
160 #[cold]
161 #[inline(never)]
162 pub(crate) const fn invalid_parameters() -> Self {
163 Self::new(ErrorKind::InvalidParameters)
164 }
165
166 #[cold]
167 #[inline(never)]
168 pub(crate) const fn internal(detail: &'static str) -> Self {
169 Self::new(ErrorKind::Internal(detail))
170 }
171
172 #[inline]
174 pub(crate) const fn at(self, at: Position) -> Self {
175 Self {
176 kind: self.kind,
177 at,
178 }
179 }
180}
181
182impl fmt::Display for Error {
186 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
187 match self.kind {
188 ErrorKind::Corrupt(detail) => write!(f, "corrupt PPMd stream: {detail}")?,
189 ErrorKind::Truncated => f.write_str("truncated PPMd stream")?,
190 ErrorKind::InvalidParameters => f.write_str("invalid PPMd parameters")?,
191 ErrorKind::AllocationFailed { bytes } => {
192 write!(f, "could not allocate {bytes} bytes for the PPMd model")?;
193 }
194 ErrorKind::MemoryLimit { required, limit } => write!(
195 f,
196 "the PPMd model needs {required} bytes, more than the {limit}-byte limit"
197 )?,
198 ErrorKind::Internal(detail) => write!(f, "internal PPMd codec failure: {detail}")?,
199 }
200 write!(
201 f,
202 " (at input byte {}, output byte {})",
203 self.at.input, self.at.output
204 )
205 }
206}
207
208impl core::error::Error for Error {}
209
210#[cfg(feature = "std")]
216impl From<Error> for std::io::Error {
217 fn from(e: Error) -> Self {
218 use std::io::ErrorKind as Io;
219 let kind = match e.kind {
220 ErrorKind::Corrupt(_) => Io::InvalidData,
221 ErrorKind::Truncated => Io::UnexpectedEof,
222 ErrorKind::InvalidParameters => Io::InvalidInput,
223 ErrorKind::AllocationFailed { .. } | ErrorKind::MemoryLimit { .. } => Io::OutOfMemory,
224 ErrorKind::Internal(_) => Io::Other,
225 };
226 std::io::Error::new(kind, e)
227 }
228}
229
230pub type Result<T> = core::result::Result<T, Error>;
232
233#[derive(Clone, Copy, Debug, PartialEq, Eq)]
240pub struct Progress<S> {
241 pub consumed: usize,
243 pub produced: usize,
245 pub status: S,
247}
248
249#[cfg(test)]
250mod tests {
251 extern crate alloc;
252 use super::*;
253 use alloc::string::{String, ToString};
254 use alloc::vec::Vec;
255
256 fn every_kind() -> [ErrorKind; 7] {
257 [
258 ErrorKind::Corrupt("model pointer or frequency out of bounds"),
259 ErrorKind::Corrupt("7z range coder: first byte is not zero"),
260 ErrorKind::Truncated,
261 ErrorKind::InvalidParameters,
262 ErrorKind::allocation_failed(1 << 30),
263 ErrorKind::memory_limit(1 << 30, 1 << 20),
264 ErrorKind::Internal("escape stop in a 7z stream"),
265 ]
266 }
267
268 fn chain(e: &(dyn core::error::Error + 'static)) -> Vec<String> {
269 let mut out = Vec::new();
270 let mut next = Some(e);
271 while let Some(e) = next {
272 out.push(e.to_string());
273 next = e.source();
274 }
275 out
276 }
277
278 #[test]
279 fn display_avoids_the_words_downstream_classifiers_match() {
280 for kind in every_kind() {
281 let text = Error::new(kind).to_string().to_ascii_lowercase();
282 for word in ["method", "unsupported", "password", "encrypted"] {
283 assert!(!text.contains(word), "{text}");
284 }
285 }
286 }
287
288 #[test]
289 fn display_is_lowercase_text_without_trailing_punctuation() {
290 for kind in every_kind() {
291 let text = Error::new(kind).to_string();
292 let first = text.chars().next().unwrap();
293 assert!(!first.is_uppercase(), "{text}");
294 assert!(!text.ends_with(['.', '!', '?']), "{text}");
295 assert_ne!(text, alloc::format!("{:?}", Error::new(kind)));
296 }
297 }
298
299 #[test]
300 fn an_error_is_its_own_whole_chain() {
301 let at = Position {
302 input: 7,
303 output: 9,
304 };
305 let cases = [
306 (
307 ErrorKind::Corrupt("bad symbol"),
308 "corrupt PPMd stream: bad symbol (at input byte 7, output byte 9)",
309 ),
310 (
311 ErrorKind::Truncated,
312 "truncated PPMd stream (at input byte 7, output byte 9)",
313 ),
314 (
315 ErrorKind::InvalidParameters,
316 "invalid PPMd parameters (at input byte 7, output byte 9)",
317 ),
318 (
319 ErrorKind::allocation_failed(4096),
320 "could not allocate 4096 bytes for the PPMd model (at input byte 7, output byte 9)",
321 ),
322 (
323 ErrorKind::memory_limit(4096, 1024),
324 "the PPMd model needs 4096 bytes, more than the 1024-byte limit (at input byte 7, output byte 9)",
325 ),
326 (
327 ErrorKind::Internal("range coder fault"),
328 "internal PPMd codec failure: range coder fault (at input byte 7, output byte 9)",
329 ),
330 ];
331 for (kind, text) in cases {
332 let e = Error::new(kind).at(at);
333 assert_eq!(chain(&e), [text]);
334 }
335 }
336
337 #[test]
338 fn the_error_crosses_threads_and_lives_forever() {
339 fn assert_bounds<T: Send + Sync + 'static>() {}
340 assert_bounds::<Error>();
341 assert_bounds::<ErrorKind>();
342 }
343
344 #[test]
345 fn the_constructors_build_the_structured_variants() {
346 assert_eq!(
347 ErrorKind::allocation_failed(5),
348 ErrorKind::AllocationFailed { bytes: 5 }
349 );
350 assert_eq!(
351 ErrorKind::memory_limit(8, 2),
352 ErrorKind::MemoryLimit {
353 required: 8,
354 limit: 2
355 }
356 );
357 let e = Error::new(ErrorKind::Truncated);
358 assert_eq!(e.kind(), ErrorKind::Truncated);
359 assert_eq!(e.position(), Position::default());
360 }
361
362 #[test]
363 fn each_kind_falls_in_exactly_one_class() {
364 for kind in every_kind() {
365 let e = Error::new(kind);
366 let data = matches!(kind, ErrorKind::Corrupt(_) | ErrorKind::Truncated);
367 let resource = matches!(
368 kind,
369 ErrorKind::AllocationFailed { .. } | ErrorKind::MemoryLimit { .. }
370 );
371 assert_eq!(e.is_data_error(), data, "{kind:?}");
372 assert_eq!(e.is_resource_error(), resource, "{kind:?}");
373 }
374 }
375
376 #[cfg(feature = "std")]
377 #[test]
378 fn io_conversion_keeps_the_kind_and_the_error() {
379 use std::io::ErrorKind as Io;
380 for (kind, io) in [
381 (ErrorKind::Corrupt("x"), Io::InvalidData),
382 (ErrorKind::Truncated, Io::UnexpectedEof),
383 (ErrorKind::InvalidParameters, Io::InvalidInput),
384 (ErrorKind::allocation_failed(1), Io::OutOfMemory),
385 (ErrorKind::memory_limit(2, 1), Io::OutOfMemory),
386 (ErrorKind::Internal("x"), Io::Other),
387 ] {
388 let e = Error::new(kind).at(Position {
389 input: 3,
390 output: 4,
391 });
392 let converted = std::io::Error::from(e);
393 assert_eq!(converted.kind(), io);
394 assert_eq!(converted.to_string(), e.to_string());
395 assert_eq!(chain(&converted), [e.to_string()]);
396 assert_eq!(converted.downcast::<Error>().unwrap(), e);
397 }
398 }
399}