use std::fmt::Display;
use std::io::ErrorKind;
use std::num::TryFromIntError;
#[derive(Debug, Clone, Copy, PartialEq)]
#[allow(dead_code)]
#[non_exhaustive]
pub enum ExitCode {
AssertionFailure = 1,
ShortRead = 3,
Unsupported4Colors = 4,
CoefficientOutOfRange = 6,
StreamInconsistent = 7,
ProgressiveUnsupported = 8,
SamplingBeyondTwoUnsupported = 10,
VersionUnsupported = 13,
OsError = 33,
UnsupportedJpeg = 42,
UnsupportedJpegWithZeroIdct0 = 43,
InvalidResetCode = 44,
InvalidPadding = 45,
BadLeptonFile = 102,
ChannelFailure = 103,
IntegerCastOverflow = 1000,
VerificationLengthMismatch = 1004,
VerificationContentMismatch = 1005,
SyntaxError = 1006,
FileNotFound = 1007,
ExternalVerificationFailed = 1008,
OutOfMemory = 2000,
}
impl Display for ExitCode {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
impl ExitCode {
pub fn as_integer_error_code(self) -> i32 {
self as i32
}
}
#[derive(Debug, Clone)]
struct LeptonErrorInternal {
exit_code: ExitCode,
message: String,
}
#[derive(Debug, Clone)]
pub struct LeptonError {
i: Box<LeptonErrorInternal>,
}
pub type Result<T> = std::result::Result<T, LeptonError>;
impl Display for LeptonError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{0}: {1}", self.i.exit_code, self.i.message)
}
}
impl LeptonError {
pub fn new(exit_code: ExitCode, message: impl AsRef<str>) -> LeptonError {
LeptonError {
i: Box::new(LeptonErrorInternal {
exit_code,
message: message.as_ref().to_owned(),
}),
}
}
pub fn exit_code(&self) -> ExitCode {
self.i.exit_code
}
pub fn message(&self) -> &str {
&self.i.message
}
#[cold]
#[inline(never)]
#[track_caller]
pub fn add_context(&mut self) {
self.i
.message
.push_str(&format!("\n at {}", std::panic::Location::caller()));
}
}
#[cold]
#[track_caller]
pub fn err_exit_code<T>(error_code: ExitCode, message: impl AsRef<str>) -> Result<T> {
let mut e = LeptonError::new(error_code, message.as_ref());
e.add_context();
return Err(e);
}
pub trait AddContext<T> {
#[track_caller]
fn context(self) -> Result<T>;
}
impl<T, E: Into<LeptonError>> AddContext<T> for core::result::Result<T, E> {
#[track_caller]
fn context(self) -> Result<T> {
match self {
Ok(x) => Ok(x),
Err(e) => {
let mut e = e.into();
e.add_context();
Err(e)
}
}
}
}
impl std::error::Error for LeptonError {}
fn get_io_error_exit_code(e: &std::io::Error) -> ExitCode {
if e.kind() == ErrorKind::UnexpectedEof {
ExitCode::ShortRead
} else {
ExitCode::OsError
}
}
impl From<TryFromIntError> for LeptonError {
#[track_caller]
fn from(e: TryFromIntError) -> Self {
let mut e = LeptonError::new(ExitCode::IntegerCastOverflow, e.to_string());
e.add_context();
e
}
}
impl<T> From<std::sync::mpsc::SendError<T>> for LeptonError {
#[track_caller]
fn from(e: std::sync::mpsc::SendError<T>) -> Self {
let mut e = LeptonError::new(ExitCode::ChannelFailure, e.to_string());
e.add_context();
e
}
}
impl From<std::sync::mpsc::RecvError> for LeptonError {
#[track_caller]
fn from(e: std::sync::mpsc::RecvError) -> Self {
let mut e = LeptonError::new(ExitCode::ChannelFailure, e.to_string());
e.add_context();
e
}
}
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 mut e = LeptonError::new(get_io_error_exit_code(&e), e.to_string());
e.add_context();
e
}
}
}
}
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() {
fn my_std_error() -> core::result::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");
let e: LeptonError = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found").into();
assert_eq!(e.exit_code(), ExitCode::OsError);
}