1use core::fmt;
4
5#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
19#[non_exhaustive]
20pub enum ErrorKind {
21 InvalidBlockType,
23 InvalidStoredLength,
25 InvalidCodeLengths,
29 InvalidSymbol,
32 DistanceTooFar,
35 UnexpectedEnd,
37 OutputLimit,
40 RatioLimit,
42 TrailingData,
44 InvalidWindowBits,
47 InvalidLevel,
49 InvalidHeader,
51 ChecksumMismatch,
54 LengthMismatch,
56 HeaderTooLarge,
58 TooManyMembers,
60 DictionaryRequired,
62 WrongDictionary,
64 InvalidState,
66 AllocationFailed,
68}
69
70impl ErrorKind {
71 #[must_use]
82 pub const fn code(self) -> &'static str {
83 match self {
84 Self::InvalidBlockType => "compress.invalid_block_type",
85 Self::InvalidStoredLength => "compress.invalid_stored_length",
86 Self::InvalidCodeLengths => "compress.invalid_code_lengths",
87 Self::InvalidSymbol => "compress.invalid_symbol",
88 Self::DistanceTooFar => "compress.distance_too_far",
89 Self::UnexpectedEnd => "compress.unexpected_end",
90 Self::OutputLimit => "compress.output_limit",
91 Self::RatioLimit => "compress.ratio_limit",
92 Self::TrailingData => "compress.trailing_data",
93 Self::InvalidWindowBits => "compress.invalid_window_bits",
94 Self::InvalidLevel => "compress.invalid_level",
95 Self::InvalidHeader => "compress.invalid_header",
96 Self::ChecksumMismatch => "compress.checksum_mismatch",
97 Self::LengthMismatch => "compress.length_mismatch",
98 Self::HeaderTooLarge => "compress.header_too_large",
99 Self::TooManyMembers => "compress.too_many_members",
100 Self::DictionaryRequired => "compress.dictionary_required",
101 Self::WrongDictionary => "compress.wrong_dictionary",
102 Self::InvalidState => "compress.invalid_state",
103 Self::AllocationFailed => "compress.allocation_failed",
104 }
105 }
106
107 #[must_use]
117 pub const fn message(self) -> &'static str {
118 match self {
119 Self::InvalidBlockType => {
120 "invalid DEFLATE block type 3: the data is not DEFLATE or is corrupt"
121 }
122 Self::InvalidStoredLength => {
123 "stored block length check failed: the data is corrupt or truncated"
124 }
125 Self::InvalidCodeLengths => {
126 "invalid Huffman code lengths: the data is corrupt or not DEFLATE"
127 }
128 Self::InvalidSymbol => "invalid or reserved symbol: the data is corrupt",
129 Self::DistanceTooFar => {
130 "back-reference beyond the data or the window: the data is corrupt, \
131 needs a preset dictionary, or uses a larger window than configured"
132 }
133 Self::UnexpectedEnd => "input ended inside the stream: the data is truncated",
134 Self::OutputLimit => {
135 "output reached max_output: raise the limit only if the source is trusted"
136 }
137 Self::RatioLimit => {
138 "output exceeded the ratio limit: likely a decompression bomb; \
139 raise max_ratio only if the source is trusted"
140 }
141 Self::TrailingData => {
142 "bytes follow the end of the stream: strip them or allow trailing data"
143 }
144 Self::InvalidWindowBits => "window bits must be 8 to 15 (and within the decoder's)",
145 Self::InvalidLevel => "compression level must be 0 to 9",
146 Self::InvalidHeader => "invalid zlib or gzip header: the data is not in that format",
147 Self::ChecksumMismatch => "checksum mismatch: the data is corrupt",
148 Self::LengthMismatch => "gzip length mismatch: the data is corrupt or truncated",
149 Self::HeaderTooLarge => {
150 "gzip header fields exceed the configured cap: raise max_header if trusted"
151 }
152 Self::TooManyMembers => {
153 "gzip stream has more members than max_members: raise it if trusted"
154 }
155 Self::DictionaryRequired => {
156 "the zlib stream needs a preset dictionary: call set_dictionary first"
157 }
158 Self::WrongDictionary => "wrong preset dictionary: use the one the zlib stream names",
159 Self::InvalidState => "call not valid in the engine's state: reset it or finish first",
160 Self::AllocationFailed => "out of memory for output: lower max_output or free memory",
161 }
162 }
163}
164
165impl fmt::Display for ErrorKind {
166 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
167 f.write_str(self.message())
168 }
169}
170
171#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
184pub struct Error {
185 kind: ErrorKind,
186 offset: u64,
187}
188
189impl Error {
190 #[allow(dead_code)]
193 #[must_use]
194 pub(crate) const fn new(kind: ErrorKind, offset: u64) -> Self {
195 Self { kind, offset }
196 }
197
198 #[must_use]
200 pub(crate) const fn config(kind: ErrorKind) -> Self {
201 Self { kind, offset: 0 }
202 }
203
204 #[must_use]
214 pub const fn kind(&self) -> ErrorKind {
215 self.kind
216 }
217
218 #[must_use]
230 pub const fn offset(&self) -> u64 {
231 self.offset
232 }
233
234 #[must_use]
244 pub const fn code(&self) -> &'static str {
245 self.kind.code()
246 }
247}
248
249impl fmt::Display for Error {
250 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
251 if self.offset == 0 {
252 f.write_str(self.kind.message())
253 } else {
254 write!(f, "{} (at input byte {})", self.kind.message(), self.offset)
255 }
256 }
257}
258
259impl core::error::Error for Error {}
260
261impl From<Error> for ErrorKind {
262 fn from(e: Error) -> Self {
263 e.kind
264 }
265}
266
267#[cfg(feature = "std")]
268impl From<Error> for std::io::Error {
269 fn from(e: Error) -> Self {
270 let kind = match e.kind {
271 ErrorKind::UnexpectedEnd => std::io::ErrorKind::UnexpectedEof,
272 ErrorKind::InvalidWindowBits | ErrorKind::InvalidLevel | ErrorKind::InvalidState => {
273 std::io::ErrorKind::InvalidInput
274 }
275 ErrorKind::AllocationFailed => std::io::ErrorKind::OutOfMemory,
276 _ => std::io::ErrorKind::InvalidData,
277 };
278 std::io::Error::new(kind, e)
279 }
280}
281
282#[cfg(test)]
283mod tests {
284 use alloc::{collections::BTreeSet, string::ToString};
285
286 use super::*;
287
288 const ALL: [ErrorKind; 20] = [
289 ErrorKind::InvalidBlockType,
290 ErrorKind::InvalidStoredLength,
291 ErrorKind::InvalidCodeLengths,
292 ErrorKind::InvalidSymbol,
293 ErrorKind::DistanceTooFar,
294 ErrorKind::UnexpectedEnd,
295 ErrorKind::OutputLimit,
296 ErrorKind::RatioLimit,
297 ErrorKind::TrailingData,
298 ErrorKind::InvalidWindowBits,
299 ErrorKind::InvalidLevel,
300 ErrorKind::InvalidHeader,
301 ErrorKind::ChecksumMismatch,
302 ErrorKind::LengthMismatch,
303 ErrorKind::HeaderTooLarge,
304 ErrorKind::TooManyMembers,
305 ErrorKind::DictionaryRequired,
306 ErrorKind::WrongDictionary,
307 ErrorKind::InvalidState,
308 ErrorKind::AllocationFailed,
309 ];
310
311 #[test]
312 fn test_error_codes_unique_and_prefixed() {
313 let codes: BTreeSet<_> = ALL.iter().map(|k| k.code()).collect();
314 assert_eq!(codes.len(), ALL.len());
315 assert!(codes.iter().all(|c| c.starts_with("compress.")));
316 }
317
318 #[test]
319 fn test_error_messages_say_what_to_do() {
320 for k in ALL {
321 let m = k.message();
322 assert!(m.contains(':') || m.contains("must"), "{k:?}: {m}");
323 assert_eq!(k.to_string(), m);
324 }
325 }
326
327 #[test]
328 fn test_error_display_includes_offset_when_known() {
329 let e = Error::new(ErrorKind::InvalidBlockType, 17);
330 assert!(e.to_string().ends_with("(at input byte 17)"));
331 assert_eq!(e.offset(), 17);
332 assert_eq!(e.kind(), ErrorKind::InvalidBlockType);
333 assert_eq!(ErrorKind::from(e), ErrorKind::InvalidBlockType);
334 let c = Error::config(ErrorKind::InvalidLevel);
335 assert_eq!(c.to_string(), ErrorKind::InvalidLevel.message());
336 assert_eq!(c.code(), "compress.invalid_level");
337 }
338
339 #[cfg(feature = "std")]
340 #[test]
341 fn test_error_into_io_error_maps_kind() {
342 let io: std::io::Error = Error::new(ErrorKind::UnexpectedEnd, 3).into();
343 assert_eq!(io.kind(), std::io::ErrorKind::UnexpectedEof);
344 let io: std::io::Error = Error::config(ErrorKind::InvalidLevel).into();
345 assert_eq!(io.kind(), std::io::ErrorKind::InvalidInput);
346 let io: std::io::Error = Error::new(ErrorKind::ChecksumMismatch, 9).into();
347 assert_eq!(io.kind(), std::io::ErrorKind::InvalidData);
348 }
349}