#[cfg(feature = "log")]
#[macro_use]
extern crate log;
use std::fmt::{self, Display, Write};
use std::panic;
const DEFAULT_FATAL_STATUS: i32 = 1;
pub mod prelude {
pub use super::{
in_context_of, in_context_of_with, problem, FailedTo, FailedToIter, Fatal, FatalProblem,
IntoError, MapProblem, MapProblemOr, OkOrProblem, Problem, ProblemWhile,
};
pub use super::result::FinalResult;
#[cfg(feature = "log")]
pub use super::logged::{OkOrLog, OkOrLogIter};
}
#[cfg(feature = "send-sync")]
type DynError = dyn std::error::Error + Send + Sync;
#[cfg(not(feature = "send-sync"))]
type DynError = dyn std::error::Error;
#[derive(Debug)]
pub struct Problem {
error: Box<DynError>,
context: Vec<String>,
backtrace: Option<String>,
}
impl Problem {
pub fn from_error(error: impl Into<Box<DynError>>) -> Problem {
Problem {
error: error.into(),
context: Vec::new(),
backtrace: format_backtrace(),
}
}
pub fn from_error_message(error: &impl std::error::Error) -> Problem {
let mut message = String::new();
write_error_message(error, &mut message).unwrap();
Problem {
error: message.into(),
context: Vec::new(),
backtrace: format_backtrace(),
}
}
pub fn backtrace(&self) -> Option<&str> {
self.backtrace.as_ref().map(String::as_str)
}
pub fn into_error(self) -> Error {
Error(self)
}
}
#[allow(deprecated)]
fn write_error_message(error: &dyn std::error::Error, w: &mut impl Write) -> fmt::Result {
write!(w, "{}", error)?;
let mut error_cause = error;
loop {
if let Some(cause) = error_cause.cause() {
write!(w, "; caused by: {}", cause)?;
error_cause = cause;
} else {
break;
}
}
Ok(())
}
impl Display for Problem {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if let Some(context) = self.context.last() {
write!(f, "while {}", context)?;
}
for context in self.context.iter().rev().skip(1) {
write!(f, ", while {}", context)?;
}
if !self.context.is_empty() {
write!(f, " got error caused by: ")?;
}
write_error_message(self.error.as_ref(), f)?;
if let Some(backtrace) = self.backtrace.as_ref() {
write!(f, "\n--- Cause\n{}", backtrace)?;
}
Ok(())
}
}
impl<E> From<E> for Problem
where
E: Into<Box<DynError>>,
{
fn from(error: E) -> Problem {
Problem::from_error(error)
}
}
#[macro_export]
macro_rules! problem {
($ ($ arg : tt) *) => { Err(Problem::from_error(format!($($arg)*))) };
}
pub struct FatalProblem {
status: i32,
problem: Problem,
}
impl From<Problem> for FatalProblem {
fn from(problem: Problem) -> FatalProblem {
FatalProblem {
status: DEFAULT_FATAL_STATUS,
problem,
}
}
}
impl<E> From<E> for FatalProblem
where
E: Into<Box<DynError>>,
{
fn from(error: E) -> FatalProblem {
FatalProblem {
status: DEFAULT_FATAL_STATUS,
problem: Problem::from_error(error),
}
}
}
impl fmt::Debug for FatalProblem {
fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result {
eprintln!("{}", self.problem);
std::process::exit(self.status)
}
}
pub trait Fatal<O> {
fn fatal(self) -> Result<O, FatalProblem>;
fn fatal_with_status(self, status: i32) -> Result<O, FatalProblem>;
}
impl<O> Fatal<O> for Result<O, Problem> {
fn fatal(self) -> Result<O, FatalProblem> {
self.fatal_with_status(DEFAULT_FATAL_STATUS)
}
fn fatal_with_status(self, status: i32) -> Result<O, FatalProblem> {
self.map_err(|problem| FatalProblem { status, problem })
}
}
pub mod result {
use super::{FatalProblem, Problem};
pub type Result<T> = std::result::Result<T, Problem>;
pub type FinalResult = std::result::Result<(), FatalProblem>;
}
pub trait MapProblem {
type ProblemCarrier;
fn map_problem(self) -> Self::ProblemCarrier;
}
impl<O, E> MapProblem for Result<O, E>
where
E: Into<Problem>,
{
type ProblemCarrier = Result<O, Problem>;
fn map_problem(self) -> Result<O, Problem> {
self.map_err(|e| e.into())
}
}
pub trait MapProblemOr {
type ProblemCarrier;
fn map_problem_or(self, problem: impl Into<Problem>) -> Self::ProblemCarrier;
fn map_problem_or_else<F, P>(self, problem: F) -> Self::ProblemCarrier
where
F: FnOnce() -> P,
P: Into<Problem>;
}
impl<O, E> MapProblemOr for Result<O, Option<E>>
where
E: Into<Problem>,
{
type ProblemCarrier = Result<O, Problem>;
fn map_problem_or(self, problem: impl Into<Problem>) -> Result<O, Problem> {
self.map_err(|e| e.map(Into::into).unwrap_or_else(|| problem.into()))
}
fn map_problem_or_else<F, P>(self, problem: F) -> Self::ProblemCarrier
where
F: FnOnce() -> P,
P: Into<Problem>,
{
self.map_err(|e| e.map(Into::into).unwrap_or_else(|| problem().into()))
}
}
pub trait OkOrProblem<O> {
fn ok_or_problem<P>(self, problem: P) -> Result<O, Problem>
where
P: Into<Problem>;
fn ok_or_problem_with<F, P>(self, problem: F) -> Result<O, Problem>
where
F: FnOnce() -> P,
P: Into<Problem>;
}
impl<O> OkOrProblem<O> for Option<O> {
fn ok_or_problem<P>(self, problem: P) -> Result<O, Problem>
where
P: Into<Problem>,
{
self.ok_or_else(|| problem.into())
}
fn ok_or_problem_with<F, P>(self, problem: F) -> Result<O, Problem>
where
F: FnOnce() -> P,
P: Into<Problem>,
{
self.ok_or_else(|| problem().into())
}
}
pub trait ProblemWhile {
type WithContext;
fn problem_while(self, message: impl ToString) -> Self::WithContext;
fn problem_while_with<F, M>(self, message: F) -> Self::WithContext
where
F: FnOnce() -> M,
M: ToString;
}
impl ProblemWhile for Problem {
type WithContext = Problem;
fn problem_while(mut self, message: impl ToString) -> Problem {
self.context.push(message.to_string());
self
}
fn problem_while_with<F, M>(self, message: F) -> Problem
where
F: FnOnce() -> M,
M: ToString,
{
self.problem_while(message())
}
}
impl<O, E> ProblemWhile for Result<O, E>
where
E: Into<Problem>,
{
type WithContext = Result<O, Problem>;
fn problem_while(self, message: impl ToString) -> Result<O, Problem> {
self.map_err(|err| err.into().problem_while(message))
}
fn problem_while_with<F, M>(self, message: F) -> Result<O, Problem>
where
F: FnOnce() -> M,
M: ToString,
{
self.map_err(|err| err.into().problem_while_with(message))
}
}
pub fn in_context_of<O, B>(message: &str, body: B) -> Result<O, Problem>
where
B: FnOnce() -> Result<O, Problem>,
{
body().problem_while(message)
}
pub fn in_context_of_with<O, F, M, B>(message: F, body: B) -> Result<O, Problem>
where
F: FnOnce() -> M,
M: ToString,
B: FnOnce() -> Result<O, Problem>,
{
body().problem_while_with(message)
}
#[derive(Debug)]
pub struct Error(Problem);
impl Error {
pub fn unwrap(self) -> Problem {
self.0
}
}
impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
impl std::error::Error for Error {}
pub trait IntoError {
type ErrorResult<E>;
fn into_error(self) -> Self::ErrorResult<Error>;
}
impl<O, E> IntoError for Result<O, E>
where
E: Into<Problem>,
{
type ErrorResult<EE> = Result<O, EE>;
fn into_error(self) -> Self::ErrorResult<Error> {
self.map_err(|err| Error(err.into()))
}
}
pub trait FailedTo<O> {
fn or_failed_to(self, message: impl Display) -> O;
}
impl<O, E> FailedTo<O> for Result<O, E>
where
E: Into<Problem>,
{
fn or_failed_to(self, message: impl Display) -> O {
self.unwrap_or_else(|err| panic!("Failed to {} due to: {}", message, err.into()))
}
}
impl<O> FailedTo<O> for Option<O> {
fn or_failed_to(self, message: impl Display) -> O {
self.unwrap_or_else(|| panic!("Failed to {}", message))
}
}
pub struct ProblemIter<I, M> {
inner: I,
message: M,
}
impl<I, O, E, M> Iterator for ProblemIter<I, M>
where
I: Iterator<Item = Result<O, E>>,
E: Into<Problem>,
M: Display,
{
type Item = O;
fn next(&mut self) -> Option<Self::Item> {
self.inner.next().map(|res| res.or_failed_to(&self.message))
}
}
pub trait FailedToIter<O, E, M>: Sized {
fn or_failed_to(self, message: M) -> ProblemIter<Self, M>;
}
impl<I, O, E, M> FailedToIter<O, E, M> for I
where
I: Iterator<Item = Result<O, E>>,
E: Into<Problem>,
M: Display,
{
fn or_failed_to(self, message: M) -> ProblemIter<Self, M> {
ProblemIter {
inner: self,
message,
}
}
}
#[cfg(feature = "log")]
pub mod logged {
use super::*;
use log::{error, warn};
pub trait OkOrLog<O> {
fn ok_or_log_warn(self) -> Option<O>;
fn ok_or_log_error(self) -> Option<O>;
}
impl<O, E> OkOrLog<O> for Result<O, E>
where
E: Into<Problem>,
{
fn ok_or_log_warn(self) -> Option<O> {
self.map_err(|err| warn!("Continuing with error: {}", err.into()))
.ok()
}
fn ok_or_log_error(self) -> Option<O> {
self.map_err(|err| error!("Continuing with error: {}", err.into()))
.ok()
}
}
pub struct ProblemWarnLoggingIter<I> {
inner: I,
}
impl<I, O, E> Iterator for ProblemWarnLoggingIter<I>
where
I: Iterator<Item = Result<O, E>>,
E: Into<Problem>,
{
type Item = Option<O>;
fn next(&mut self) -> Option<Self::Item> {
self.inner.next().map(|res| res.ok_or_log_warn())
}
}
pub struct ProblemErrorLoggingIter<I> {
inner: I,
}
impl<I, O, E> Iterator for ProblemErrorLoggingIter<I>
where
I: Iterator<Item = Result<O, E>>,
E: Into<Problem>,
{
type Item = Option<O>;
fn next(&mut self) -> Option<Self::Item> {
self.inner.next().map(|res| res.ok_or_log_error())
}
}
pub trait OkOrLogIter<O, E>: Sized {
fn ok_or_log_warn(self) -> ProblemWarnLoggingIter<Self>;
fn ok_or_log_error(self) -> ProblemErrorLoggingIter<Self>;
}
impl<I, O, E> OkOrLogIter<O, E> for I
where
I: Iterator<Item = Result<O, E>>,
E: Into<Problem>,
{
fn ok_or_log_warn(self) -> ProblemWarnLoggingIter<Self> {
ProblemWarnLoggingIter { inner: self }
}
fn ok_or_log_error(self) -> ProblemErrorLoggingIter<Self> {
ProblemErrorLoggingIter { inner: self }
}
}
}
#[cfg(not(feature = "backtrace"))]
fn format_backtrace() -> Option<String> {
None
}
#[cfg(feature = "backtrace")]
#[inline(always)]
fn format_backtrace() -> Option<String> {
if let Ok("1") = std::env::var("RUST_BACKTRACE").as_ref().map(String::as_str) {
let mut backtrace = String::new();
let mut frame_no: u32 = 0;
backtrace::trace(|frame| {
let ip = frame.ip();
if frame_no > 0 {
backtrace.push_str("\n");
}
backtrace::resolve(ip, |symbol| {
if let Some(name) = symbol.name() {
write!(backtrace, "{:4}: {}", frame_no, name).unwrap();
}
if let (Some(filename), Some(lineno)) = (symbol.filename(), symbol.lineno()) {
write!(
backtrace,
"\n at {}:{}",
filename.display(),
lineno
)
.unwrap();
}
});
frame_no += 1;
true });
Some(backtrace)
} else {
None
}
}
fn format_panic(panic: &std::panic::PanicHookInfo, backtrace: Option<String>) -> String {
let mut message = String::new();
let thread = std::thread::current();
let name = thread.name().unwrap_or("<unnamed>");
let msg = match panic.payload().downcast_ref::<&'static str>() {
Some(s) => *s,
None => match panic.payload().downcast_ref::<String>() {
Some(s) => &s[..],
None => "Box<Any>",
},
};
match (backtrace.is_some(), panic.location()) {
(true, Some(location)) => write!(
message,
"thread '{}' panicked at {} with: {}",
name, location, msg
)
.ok(),
(true, None) => write!(message, "thread '{}' panicked with: {}", name, msg).ok(),
(false, _) => write!(message, "{}", msg).ok(),
};
if let Some(backtrace) = backtrace {
message.push_str("\n--- Panicked\n");
message.push_str(&backtrace);
}
message
}
pub fn format_panic_to_stderr() {
panic::set_hook(Box::new(|panic_info| {
let backtrace = format_backtrace();
eprintln!("Fatal error: {}", format_panic(panic_info, backtrace));
}));
}
#[cfg(feature = "log")]
pub fn format_panic_to_error_log() {
panic::set_hook(Box::new(|panic_info| {
let backtrace = format_backtrace();
error!("{}", format_panic(panic_info, backtrace));
}));
}
#[cfg(test)]
mod tests {
use super::prelude::*;
use super::{format_panic_to_stderr, in_context_of};
use std::error::Error;
use std::fmt::{self, Display};
use std::io;
#[derive(Debug)]
struct Foo;
impl Display for Foo {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Foo error")
}
}
impl Error for Foo {}
#[derive(Debug)]
struct Bar(Foo);
impl Display for Bar {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Bar error")
}
}
impl Error for Bar {
fn source(&self) -> Option<&(dyn Error + 'static)> {
Some(&self.0)
}
}
#[derive(Debug)]
struct Baz(Bar);
impl Display for Baz {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Baz error")
}
}
impl Error for Baz {
fn source(&self) -> Option<&(dyn Error + 'static)> {
Some(&self.0)
}
}
#[test]
#[should_panic(expected = "Failed to test due to: foo: 1")]
fn test_problem_macro() {
let err: Result<(), Problem> = problem!("foo: {}", 1);
err.or_failed_to("test");
}
#[test]
fn test_convertion() {
let _: Problem = io::Error::new(io::ErrorKind::InvalidInput, "boom!").into();
let _: Problem = "boom!".into(); }
#[test]
#[should_panic(
expected = "Failed to complete processing task due to: while processing object, while processing input data, while parsing input got error caused by: boom!"
)]
fn test_integration() {
Err(io::Error::new(io::ErrorKind::InvalidInput, "boom!"))
.problem_while("parsing input")
.problem_while("processing input data")
.problem_while("processing object")
.or_failed_to("complete processing task")
}
#[test]
#[should_panic(
expected = "Failed to complete processing task due to: while processing object, while processing input data, while parsing input got error caused by: boom!"
)]
fn test_integration_message() {
Err("boom!")
.problem_while("parsing input")
.problem_while("processing input data")
.problem_while("processing object")
.or_failed_to("complete processing task")
}
#[test]
#[should_panic(
expected = "Failed to complete processing task due to: while processing object, while processing input data, while parsing input got error caused by: Baz error; caused by: Bar error; caused by: Foo error"
)]
fn test_integration_cause_chain() {
Err(Baz(Bar(Foo)))
.problem_while("parsing input")
.problem_while("processing input data")
.problem_while("processing object")
.or_failed_to("complete processing task")
}
#[test]
#[should_panic(
expected = "Failed to complete processing task due to: while doing stuff got error caused by: boom!"
)]
fn test_in_context_of() {
in_context_of("doing stuff", || {
Err(io::Error::new(io::ErrorKind::InvalidInput, "boom!"))?
})
.or_failed_to("complete processing task")
}
#[test]
#[should_panic(expected = "Failed to foo due to: boom!")]
fn test_result() {
Err(io::Error::new(io::ErrorKind::InvalidInput, "boom!")).or_failed_to("foo")
}
#[test]
#[should_panic(
expected = "Failed to quix due to: Baz error; caused by: Bar error; caused by: Foo error"
)]
fn test_result_cause_chain() {
Err(Baz(Bar(Foo))).or_failed_to("quix")
}
#[test]
#[should_panic(
expected = "Failed to quix due to: Baz error; caused by: Bar error; caused by: Foo error"
)]
fn test_result_cause_chain_message() {
let error = Baz(Bar(Foo));
Err(Problem::from_error_message(&error)).or_failed_to("quix")
}
#[test]
#[should_panic(expected = "Failed to foo")]
fn test_option() {
None.or_failed_to("foo")
}
#[test]
#[should_panic(expected = "Failed to foo due to: boom!")]
fn test_option_errors() {
Err(Some(io::Error::new(io::ErrorKind::InvalidInput, "boom!")))
.map_problem_or("<unknown error>")
.or_failed_to("foo")
}
#[test]
#[should_panic(expected = "Failed to foo due to: <unknown error>")]
fn test_result_option_errors_unknown() {
let err: Result<(), Option<io::Error>> = Err(None);
err.map_problem_or("<unknown error>").or_failed_to("foo")
}
#[test]
#[should_panic(expected = "Failed to foo due to: nothing here")]
fn test_result_ok_or_problem() {
None.ok_or_problem("nothing here").or_failed_to("foo")
}
#[test]
#[should_panic(expected = "Failed to foo due to: omg!")]
fn test_result_iter_or_failed_to() {
let results = vec![Ok(1u32), Ok(2u32), Err("omg!")];
let _ok = results.into_iter().or_failed_to("foo").collect::<Vec<_>>();
}
#[test]
#[should_panic]
fn test_panic_format_stderr() {
format_panic_to_stderr();
panic!("foo bar!");
}
#[test]
#[should_panic]
fn test_panic_format_stderr_problem() {
format_panic_to_stderr();
let result: Result<(), Problem> = Err(io::Error::new(io::ErrorKind::InvalidInput, "boom!"))
.problem_while("parsing input")
.problem_while("processing input data")
.problem_while("processing object");
result.or_failed_to("complete processing task");
}
#[test]
#[should_panic]
fn test_panic_format_stderr_unwrap() {
format_panic_to_stderr();
let result: Result<(), io::Error> =
Err(io::Error::new(io::ErrorKind::InvalidInput, "boom!"));
result.unwrap();
}
#[test]
#[should_panic]
fn test_panic_format_stderr_expect() {
format_panic_to_stderr();
let result: Result<(), io::Error> =
Err(io::Error::new(io::ErrorKind::InvalidInput, "boom!"));
result.expect("foo");
}
#[test]
#[cfg(feature = "backtrace")]
fn test_problem_backtrace() {
let p = Problem::from_error("foo")
.problem_while("bar")
.problem_while("baz");
if let Ok("1") = std::env::var("RUST_BACKTRACE").as_ref().map(String::as_str) {
assert!(p.backtrace().is_some());
println!("{}", p.backtrace().unwrap());
} else {
assert!(p.backtrace().is_none());
}
}
#[test]
#[cfg(feature = "log")]
fn test_problem_log_error() {
loggerv::init_quiet().ok();
let error: Result<(), _> = Err(Baz(Bar(Foo)));
error.ok_or_log_error();
}
#[test]
#[cfg(feature = "log")]
fn test_problem_log_warn() {
loggerv::init_quiet().ok();
let error: Result<(), _> = Err(Baz(Bar(Foo)));
error.ok_or_log_warn();
}
#[test]
#[cfg(feature = "log")]
fn test_problem_log_iter_error() {
loggerv::init_quiet().ok();
assert_eq!(
vec![Ok(1), Err(Foo), Err(Foo), Ok(2), Err(Foo), Ok(3)]
.into_iter()
.ok_or_log_error()
.flatten()
.collect::<Vec<_>>(),
vec![1, 2, 3]
);
}
#[test]
#[cfg(feature = "log")]
fn test_problem_log_iter_warn() {
loggerv::init_quiet().ok();
assert_eq!(
vec![Ok(1), Err(Foo), Err(Foo), Ok(2), Err(Foo), Ok(3)]
.into_iter()
.ok_or_log_warn()
.flatten()
.collect::<Vec<_>>(),
vec![1, 2, 3]
);
}
#[test]
#[cfg(feature = "send-sync")]
fn test_problem_send_sync() {
fn foo<S: Send + Sync>(_s: S) {}
foo(Problem::from("foo"));
assert!(true)
}
}