Skip to main content

preflate_rs/
preflate_error.rs

1/*---------------------------------------------------------------------------------------------
2 *  Copyright (c) Microsoft Corporation. All rights reserved.
3 *  Licensed under the Apache License, Version 2.0. See LICENSE.txt in the project root for license information.
4 *  This software incorporates material from third parties. See NOTICE.txt for details.
5 *--------------------------------------------------------------------------------------------*/
6
7use std::fmt::Display;
8use std::io::ErrorKind;
9use std::num::TryFromIntError;
10
11#[derive(Debug, Clone, Copy, PartialEq)]
12#[non_exhaustive]
13pub enum ExitCode {
14    ReadDeflate = 1,
15    InvalidPredictionData = 2,
16    AnalyzeFailed = 3,
17    RecompressFailed = 4,
18    RoundtripMismatch = 5,
19    ReadBlock = 6,
20    PredictBlock = 7,
21    PredictTree = 8,
22    RecreateBlock = 9,
23    RecreateTree = 10,
24    EncodeBlock = 11,
25    InvalidCompressedWrapper = 12,
26    ZstdError = 14,
27    InvalidParameterHeader = 15,
28    ShortRead = 16,
29    OsError = 17,
30    GeneralFailure = 18,
31    InvalidIDat = 19,
32    MatchNotFound = 20,
33
34    /// The deflate data stream is invalid or corrupt and cannot be properly read
35    /// or reconstructed.
36    InvalidDeflate = 21,
37
38    /// We couldn't find a reasonable candidate for the version of the
39    /// deflate algorithm used to compress the data. No gain would be
40    /// had from recompressing the data since the amount of correction
41    /// data would be larger than the original data.
42    NoCompressionCandidates = 22,
43
44    InvalidParameter = 23,
45
46    /// panic in rust code
47    AssertionFailure = 24,
48
49    /// Non-zero padding found in deflate, which we currently don't handle
50    NonZeroPadding = 25,
51
52    /// Unable to predict the sequence of compression. Doesn't mean that
53    /// the deflate content was invalid, but just that we don't handle
54    /// some of the rare corner cases.
55    PredictionFailure = 26,
56
57    /// Plain text memory limit exceeded
58    PlainTextLimit = 27,
59
60    /// WebP decoding error
61    WebPDecodeError = 28,
62
63    /// Out of memory
64    OutOfMemory = 29,
65}
66
67impl Display for ExitCode {
68    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
69        write!(f, "{:?}", self)
70    }
71}
72
73impl ExitCode {
74    /// Converts the error code into an integer for use as an error code when
75    /// returning from a C API.
76    pub fn as_integer_error_code(self) -> i32 {
77        self as i32
78    }
79}
80
81/// Since errors are rare and stop everything, we want them to be as lightweight as possible.
82#[derive(Debug, Clone)]
83struct PreflateErrorInternal {
84    exit_code: ExitCode,
85    message: String,
86}
87
88/// Standard error returned by Preflate library
89#[derive(Clone)]
90pub struct PreflateError {
91    i: Box<PreflateErrorInternal>,
92}
93
94/// don't show internal indirrection in debug output
95impl std::fmt::Debug for PreflateError {
96    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
97        self.i.fmt(f)
98    }
99}
100
101pub type Result<T> = std::result::Result<T, PreflateError>;
102
103impl Display for PreflateError {
104    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
105        write!(f, "{0}: {1}", self.i.exit_code, self.i.message)
106    }
107}
108
109impl PreflateError {
110    pub fn new(exit_code: ExitCode, message: impl AsRef<str>) -> PreflateError {
111        PreflateError {
112            i: Box::new(PreflateErrorInternal {
113                exit_code,
114                message: message.as_ref().to_owned(),
115            }),
116        }
117    }
118
119    pub fn exit_code(&self) -> ExitCode {
120        self.i.exit_code
121    }
122
123    pub fn message(&self) -> &str {
124        &self.i.message
125    }
126
127    #[cold]
128    #[inline(never)]
129    #[track_caller]
130    pub fn add_context(&mut self) {
131        self.i
132            .message
133            .push_str(&format!("\n at {}", std::panic::Location::caller()));
134    }
135}
136
137#[cold]
138#[track_caller]
139pub fn err_exit_code<T>(error_code: ExitCode, message: impl AsRef<str>) -> Result<T> {
140    let mut e = PreflateError::new(error_code, message.as_ref());
141    e.add_context();
142    return Err(e);
143}
144
145pub trait AddContext<T> {
146    #[track_caller]
147    fn context(self) -> Result<T>;
148    fn with_context<FN: Fn() -> String>(self, f: FN) -> Result<T>;
149}
150
151impl<T, E: Into<PreflateError>> AddContext<T> for core::result::Result<T, E> {
152    #[track_caller]
153    fn context(self) -> Result<T> {
154        match self {
155            Ok(x) => Ok(x),
156            Err(e) => {
157                let mut e = e.into();
158                e.add_context();
159                Err(e)
160            }
161        }
162    }
163
164    #[track_caller]
165    fn with_context<FN: Fn() -> String>(self, f: FN) -> Result<T> {
166        match self {
167            Ok(x) => Ok(x),
168            Err(e) => {
169                let mut e = e.into();
170                e.i.message.push_str(&f());
171                e.add_context();
172                Err(e)
173            }
174        }
175    }
176}
177
178impl std::error::Error for PreflateError {}
179
180fn get_io_error_exit_code(e: &std::io::Error) -> ExitCode {
181    if e.kind() == ErrorKind::UnexpectedEof {
182        ExitCode::ShortRead
183    } else {
184        ExitCode::OsError
185    }
186}
187
188impl From<TryFromIntError> for PreflateError {
189    #[track_caller]
190    fn from(e: TryFromIntError) -> Self {
191        let mut e = PreflateError::new(ExitCode::GeneralFailure, e.to_string().as_str());
192        e.add_context();
193        e
194    }
195}
196
197/// translates std::io::Error into PreflateError
198impl From<std::io::Error> for PreflateError {
199    #[track_caller]
200    fn from(e: std::io::Error) -> Self {
201        match e.downcast::<PreflateError>() {
202            Ok(le) => {
203                return le;
204            }
205            Err(e) => {
206                let mut e = PreflateError::new(get_io_error_exit_code(&e), e.to_string().as_str());
207                e.add_context();
208                e
209            }
210        }
211    }
212}
213
214/// translates PreflateError into std::io::Error, which involves putting into a Box and using Other
215impl From<PreflateError> for std::io::Error {
216    fn from(e: PreflateError) -> Self {
217        return std::io::Error::new(std::io::ErrorKind::Other, e);
218    }
219}
220
221#[test]
222fn test_error_translation() {
223    // test wrapping inside an io error
224    fn my_std_error() -> core::result::Result<(), std::io::Error> {
225        Err(PreflateError::new(ExitCode::AnalyzeFailed, "test error").into())
226    }
227
228    let e: PreflateError = my_std_error().unwrap_err().into();
229    assert_eq!(e.exit_code(), ExitCode::AnalyzeFailed);
230    assert_eq!(e.message(), "test error");
231
232    // an IO error should be translated into an OsError
233    let e: PreflateError =
234        std::io::Error::new(std::io::ErrorKind::NotFound, "file not found").into();
235    assert_eq!(e.exit_code(), ExitCode::OsError);
236}