#![allow(non_snake_case)]
use std::error::Error as StdError;
use std::fmt;
#[derive(Debug)]
pub struct RiErrorChain {
source: Box<dyn StdError + Send + Sync>,
context: String,
previous: Option<Box<RiErrorChain>>,
}
impl RiErrorChain {
pub fn new<E>(source: E) -> Self
where
E: StdError + Send + Sync + 'static,
{
Self {
source: Box::new(source),
context: String::new(),
previous: None,
}
}
pub fn with_context<E, S>(source: E, context: S) -> Self
where
E: StdError + Send + Sync + 'static,
S: Into<String>,
{
Self {
source: Box::new(source),
context: context.into(),
previous: None,
}
}
pub fn context<S>(mut self, context: S) -> Self
where
S: Into<String>,
{
let source = std::mem::replace(&mut self.source, Box::new(std::io::Error::other("error_chain_internal")));
Self {
source,
context: context.into(),
previous: Some(Box::new(self)),
}
}
pub fn source_error(&self) -> &(dyn StdError + Send + Sync) {
&*self.source
}
pub fn get_context(&self) -> &str {
&self.context
}
pub fn previous(&self) -> Option<&RiErrorChain> {
self.previous.as_deref()
}
pub fn chain(&self) -> RiErrorChainIter<'_> {
RiErrorChainIter { current: Some(self) }
}
pub fn contains<E>(&self) -> bool
where
E: StdError + Send + Sync + 'static,
{
if self.source.is::<E>() {
return true;
}
let mut current = self.previous.as_deref();
while let Some(chain) = current {
if chain.source.is::<E>() {
return true;
}
current = chain.previous.as_deref();
}
false
}
pub fn root_cause(&self) -> &(dyn StdError + Send + Sync) {
let mut current = self;
while let Some(prev) = ¤t.previous {
current = prev;
}
current.source_error()
}
pub fn pretty_format(&self) -> String {
let mut result = String::new();
if !self.context.is_empty() {
result.push_str(&format!("Error: {}\n", self.context));
}
result.push_str(&format!("Source: {}\n", self.source_error()));
let mut level = 1;
let mut current = self.previous.as_deref();
while let Some(chain) = current {
result.push_str(&format!("\nCaused by (level {level}):\n"));
if !chain.context.is_empty() {
result.push_str(&format!(" {}\n", chain.get_context()));
}
result.push_str(&format!(" {}\n", chain.source_error()));
level += 1;
current = chain.previous.as_deref();
}
result
}
}
impl StdError for RiErrorChain {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
Some(&*self.source)
}
}
impl fmt::Display for RiErrorChain {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if !self.context.is_empty() {
write!(f, "{}", self.context)?;
if self.previous.is_some() {
write!(f, ": ")?;
}
}
if let Some(prev) = &self.previous {
write!(f, "{prev}")?;
} else {
write!(f, "{}", self.source_error())?;
}
Ok(())
}
}
pub struct RiErrorChainIter<'a> {
current: Option<&'a RiErrorChain>,
}
impl<'a> Iterator for RiErrorChainIter<'a> {
type Item = &'a RiErrorChain;
fn next(&mut self) -> Option<Self::Item> {
let current = self.current?;
self.current = current.previous.as_deref();
Some(current)
}
}
pub trait RiErrorContext<T, E> {
fn chain_context<S>(self, context: S) -> Result<T, RiErrorChain>
where
S: Into<String>;
fn with_chain_context<S, F>(self, f: F) -> Result<T, RiErrorChain>
where
S: Into<String>,
F: FnOnce() -> S;
}
impl<T, E> RiErrorContext<T, E> for Result<T, E>
where
E: StdError + Send + Sync + 'static,
{
fn chain_context<S>(self, context: S) -> Result<T, RiErrorChain>
where
S: Into<String>,
{
self.map_err(|e| RiErrorChain::with_context(e, context))
}
fn with_chain_context<S, F>(self, f: F) -> Result<T, RiErrorChain>
where
S: Into<String>,
F: FnOnce() -> S,
{
self.map_err(|e| RiErrorChain::with_context(e, f()))
}
}
pub trait RiOptionErrorContext<T> {
fn ok_or_chain<E>(self, err: E) -> Result<T, RiErrorChain>
where
E: StdError + Send + Sync + 'static;
fn ok_or_else_chain<E, F>(self, f: F) -> Result<T, RiErrorChain>
where
E: StdError + Send + Sync + 'static,
F: FnOnce() -> E;
}
impl<T> RiOptionErrorContext<T> for Option<T> {
fn ok_or_chain<E>(self, err: E) -> Result<T, RiErrorChain>
where
E: StdError + Send + Sync + 'static,
{
self.ok_or_else(|| RiErrorChain::new(err))
}
fn ok_or_else_chain<E, F>(self, f: F) -> Result<T, RiErrorChain>
where
E: StdError + Send + Sync + 'static,
F: FnOnce() -> E,
{
self.ok_or_else(|| RiErrorChain::new(f()))
}
}
pub mod utils {
use super::*;
pub fn chain_from_msg<S>(msg: S) -> RiErrorChain
where
S: Into<String>,
{
RiErrorChain::new(std::io::Error::other(msg.into()))
}
pub fn chain_if<E, F, S>(err: E, predicate: F, context: S) -> RiErrorChain
where
E: StdError + Send + Sync + 'static,
F: FnOnce(&E) -> bool,
S: Into<String>,
{
if predicate(&err) {
RiErrorChain::with_context(err, context)
} else {
RiErrorChain::new(err)
}
}
pub fn chain_from_multiple<S>(errors: Vec<Box<dyn StdError + Send + Sync>>, context: S) -> RiErrorChain
where
S: Into<String>,
{
if errors.is_empty() {
return chain_from_msg("No errors provided");
}
if errors.len() == 1 {
let error = errors.into_iter().next()
.ok_or_else(|| std::io::Error::other("errors vector should have at least one element"))
.unwrap_or_else(|_| Box::new(std::io::Error::other("errors vector should have at least one element")));
return RiErrorChain::with_context(MultiError { errors: vec![error] }, context);
}
let combined = MultiError { errors };
RiErrorChain::with_context(combined, context)
}
#[derive(Debug)]
struct MultiError {
errors: Vec<Box<dyn StdError + Send + Sync>>,
}
impl fmt::Display for MultiError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "Multiple errors occurred ({} total):", self.errors.len())?;
for (i, err) in self.errors.iter().enumerate() {
writeln!(f, " [{}] {}", i + 1, err)?;
}
Ok(())
}
}
impl StdError for MultiError {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
self.errors.first().map(|e| &**e as &(dyn StdError + 'static))
}
}
}