#![warn(clippy::all)]
pub use std::error::Error as ErrorTrait;
use std::fmt;
use std::fmt::Debug;
use std::result::Result as StdResult;
mod immut_str;
pub use immut_str::ImmutStr;
pub type BError = Box<Error>;
pub type Result<T, E = BError> = StdResult<T, E>;
#[derive(Debug)]
pub struct Error {
pub etype: ErrorType,
pub esource: ErrorSource,
pub retry: RetryType,
pub cause: Option<Box<(dyn ErrorTrait + Send + Sync)>>,
pub context: Option<ImmutStr>,
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum ErrorSource {
Upstream,
Downstream,
Internal,
Unset,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum RetryType {
Decided(bool),
ReusedOnly, }
impl RetryType {
pub fn decide_reuse(&mut self, reused: bool) {
if matches!(self, RetryType::ReusedOnly) {
*self = RetryType::Decided(reused);
}
}
pub fn retry(&self) -> bool {
match self {
RetryType::Decided(b) => *b,
RetryType::ReusedOnly => {
panic!("Retry is not decided")
}
}
}
}
impl From<bool> for RetryType {
fn from(b: bool) -> Self {
RetryType::Decided(b)
}
}
impl ErrorSource {
pub fn as_str(&self) -> &str {
match self {
Self::Upstream => "Upstream",
Self::Downstream => "Downstream",
Self::Internal => "Internal",
Self::Unset => "",
}
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum ErrorType {
ConnectTimedout,
ConnectRefused,
ConnectNoRoute,
TLSWantX509Lookup,
TLSHandshakeFailure,
TLSHandshakeTimedout,
InvalidCert,
HandshakeError, ConnectError, BindError,
AcceptError,
SocketError,
ConnectProxyFailure,
InvalidHTTPHeader,
H1Error, H2Error, H2Downgrade, InvalidH2, ReadError,
WriteError,
ReadTimedout,
WriteTimedout,
ConnectionClosed,
HTTPStatus(u16),
FileOpenError,
FileCreateError,
FileReadError,
FileWriteError,
InternalError,
UnknownError,
Custom(&'static str),
CustomCode(&'static str, u16),
}
impl ErrorType {
pub const fn new(name: &'static str) -> Self {
ErrorType::Custom(name)
}
pub const fn new_code(name: &'static str, code: u16) -> Self {
ErrorType::CustomCode(name, code)
}
pub fn as_str(&self) -> &str {
match self {
ErrorType::ConnectTimedout => "ConnectTimedout",
ErrorType::ConnectRefused => "ConnectRefused",
ErrorType::ConnectNoRoute => "ConnectNoRoute",
ErrorType::ConnectProxyFailure => "ConnectProxyFailure",
ErrorType::TLSWantX509Lookup => "TLSWantX509Lookup",
ErrorType::TLSHandshakeFailure => "TLSHandshakeFailure",
ErrorType::TLSHandshakeTimedout => "TLSHandshakeTimedout",
ErrorType::InvalidCert => "InvalidCert",
ErrorType::HandshakeError => "HandshakeError",
ErrorType::ConnectError => "ConnectError",
ErrorType::BindError => "BindError",
ErrorType::AcceptError => "AcceptError",
ErrorType::SocketError => "SocketError",
ErrorType::InvalidHTTPHeader => "InvalidHTTPHeader",
ErrorType::H1Error => "H1Error",
ErrorType::H2Error => "H2Error",
ErrorType::InvalidH2 => "InvalidH2",
ErrorType::H2Downgrade => "H2Downgrade",
ErrorType::ReadError => "ReadError",
ErrorType::WriteError => "WriteError",
ErrorType::ReadTimedout => "ReadTimedout",
ErrorType::WriteTimedout => "WriteTimedout",
ErrorType::ConnectionClosed => "ConnectionClosed",
ErrorType::FileOpenError => "FileOpenError",
ErrorType::FileCreateError => "FileCreateError",
ErrorType::FileReadError => "FileReadError",
ErrorType::FileWriteError => "FileWriteError",
ErrorType::HTTPStatus(_) => "HTTPStatus",
ErrorType::InternalError => "InternalError",
ErrorType::UnknownError => "UnknownError",
ErrorType::Custom(s) => s,
ErrorType::CustomCode(s, _) => s,
}
}
}
impl Error {
#[inline]
pub fn create(
etype: ErrorType,
esource: ErrorSource,
context: Option<ImmutStr>,
cause: Option<Box<dyn ErrorTrait + Send + Sync>>,
) -> BError {
let retry = if let Some(c) = cause.as_ref() {
if let Some(e) = c.downcast_ref::<BError>() {
e.retry
} else {
false.into()
}
} else {
false.into()
};
Box::new(Error {
etype,
esource,
retry,
cause,
context,
})
}
#[inline]
fn do_new(e: ErrorType, s: ErrorSource) -> BError {
Self::create(e, s, None, None)
}
#[inline]
pub fn new(e: ErrorType) -> BError {
Self::do_new(e, ErrorSource::Unset)
}
#[inline]
pub fn because<S: Into<ImmutStr>, E: Into<Box<dyn ErrorTrait + Send + Sync>>>(
e: ErrorType,
context: S,
cause: E,
) -> BError {
Self::create(
e,
ErrorSource::Unset,
Some(context.into()),
Some(cause.into()),
)
}
#[inline]
pub fn e_because<T, S: Into<ImmutStr>, E: Into<Box<dyn ErrorTrait + Send + Sync>>>(
e: ErrorType,
context: S,
cause: E,
) -> Result<T> {
Err(Self::because(e, context, cause))
}
#[inline]
pub fn explain<S: Into<ImmutStr>>(e: ErrorType, context: S) -> BError {
Self::create(e, ErrorSource::Unset, Some(context.into()), None)
}
#[inline]
pub fn e_explain<T, S: Into<ImmutStr>>(e: ErrorType, context: S) -> Result<T> {
Err(Self::explain(e, context))
}
#[inline]
pub fn new_up(e: ErrorType) -> BError {
Self::do_new(e, ErrorSource::Upstream)
}
#[inline]
pub fn new_down(e: ErrorType) -> BError {
Self::do_new(e, ErrorSource::Downstream)
}
#[inline]
pub fn new_in(e: ErrorType) -> BError {
Self::do_new(e, ErrorSource::Internal)
}
#[inline]
pub fn new_str(s: &'static str) -> BError {
Self::do_new(ErrorType::Custom(s), ErrorSource::Unset)
}
#[inline]
pub fn err<T>(e: ErrorType) -> Result<T> {
Err(Self::new(e))
}
#[inline]
pub fn err_up<T>(e: ErrorType) -> Result<T> {
Err(Self::new_up(e))
}
#[inline]
pub fn err_down<T>(e: ErrorType) -> Result<T> {
Err(Self::new_down(e))
}
#[inline]
pub fn err_in<T>(e: ErrorType) -> Result<T> {
Err(Self::new_in(e))
}
pub fn etype(&self) -> &ErrorType {
&self.etype
}
pub fn esource(&self) -> &ErrorSource {
&self.esource
}
pub fn retry(&self) -> bool {
self.retry.retry()
}
pub fn set_retry(&mut self, retry: bool) {
self.retry = retry.into();
}
pub fn reason_str(&self) -> &str {
self.etype.as_str()
}
pub fn source_str(&self) -> &str {
self.esource.as_str()
}
pub fn as_up(&mut self) {
self.esource = ErrorSource::Upstream;
}
pub fn as_down(&mut self) {
self.esource = ErrorSource::Downstream;
}
pub fn as_in(&mut self) {
self.esource = ErrorSource::Internal;
}
pub fn into_up(mut self: BError) -> BError {
self.as_up();
self
}
pub fn into_down(mut self: BError) -> BError {
self.as_down();
self
}
pub fn into_in(mut self: BError) -> BError {
self.as_in();
self
}
pub fn into_err<T>(self: BError) -> Result<T> {
Err(self)
}
pub fn set_cause<C: Into<Box<dyn ErrorTrait + Send + Sync>>>(&mut self, cause: C) {
self.cause = Some(cause.into());
}
pub fn set_context<T: Into<ImmutStr>>(&mut self, context: T) {
self.context = Some(context.into());
}
pub fn more_context<T: Into<ImmutStr>>(self: BError, context: T) -> BError {
let esource = self.esource.clone();
let retry = self.retry;
let mut e = Self::because(self.etype.clone(), context, self);
e.esource = esource;
e.retry = retry;
e
}
fn chain_display(&self, previous: Option<&Error>, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if previous.map(|p| p.esource != self.esource).unwrap_or(true) {
write!(f, "{}", self.esource.as_str())?
}
if previous.map(|p| p.etype != self.etype).unwrap_or(true) {
write!(f, " {}", self.etype.as_str())?
}
if let Some(c) = self.context.as_ref() {
write!(f, " context: {}", c)?;
}
if let Some(c) = self.cause.as_ref() {
if let Some(e) = c.downcast_ref::<BError>() {
write!(f, " cause: ")?;
e.chain_display(Some(self), f)
} else {
write!(f, " cause: {}", c)
}
} else {
Ok(())
}
}
pub fn root_etype(&self) -> &ErrorType {
self.cause.as_ref().map_or(&self.etype, |c| {
c.downcast_ref::<BError>()
.map_or(&self.etype, |e| e.root_etype())
})
}
pub fn root_cause(&self) -> &(dyn ErrorTrait + Send + Sync + 'static) {
self.cause.as_deref().map_or(self, |c| {
c.downcast_ref::<BError>().map_or(c, |e| e.root_cause())
})
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.chain_display(None, f)
}
}
impl ErrorTrait for Error {}
pub trait Context<T> {
fn err_context<C: Into<ImmutStr>, F: FnOnce() -> C>(self, context: F) -> Result<T, BError>;
}
impl<T> Context<T> for Result<T, BError> {
fn err_context<C: Into<ImmutStr>, F: FnOnce() -> C>(self, context: F) -> Result<T, BError> {
self.map_err(|e| e.more_context(context()))
}
}
pub trait OrErr<T, E> {
fn or_err(self, et: ErrorType, context: &'static str) -> Result<T, BError>
where
E: Into<Box<dyn ErrorTrait + Send + Sync>>;
fn or_err_with<C: Into<ImmutStr>, F: FnOnce() -> C>(
self,
et: ErrorType,
context: F,
) -> Result<T, BError>
where
E: Into<Box<dyn ErrorTrait + Send + Sync>>;
fn explain_err<C: Into<ImmutStr>, F: FnOnce(E) -> C>(
self,
et: ErrorType,
context: F,
) -> Result<T, BError>;
fn or_fail(self) -> Result<T>
where
E: Into<Box<dyn ErrorTrait + Send + Sync>>;
}
impl<T, E> OrErr<T, E> for Result<T, E> {
fn or_err(self, et: ErrorType, context: &'static str) -> Result<T, BError>
where
E: Into<Box<dyn ErrorTrait + Send + Sync>>,
{
self.map_err(|e| Error::because(et, context, e))
}
fn or_err_with<C: Into<ImmutStr>, F: FnOnce() -> C>(
self,
et: ErrorType,
context: F,
) -> Result<T, BError>
where
E: Into<Box<dyn ErrorTrait + Send + Sync>>,
{
self.map_err(|e| Error::because(et, context(), e))
}
fn explain_err<C: Into<ImmutStr>, F: FnOnce(E) -> C>(
self,
et: ErrorType,
exp: F,
) -> Result<T, BError> {
self.map_err(|e| Error::explain(et, exp(e)))
}
fn or_fail(self) -> Result<T, BError>
where
E: Into<Box<dyn ErrorTrait + Send + Sync>>,
{
self.map_err(|e| Error::because(ErrorType::InternalError, "", e))
}
}
pub trait OkOrErr<T> {
fn or_err(self, et: ErrorType, context: &'static str) -> Result<T, BError>;
fn or_err_with<C: Into<ImmutStr>, F: FnOnce() -> C>(
self,
et: ErrorType,
context: F,
) -> Result<T, BError>;
}
impl<T> OkOrErr<T> for Option<T> {
fn or_err(self, et: ErrorType, context: &'static str) -> Result<T, BError> {
self.ok_or(Error::explain(et, context))
}
fn or_err_with<C: Into<ImmutStr>, F: FnOnce() -> C>(
self,
et: ErrorType,
context: F,
) -> Result<T, BError> {
self.ok_or_else(|| Error::explain(et, context()))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_chain_of_error() {
let e1 = Error::new(ErrorType::InternalError);
let mut e2 = Error::new(ErrorType::HTTPStatus(400));
e2.set_cause(e1);
assert_eq!(format!("{}", e2), " HTTPStatus cause: InternalError");
assert_eq!(e2.root_etype().as_str(), "InternalError");
let e3 = Error::new(ErrorType::InternalError);
let e4 = Error::because(ErrorType::HTTPStatus(400), "test", e3);
assert_eq!(
format!("{}", e4),
" HTTPStatus context: test cause: InternalError"
);
assert_eq!(e4.root_etype().as_str(), "InternalError");
}
#[test]
fn test_error_context() {
let mut e1 = Error::new(ErrorType::InternalError);
e1.set_context(format!("{} {}", "my", "context"));
assert_eq!(format!("{}", e1), " InternalError context: my context");
}
#[test]
fn test_context_trait() {
let e1: Result<(), BError> = Err(Error::new(ErrorType::InternalError));
let e2 = e1.err_context(|| "another");
assert_eq!(
format!("{}", e2.unwrap_err()),
" InternalError context: another cause: "
);
}
#[test]
fn test_cause_trait() {
let e1: Result<(), BError> = Err(Error::new(ErrorType::InternalError));
let e2 = e1.or_err(ErrorType::HTTPStatus(400), "another");
assert_eq!(
format!("{}", e2.unwrap_err()),
" HTTPStatus context: another cause: InternalError"
);
}
#[test]
fn test_option_some_ok() {
let m = Some(2);
let o = m.or_err(ErrorType::InternalError, "some is not an error!");
assert_eq!(2, o.unwrap());
let o = m.or_err_with(ErrorType::InternalError, || "some is not an error!");
assert_eq!(2, o.unwrap());
}
#[test]
fn test_option_none_err() {
let m: Option<i32> = None;
let e1 = m.or_err(ErrorType::InternalError, "none is an error!");
assert_eq!(
format!("{}", e1.unwrap_err()),
" InternalError context: none is an error!"
);
let e1 = m.or_err_with(ErrorType::InternalError, || "none is an error!");
assert_eq!(
format!("{}", e1.unwrap_err()),
" InternalError context: none is an error!"
);
}
#[test]
fn test_into() {
fn other_error() -> Result<(), &'static str> {
Err("oops")
}
fn surface_err() -> Result<()> {
other_error().or_fail()?; Ok(())
}
let e = surface_err().unwrap_err();
assert_eq!(format!("{}", e), " InternalError context: cause: oops");
}
}