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
61impl Display for ExitCode {
62    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
63        write!(f, "{:?}", self)
64    }
65}
66
67impl ExitCode {
68    /// Converts the error code into an integer for use as an error code when
69    /// returning from a C API.
70    pub fn as_integer_error_code(self) -> i32 {
71        self as i32
72    }
73}
74
75/// Since errors are rare and stop everything, we want them to be as lightweight as possible.
76#[derive(Debug, Clone)]
77struct PreflateErrorInternal {
78    exit_code: ExitCode,
79    message: String,
80}
81
82/// Standard error returned by Preflate library
83#[derive(Clone)]
84pub struct PreflateError {
85    i: Box<PreflateErrorInternal>,
86}
87
88/// don't show internal indirrection in debug output
89impl std::fmt::Debug for PreflateError {
90    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91        self.i.fmt(f)
92    }
93}
94
95pub type Result<T> = std::result::Result<T, PreflateError>;
96
97impl Display for PreflateError {
98    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
99        write!(f, "{0}: {1}", self.i.exit_code, self.i.message)
100    }
101}
102
103impl PreflateError {
104    pub fn new(exit_code: ExitCode, message: impl AsRef<str>) -> PreflateError {
105        PreflateError {
106            i: Box::new(PreflateErrorInternal {
107                exit_code,
108                message: message.as_ref().to_owned(),
109            }),
110        }
111    }
112
113    pub fn exit_code(&self) -> ExitCode {
114        self.i.exit_code
115    }
116
117    pub fn message(&self) -> &str {
118        &self.i.message
119    }
120
121    #[cold]
122    #[inline(never)]
123    #[track_caller]
124    pub fn add_context(&mut self) {
125        self.i
126            .message
127            .push_str(&format!("\n at {}", std::panic::Location::caller()));
128    }
129}
130
131#[cold]
132#[track_caller]
133pub fn err_exit_code<T>(error_code: ExitCode, message: impl AsRef<str>) -> Result<T> {
134    let mut e = PreflateError::new(error_code, message.as_ref());
135    e.add_context();
136    return Err(e);
137}
138
139pub trait AddContext<T> {
140    #[track_caller]
141    fn context(self) -> Result<T>;
142    fn with_context<FN: Fn() -> String>(self, f: FN) -> Result<T>;
143}
144
145impl<T, E: Into<PreflateError>> AddContext<T> for core::result::Result<T, E> {
146    #[track_caller]
147    fn context(self) -> Result<T> {
148        match self {
149            Ok(x) => Ok(x),
150            Err(e) => {
151                let mut e = e.into();
152                e.add_context();
153                Err(e)
154            }
155        }
156    }
157
158    #[track_caller]
159    fn with_context<FN: Fn() -> String>(self, f: FN) -> Result<T> {
160        match self {
161            Ok(x) => Ok(x),
162            Err(e) => {
163                let mut e = e.into();
164                e.i.message.push_str(&f());
165                e.add_context();
166                Err(e)
167            }
168        }
169    }
170}
171
172impl std::error::Error for PreflateError {}
173
174fn get_io_error_exit_code(e: &std::io::Error) -> ExitCode {
175    if e.kind() == ErrorKind::UnexpectedEof {
176        ExitCode::ShortRead
177    } else {
178        ExitCode::OsError
179    }
180}
181
182impl From<TryFromIntError> for PreflateError {
183    #[track_caller]
184    fn from(e: TryFromIntError) -> Self {
185        let mut e = PreflateError::new(ExitCode::GeneralFailure, e.to_string().as_str());
186        e.add_context();
187        e
188    }
189}
190
191/// translates std::io::Error into PreflateError
192impl From<std::io::Error> for PreflateError {
193    #[track_caller]
194    fn from(e: std::io::Error) -> Self {
195        match e.downcast::<PreflateError>() {
196            Ok(le) => {
197                return le;
198            }
199            Err(e) => {
200                let mut e = PreflateError::new(get_io_error_exit_code(&e), e.to_string().as_str());
201                e.add_context();
202                e
203            }
204        }
205    }
206}
207
208/// translates PreflateError into std::io::Error, which involves putting into a Box and using Other
209impl From<PreflateError> for std::io::Error {
210    fn from(e: PreflateError) -> Self {
211        return std::io::Error::new(std::io::ErrorKind::Other, e);
212    }
213}
214
215#[test]
216fn test_error_translation() {
217    // test wrapping inside an io error
218    fn my_std_error() -> core::result::Result<(), std::io::Error> {
219        Err(PreflateError::new(ExitCode::AnalyzeFailed, "test error").into())
220    }
221
222    let e: PreflateError = my_std_error().unwrap_err().into();
223    assert_eq!(e.exit_code(), ExitCode::AnalyzeFailed);
224    assert_eq!(e.message(), "test error");
225
226    // an IO error should be translated into an OsError
227    let e: PreflateError =
228        std::io::Error::new(std::io::ErrorKind::NotFound, "file not found").into();
229    assert_eq!(e.exit_code(), ExitCode::OsError);
230}