Skip to main content

bzip3_rs/
error.rs

1//! Error type for the bzip3 port.
2//!
3//! Variants keep the C reference codes where they exist. New variants
4//! have no reference code. Display strings diverge from the reference text
5//! where the reference text misleads. This divergence is recorded here.
6
7use std::fmt::Display;
8use std::fmt::Formatter;
9use std::io;
10
11/// Fallible result with [`Error`] as the error type.
12pub type Result<T> = std::result::Result<T, Error>;
13
14/// Every failure mode of the bzip3 port.
15///
16/// Ten variants keep the C reference codes. Four variants have
17/// no reference code. `OutOfMemory`, `InvalidBlockSize`, and
18/// `ThreadPoolFailed` split the old reference init error. `Io` is
19/// produced only by the streaming wrappers when a read or write fails.
20/// Some reference codes map to more than one variant. Use [`Error::code`]
21/// to get the reference code.
22///
23/// The enum is `#[non_exhaustive]`. A `match` needs a wildcard arm.
24///
25/// # Examples
26///
27/// ```
28/// use bzip3_rs::decompress;
29/// use bzip3_rs::error::Error;
30///
31/// match decompress(&[0_u8; 13]) {
32///     Ok(_) => unreachable!("zero bytes are not a frame"),
33///     Err(Error::InvalidMagic) => {}
34///     Err(other) => panic!("unexpected error: {other}"),
35/// }
36/// ```
37#[derive(Debug)]
38#[non_exhaustive]
39pub enum Error {
40    /// Burrows-Wheeler transform failed (reference code -2).
41    BwtFailed,
42    /// Checksum of the decoded block does not match (reference code -3).
43    ChecksumMismatch,
44    /// RLE or LZP data is corrupt (reference code -3).
45    CorruptStageData,
46    /// Magic number is wrong (reference code -4).
47    InvalidMagic,
48    /// Header is shorter than its fixed size (reference code -4).
49    TruncatedHeader,
50    /// Header field is out of range or inconsistent (reference code -4).
51    InvalidHeader,
52    /// Data ends before the end of a payload (reference code -5).
53    TruncatedData,
54    /// Block too large (reference code -6).
55    BlockTooLarge,
56    /// Payload is larger than the decode buffer (reference code -8).
57    BufferTooSmall,
58    /// Output buffer length is not the original block size (reference code -8).
59    OutputSizeMismatch,
60    /// Working memory allocation failed. No reference code.
61    OutOfMemory,
62    /// Block size outside the valid range. No reference code.
63    InvalidBlockSize,
64    /// Private thread pool build failed. No reference code.
65    ThreadPoolFailed,
66    /// Read or write failure inside a streaming wrapper. No reference code.
67    Io(io::Error),
68}
69
70impl Error {
71    /// Map a codec error to an I/O error for streaming wrappers.
72    ///
73    /// I/O faults wrapped in [`Error::Io`] pass through
74    /// unchanged. Codec faults become invalid-data errors that keep the
75    /// original [`Error`] as their source.
76    #[must_use]
77    #[inline]
78    pub(crate) fn into_io(self) -> io::Error {
79        match self {
80            Self::Io(e) => e,
81            other => io::Error::new(io::ErrorKind::InvalidData, other),
82        }
83    }
84
85    /// Numeric reference code (-2 to -8).
86    ///
87    /// Returns `None` for variants with no reference code.
88    ///
89    /// # Examples
90    ///
91    /// ```
92    /// use bzip3_rs::error::Error;
93    ///
94    /// assert_eq!(Error::InvalidMagic.code(), Some(-4));
95    /// assert_eq!(Error::InvalidBlockSize.code(), None);
96    /// ```
97    #[must_use]
98    pub const fn code(&self) -> Option<i32> {
99        match self {
100            Self::BwtFailed => Some(-2),
101            Self::ChecksumMismatch | Self::CorruptStageData => Some(-3),
102            Self::InvalidMagic | Self::TruncatedHeader | Self::InvalidHeader => Some(-4),
103            Self::TruncatedData => Some(-5),
104            Self::BlockTooLarge => Some(-6),
105            Self::BufferTooSmall | Self::OutputSizeMismatch => Some(-8),
106            Self::OutOfMemory | Self::InvalidBlockSize | Self::ThreadPoolFailed | Self::Io(_) => {
107                None
108            }
109        }
110    }
111}
112
113impl Display for Error {
114    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
115        match self {
116            Self::BwtFailed => write!(f, "Burrows-Wheeler transform failed"),
117            Self::ChecksumMismatch => write!(f, "Checksum mismatch"),
118            Self::CorruptStageData => write!(f, "Corrupt RLE or LZP data"),
119            Self::InvalidMagic => write!(f, "Invalid magic number"),
120            Self::TruncatedHeader => write!(f, "Truncated header"),
121            Self::InvalidHeader => write!(f, "Invalid header"),
122            Self::TruncatedData => write!(f, "Truncated data"),
123            Self::BlockTooLarge => write!(f, "Block too large"),
124            Self::BufferTooSmall => write!(f, "Decode buffer too small"),
125            Self::OutputSizeMismatch => write!(f, "Output buffer size mismatch"),
126            Self::OutOfMemory => write!(f, "Out of memory"),
127            Self::InvalidBlockSize => write!(f, "Invalid block size"),
128            Self::ThreadPoolFailed => write!(f, "Thread pool build failed"),
129            Self::Io(e) => write!(f, "{e}"),
130        }
131    }
132}
133
134impl std::error::Error for Error {
135    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
136        if let Self::Io(e) = self {
137            Some(e)
138        } else {
139            None
140        }
141    }
142}
143
144impl From<io::Error> for Error {
145    #[inline]
146    fn from(e: io::Error) -> Self {
147        Self::Io(e)
148    }
149}
150
151#[cfg(test)]
152mod tests {
153    use super::Error;
154    use super::Result;
155    use std::error::Error as StdError;
156    use std::io;
157
158    #[test]
159    fn display_strings_match_port() {
160        let cases: &[(Error, &str)] = &[
161            (Error::BwtFailed, "Burrows-Wheeler transform failed"),
162            (Error::ChecksumMismatch, "Checksum mismatch"),
163            (Error::CorruptStageData, "Corrupt RLE or LZP data"),
164            (Error::InvalidMagic, "Invalid magic number"),
165            (Error::TruncatedHeader, "Truncated header"),
166            (Error::InvalidHeader, "Invalid header"),
167            (Error::TruncatedData, "Truncated data"),
168            (Error::BlockTooLarge, "Block too large"),
169            (Error::BufferTooSmall, "Decode buffer too small"),
170            (Error::OutputSizeMismatch, "Output buffer size mismatch"),
171            (Error::OutOfMemory, "Out of memory"),
172            (Error::InvalidBlockSize, "Invalid block size"),
173            (Error::ThreadPoolFailed, "Thread pool build failed"),
174        ];
175        for (e, want) in cases {
176            assert_eq!(e.to_string(), *want);
177        }
178    }
179
180    #[test]
181    fn codes_match_reference() {
182        assert_eq!(Error::BwtFailed.code(), Some(-2));
183        assert_eq!(Error::ChecksumMismatch.code(), Some(-3));
184        assert_eq!(Error::CorruptStageData.code(), Some(-3));
185        assert_eq!(Error::InvalidMagic.code(), Some(-4));
186        assert_eq!(Error::TruncatedHeader.code(), Some(-4));
187        assert_eq!(Error::InvalidHeader.code(), Some(-4));
188        assert_eq!(Error::TruncatedData.code(), Some(-5));
189        assert_eq!(Error::BlockTooLarge.code(), Some(-6));
190        assert_eq!(Error::BufferTooSmall.code(), Some(-8));
191        assert_eq!(Error::OutputSizeMismatch.code(), Some(-8));
192        assert_eq!(Error::OutOfMemory.code(), None);
193        assert_eq!(Error::InvalidBlockSize.code(), None);
194        assert_eq!(Error::ThreadPoolFailed.code(), None);
195    }
196
197    #[test]
198    fn io_variant_carries_io_error() {
199        let cause = io::Error::new(io::ErrorKind::BrokenPipe, "pipe broke");
200        let e = Error::from(cause);
201        assert!(matches!(e, Error::Io(_)));
202        assert_eq!(e.code(), None);
203        assert_eq!(e.to_string(), "pipe broke");
204        assert!(e.source().is_some());
205    }
206
207    #[test]
208    fn non_io_variants_have_no_source() {
209        assert!(Error::ChecksumMismatch.source().is_none());
210    }
211
212    #[test]
213    fn result_alias_uses_error() {
214        let r: Result<u32> = Err(Error::TruncatedData);
215        assert!(matches!(r, Err(Error::TruncatedData)));
216    }
217
218    struct HeaderThenFail {
219        rest: Vec<u8>,
220        text: &'static str,
221    }
222
223    impl io::Read for HeaderThenFail {
224        fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
225            if self.rest.is_empty() {
226                return Err(io::Error::new(io::ErrorKind::BrokenPipe, self.text));
227            }
228            let n = self.rest.len().min(buf.len());
229            buf[..n].copy_from_slice(&self.rest[..n]);
230            self.rest.drain(..n);
231            Ok(n)
232        }
233    }
234
235    struct FailWrite;
236
237    impl io::Write for FailWrite {
238        fn write(&mut self, _buf: &[u8]) -> io::Result<usize> {
239            Err(io::Error::new(io::ErrorKind::BrokenPipe, "pipe broke"))
240        }
241        fn flush(&mut self) -> io::Result<()> {
242            Ok(())
243        }
244    }
245
246    #[test]
247    fn read_decoder_passes_through_io_faults() {
248        use crate::BlockSize;
249        use crate::StreamHeader;
250        use std::io::Read as _;
251        let header = StreamHeader::new(BlockSize::MIN).to_bytes().to_vec();
252        let mut decoder = crate::read::Decoder::new(HeaderThenFail {
253            rest: header,
254            text: "pipe broke",
255        })
256        .expect("header reads fine");
257        let mut out = Vec::new();
258        let fault = decoder.read_to_end(&mut out).unwrap_err();
259        assert_eq!(fault.kind(), io::ErrorKind::BrokenPipe);
260        assert_eq!(fault.to_string(), "pipe broke");
261    }
262
263    #[test]
264    fn read_decoder_maps_codec_faults_to_invalid_data() {
265        use crate::BlockSize;
266        use crate::StreamHeader;
267        use std::io::Read as _;
268        let mut coded = StreamHeader::new(BlockSize::MIN).to_bytes().to_vec();
269        coded.extend_from_slice(&[1_u8, 0, 0, 0, 1, 0, 0, 0, 0xFF]);
270        let mut decoder =
271            crate::read::Decoder::new(std::io::Cursor::new(coded)).expect("header reads fine");
272        let mut out = Vec::new();
273        let fault = decoder.read_to_end(&mut out).unwrap_err();
274        assert_eq!(fault.kind(), io::ErrorKind::InvalidData);
275        assert!(fault.get_ref().is_some());
276    }
277
278    #[test]
279    fn write_decoder_passes_through_io_faults() {
280        use crate::BlockSize;
281        use crate::Jobs;
282        use std::io::Write as _;
283        let input = b"hello world ".repeat(100);
284        let mut coded = Vec::new();
285        crate::stream::compress_slice(&input, &mut coded, BlockSize::MIN, Jobs::SERIAL).unwrap();
286        let mut decoder = crate::write::Decoder::new(FailWrite);
287        decoder.write_all(&coded[..9]).unwrap();
288        decoder.flush().unwrap();
289        let fault = decoder.write_all(&coded[9..]).unwrap_err();
290        assert_eq!(fault.kind(), io::ErrorKind::BrokenPipe);
291    }
292
293    #[test]
294    fn write_encoder_passes_through_io_faults() {
295        use crate::BlockSize;
296        use std::io::Write as _;
297        struct FailAfterFirst {
298            seen: usize,
299        }
300        impl io::Write for FailAfterFirst {
301            fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
302                self.seen += buf.len();
303                if self.seen > 9 {
304                    return Err(io::Error::new(io::ErrorKind::BrokenPipe, "pipe broke"));
305                }
306                Ok(buf.len())
307            }
308            fn flush(&mut self) -> io::Result<()> {
309                Ok(())
310            }
311        }
312        let mut encoder =
313            crate::write::Encoder::new(FailAfterFirst { seen: 0 }, BlockSize::MIN).unwrap();
314        let chunk = vec![b'x'; BlockSize::MIN.as_usize()];
315        let fault = encoder.write(&chunk).unwrap_err();
316        assert_eq!(fault.kind(), io::ErrorKind::BrokenPipe);
317    }
318}