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 #[must_use]
192 pub(crate) const fn new(kind: ErrorKind, offset: u64) -> Self {
193 Self { kind, offset }
194 }
195
196 #[must_use]
198 pub(crate) const fn config(kind: ErrorKind) -> Self {
199 Self { kind, offset: 0 }
200 }
201
202 #[must_use]
212 pub const fn kind(&self) -> ErrorKind {
213 self.kind
214 }
215
216 #[must_use]
228 pub const fn offset(&self) -> u64 {
229 self.offset
230 }
231
232 #[must_use]
242 pub const fn code(&self) -> &'static str {
243 self.kind.code()
244 }
245}
246
247impl fmt::Display for Error {
248 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
249 if self.offset == 0 {
250 f.write_str(self.kind.message())
251 } else {
252 write!(f, "{} (at input byte {})", self.kind.message(), self.offset)
253 }
254 }
255}
256
257impl core::error::Error for Error {}
258
259impl From<Error> for ErrorKind {
260 fn from(e: Error) -> Self {
261 e.kind
262 }
263}
264
265#[cfg(feature = "std")]
266impl From<Error> for std::io::Error {
267 fn from(e: Error) -> Self {
268 let kind = match e.kind {
269 ErrorKind::UnexpectedEnd => std::io::ErrorKind::UnexpectedEof,
270 ErrorKind::InvalidWindowBits | ErrorKind::InvalidLevel | ErrorKind::InvalidState => {
271 std::io::ErrorKind::InvalidInput
272 }
273 ErrorKind::AllocationFailed => std::io::ErrorKind::OutOfMemory,
274 _ => std::io::ErrorKind::InvalidData,
275 };
276 std::io::Error::new(kind, e)
277 }
278}
279
280#[cfg(test)]
281mod tests {
282 use alloc::{collections::BTreeSet, string::ToString};
283
284 use super::*;
285
286 const ALL: [ErrorKind; 20] = [
287 ErrorKind::InvalidBlockType,
288 ErrorKind::InvalidStoredLength,
289 ErrorKind::InvalidCodeLengths,
290 ErrorKind::InvalidSymbol,
291 ErrorKind::DistanceTooFar,
292 ErrorKind::UnexpectedEnd,
293 ErrorKind::OutputLimit,
294 ErrorKind::RatioLimit,
295 ErrorKind::TrailingData,
296 ErrorKind::InvalidWindowBits,
297 ErrorKind::InvalidLevel,
298 ErrorKind::InvalidHeader,
299 ErrorKind::ChecksumMismatch,
300 ErrorKind::LengthMismatch,
301 ErrorKind::HeaderTooLarge,
302 ErrorKind::TooManyMembers,
303 ErrorKind::DictionaryRequired,
304 ErrorKind::WrongDictionary,
305 ErrorKind::InvalidState,
306 ErrorKind::AllocationFailed,
307 ];
308
309 #[test]
310 fn test_error_codes_unique_and_prefixed() {
311 let codes: BTreeSet<_> = ALL.iter().map(|k| k.code()).collect();
312 assert_eq!(codes.len(), ALL.len());
313 assert!(codes.iter().all(|c| c.starts_with("compress.")));
314 }
315
316 #[test]
317 fn test_error_messages_say_what_to_do() {
318 for k in ALL {
319 let m = k.message();
320 assert!(m.contains(':') || m.contains("must"), "{k:?}: {m}");
321 assert_eq!(k.to_string(), m);
322 }
323 }
324
325 #[test]
326 fn test_error_display_includes_offset_when_known() {
327 let e = Error::new(ErrorKind::InvalidBlockType, 17);
328 assert!(e.to_string().ends_with("(at input byte 17)"));
329 assert_eq!(e.offset(), 17);
330 assert_eq!(e.kind(), ErrorKind::InvalidBlockType);
331 assert_eq!(ErrorKind::from(e), ErrorKind::InvalidBlockType);
332 let c = Error::config(ErrorKind::InvalidLevel);
333 assert_eq!(c.to_string(), ErrorKind::InvalidLevel.message());
334 assert_eq!(c.code(), "compress.invalid_level");
335 }
336
337 #[cfg(feature = "std")]
338 #[test]
339 fn test_error_into_io_error_maps_kind() {
340 let io: std::io::Error = Error::new(ErrorKind::UnexpectedEnd, 3).into();
341 assert_eq!(io.kind(), std::io::ErrorKind::UnexpectedEof);
342 let io: std::io::Error = Error::config(ErrorKind::InvalidLevel).into();
343 assert_eq!(io.kind(), std::io::ErrorKind::InvalidInput);
344 let io: std::io::Error = Error::new(ErrorKind::ChecksumMismatch, 9).into();
345 assert_eq!(io.kind(), std::io::ErrorKind::InvalidData);
346 }
347}