Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

improve error handling and utility #100

Merged
merged 5 commits into from
Oct 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added images/mismatch_encode.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 2 additions & 5 deletions src/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,8 @@ pub const fn u32_bit_length(v: u32) -> u8 {
}

#[cold]
pub fn err_exit_code<T>(_error_code: ExitCode, message: &str) -> anyhow::Result<T> {
return Err(anyhow::Error::new(LeptonError {
exit_code: _error_code,
message: message.to_string(),
}));
pub fn err_exit_code<T>(error_code: ExitCode, message: &str) -> anyhow::Result<T> {
return Err(anyhow::Error::new(LeptonError::new(error_code, message)));
}

pub fn buffer_prefix_matches_marker<const BS: usize, const MS: usize>(
Expand Down
135 changes: 128 additions & 7 deletions src/lepton_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@
* This software incorporates material from third parties. See NOTICE.txt for details.
*--------------------------------------------------------------------------------------------*/

use std::fmt::Display;
use std::{fmt::Display, io::ErrorKind};

#[derive(Debug, Clone, Copy, PartialEq)]
#[allow(dead_code)]

#[non_exhaustive]
/// Well-defined errors for bad things that are expected to happen as part of compression/decompression
pub enum ExitCode {
//AssertionFailure = 1,
//CodingError = 2,
//ShortRead = 3,
ShortRead = 3,
Unsupported4Colors = 4,
CoefficientOutOfRange = 6,
StreamInconsistent = 7,
Expand All @@ -23,10 +23,13 @@ pub enum ExitCode {
//ThreadingPartialMcu = 12,
VersionUnsupported = 13,
//OnlyGarbageNoJpeg = 14,
//OsError = 33,
OsError = 33,
//HeaderTooLarge = 34,
//BlockOffsetOOM = 37,
UnsupportedJpeg = 42,
UnsupportedJpegWithZeroIdct0 = 43,
InvalidResetCode = 44,
InvalidPadding = 45,
//WrapperOutputWriteFailed = 101,
BadLeptonFile = 102,

Expand All @@ -48,13 +51,13 @@ impl Display for ExitCode {
}

/// Standard error returned by Lepton library
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct LeptonError {
/// standard error code
pub exit_code: ExitCode,
exit_code: ExitCode,

/// diagnostic message including location. Content should not be relied on.
pub message: String,
message: String,
}

impl Display for LeptonError {
Expand All @@ -63,4 +66,122 @@ impl Display for LeptonError {
}
}

impl LeptonError {
pub fn new(exit_code: ExitCode, message: &str) -> LeptonError {
LeptonError {
exit_code,
message: message.to_owned(),
}
}

pub fn exit_code(&self) -> ExitCode {
self.exit_code
}

pub fn message(&self) -> &str {
&self.message
}
}

impl std::error::Error for LeptonError {}

impl From<anyhow::Error> for LeptonError {
fn from(mut error: anyhow::Error) -> Self {
// first see if there is a LeptonError already inside
match error.downcast::<LeptonError>() {
Ok(le) => {
return le;
}
Err(old_error) => {
error = old_error;
}
}

// capture the original error string before we lose it due
// to downcasting to look for stashed LeptonErrors
let original_string = error.to_string();

// see if there is a LeptonError hiding inside an io error
// which happens if we cross an API boundary that returns an std::io:Error
// like Read or Write
match error.downcast::<std::io::Error>() {
Ok(ioe) => match ioe.downcast::<LeptonError>() {
Ok(le) => {
return le;
}
Err(e) => {
return LeptonError {
exit_code: get_io_error_exit_code(&e),
message: format!("{} {}", e, original_string),
};
}
},
Err(_) => {}
}

// don't know what we got, so treat it as a general failure
return LeptonError {
exit_code: ExitCode::GeneralFailure,
message: original_string,
};
}
}

fn get_io_error_exit_code(e: &std::io::Error) -> ExitCode {
if e.kind() == ErrorKind::UnexpectedEof {
ExitCode::ShortRead
} else {
ExitCode::OsError
}
}

/// translates std::io::Error into LeptonError
impl From<std::io::Error> for LeptonError {
#[track_caller]
fn from(e: std::io::Error) -> Self {
match e.downcast::<LeptonError>() {
Ok(le) => {
return le;
}
Err(e) => {
let caller = std::panic::Location::caller();
return LeptonError {
exit_code: get_io_error_exit_code(&e),
message: format!("error {} at {}", e.to_string(), caller.to_string()),
};
}
}
}
}

/// translates LeptonError into std::io::Error, which involves putting into a Box and using Other
impl From<LeptonError> for std::io::Error {
fn from(e: LeptonError) -> Self {
return std::io::Error::new(std::io::ErrorKind::Other, e);
}
}

#[test]
fn test_error_translation() {
// test wrapping inside an io error
fn my_std_error() -> Result<(), std::io::Error> {
Err(LeptonError::new(ExitCode::SyntaxError, "test error").into())
}

let e: LeptonError = my_std_error().unwrap_err().into();
assert_eq!(e.exit_code, ExitCode::SyntaxError);
assert_eq!(e.message, "test error");

// wrapping inside anyhow
fn my_anyhow() -> Result<(), anyhow::Error> {
Err(LeptonError::new(ExitCode::SyntaxError, "test error").into())
}

let e: LeptonError = my_anyhow().unwrap_err().into();
assert_eq!(e.exit_code, ExitCode::SyntaxError);
assert_eq!(e.message, "test error");

// an IO error should be translated into an OsError
let e: LeptonError = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found").into();
assert_eq!(e.exit_code, ExitCode::OsError);
}
Loading
Loading