1use std::fmt::Display;
8use std::fmt::Formatter;
9use std::io;
10
11pub type Result<T> = std::result::Result<T, Error>;
13
14#[derive(Debug)]
38#[non_exhaustive]
39pub enum Error {
40 BwtFailed,
42 ChecksumMismatch,
44 CorruptStageData,
46 InvalidMagic,
48 TruncatedHeader,
50 InvalidHeader,
52 TruncatedData,
54 BlockTooLarge,
56 BufferTooSmall,
58 OutputSizeMismatch,
60 OutOfMemory,
62 InvalidBlockSize,
64 ThreadPoolFailed,
66 Io(io::Error),
68}
69
70impl Error {
71 #[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 #[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}