#[cfg(feature = "log-panic")]
#[macro_use]
extern crate log;
use std::error::Error;
use std::fmt::{self, Display};
use std::panic;
pub mod prelude {
pub use super::{
in_context_of, in_context_of_with, FailedTo, FailedToIter, OptionErrorToProblem,
OptionToProblem, Problem, ProblemWhile, ResultOptionToProblem, ResultToProblem, ToProblem,
};
}
#[derive(Debug)]
pub enum Problem {
Cause(String, Option<String>),
Context(String, Box<Problem>),
}
impl Problem {
pub fn cause(msg: impl ToString) -> Problem {
Problem::Cause(msg.to_string(), format_backtrace())
}
pub fn while_context(self, msg: impl ToString) -> Problem {
Problem::Context(msg.to_string(), Box::new(self))
}
pub fn backtrace(&self) -> Option<&str> {
match self {
&Problem::Cause(_, ref backtrace) => backtrace.as_ref().map(String::as_str),
&Problem::Context(_, ref problem) => problem.backtrace(),
}
}
}
impl Display for Problem {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
&Problem::Cause(ref msg, None) => write!(f, "{}", msg),
&Problem::Cause(ref msg, Some(ref backtrace)) => {
write!(f, "{}\n\t--- Cause\n{}", msg, backtrace)
}
&Problem::Context(ref msg, ref inner) => match inner.as_ref() {
cause @ &Problem::Cause(..) => {
write!(f, "while {} got problem caused by: {}", msg, cause)
}
inner => write!(f, "while {}, {}", msg, inner),
},
}
}
}
impl<E> From<E> for Problem
where
E: Error,
{
fn from(error: E) -> Problem {
Problem::cause(error)
}
}
pub trait ToProblem {
fn to_problem(self) -> Problem;
}
impl<T> ToProblem for T
where
T: ToString,
{
fn to_problem(self) -> Problem {
Problem::cause(self)
}
}
pub trait OptionErrorToProblem {
fn to_problem(self) -> Problem;
}
impl<E> OptionErrorToProblem for Option<E>
where
E: ToProblem,
{
fn to_problem(self) -> Problem {
self.map(ToProblem::to_problem)
.unwrap_or(Problem::cause("<unknown error>"))
}
}
pub trait ResultToProblem<O> {
fn map_problem(self) -> Result<O, Problem>;
}
impl<O, E> ResultToProblem<O> for Result<O, E>
where
E: ToProblem,
{
fn map_problem(self) -> Result<O, Problem> {
self.map_err(|e| e.to_problem())
}
}
pub trait ResultOptionToProblem<O> {
fn map_problem(self) -> Result<O, Problem>;
}
impl<O, E> ResultOptionToProblem<O> for Result<O, Option<E>>
where
E: ToProblem,
{
fn map_problem(self) -> Result<O, Problem> {
self.map_err(OptionErrorToProblem::to_problem)
}
}
pub trait OptionToProblem<O> {
fn ok_or_problem<M>(self, msg: M) -> Result<O, Problem>
where
M: ToString;
}
impl<O> OptionToProblem<O> for Option<O> {
fn ok_or_problem<M>(self, msg: M) -> Result<O, Problem>
where
M: ToString,
{
self.ok_or_else(|| Problem::cause(msg))
}
}
pub trait ProblemWhile<O> {
fn problem_while(self, msg: impl Display) -> Result<O, Problem>;
fn problem_while_with<F, M>(self, msg: F) -> Result<O, Problem>
where
F: FnOnce() -> M,
M: Display;
}
impl<O, E> ProblemWhile<O> for Result<O, E>
where
E: Into<Problem>,
{
fn problem_while(self, msg: impl Display) -> Result<O, Problem> {
self.map_err(|err| err.into().while_context(msg))
}
fn problem_while_with<F, M>(self, msg: F) -> Result<O, Problem>
where
F: FnOnce() -> M,
M: Display,
{
self.map_err(|err| err.into().while_context(msg()))
}
}
pub fn in_context_of<O, M, B>(msg: M, body: B) -> Result<O, Problem>
where
M: Display,
B: FnOnce() -> Result<O, Problem>,
{
body().problem_while(msg)
}
pub fn in_context_of_with<O, F, M, B>(msg: F, body: B) -> Result<O, Problem>
where
F: FnOnce() -> M,
M: Display,
B: FnOnce() -> Result<O, Problem>,
{
body().problem_while_with(msg)
}
pub trait FailedTo<O> {
fn or_failed_to(self, msg: impl Display) -> O;
}
impl<O, E> FailedTo<O> for Result<O, E>
where
E: Display,
{
fn or_failed_to(self, msg: impl Display) -> O {
match self {
Err(err) => panic!("Failed to {} due to: {}", msg, err),
Ok(ok) => ok,
}
}
}
impl<O> FailedTo<O> for Option<O> {
fn or_failed_to(self, msg: impl Display) -> O {
match self {
None => panic!("Failed to {}", msg),
Some(ok) => ok,
}
}
}
pub struct ProblemIter<I> {
inner: I,
message: String,
}
impl<I, O, E> Iterator for ProblemIter<I>
where
I: Iterator<Item = Result<O, E>>,
E: Display,
{
type Item = O;
fn next(&mut self) -> Option<Self::Item> {
self.inner
.next()
.map(|res| res.or_failed_to(self.message.as_str()))
}
}
pub trait FailedToIter<O, E>: Sized {
fn or_failed_to(self, msg: impl ToString) -> ProblemIter<Self>;
}
impl<I, O, E> FailedToIter<O, E> for I
where
I: Iterator<Item = Result<O, E>>,
E: Display,
{
fn or_failed_to(self, msg: impl ToString) -> ProblemIter<Self> {
ProblemIter {
inner: self,
message: msg.to_string(),
}
}
}
#[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();
backtrace::trace(|frame| {
let ip = frame.ip();
backtrace.push_str("\tat ");
backtrace::resolve(ip, |symbol| {
if let Some(name) = symbol.name() {
backtrace.push_str(&name.to_string());
}
backtrace.push_str("(");
if let Some(filename) = symbol.filename() {
backtrace.push_str(&filename.display().to_string());
}
if let Some(lineno) = symbol.lineno() {
backtrace.push_str(":");
backtrace.push_str(&lineno.to_string());
}
backtrace.push_str(")");
});
backtrace.push_str("\n");
true });
backtrace.pop();
Some(backtrace)
} else {
None
}
}
fn format_panic(panic: &std::panic::PanicInfo, backtrace: Option<String>) -> String {
let mut message = String::new();
if let Some(location) = panic.location() {
message.push_str(&format!(
"Panicked in {}:{}:{}: ",
location.file(),
location.line(),
location.column()
));
};
if let Some(value) = panic.payload().downcast_ref::<String>() {
message.push_str(&value);
} else if let Some(value) = panic.payload().downcast_ref::<&str>() {
message.push_str(value);
} else if let Some(value) = panic.payload().downcast_ref::<&Error>() {
message.push_str(&format!("{} ({:?})", value, value));
} else {
message.push_str(&format!("{:?}", panic));
};
if let Some(backtrace) = backtrace {
message.push_str("\n\t--- 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-panic")]
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::*;
use std::io;
#[test]
#[should_panic(
expected = "Failed to complete processing task due to: while processing object, while processing input data, while parsing input got problem 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 doing stuff got problem 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 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_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_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]
#[cfg(feature = "backtrace")]
fn test_problem_backtrace() {
let p = Problem::cause("foo")
.while_context("bar")
.while_context("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());
}
}
}