Skip to content

Commit

Permalink
improve error handling and utility (#100)
Browse files Browse the repository at this point in the history
* improve error handling and utility

* non-exhaustive

* fix warning

* clean up test utility and properly classify EOF errors

* address comments
  • Loading branch information
mcroomp authored Oct 16, 2024
1 parent 2dbd651 commit 2a446f4
Show file tree
Hide file tree
Showing 8 changed files with 373 additions and 195 deletions.
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

0 comments on commit 2a446f4

Please sign in to comment.