[][src]Crate problem

This crate introduces Problem type which can be used on high level APIs (e.g. in command line programs or prototypes) for which error handling boils down to:

  • reporting error message (e.g. log with error! macro),
  • aborting program on error other than a bug (e.g. using panic! macro),
  • bubbling up errors (e.g. with ?),
  • ignoring errors (e.g. using Result::ok).

Usage

The core type of this crate is Problem. It is used to capture error, backtrace (if enabled) and any additional context information and present it in user friendly way via Display implementation.

This library also provides many additional extension traits and some functions that make it easy to construct Problem type in different situations as well as report or abort programs on error. It is recommended to import all the types and traits via perlude module: use problem::prelude::*.

Problem stores error cause information as Box<dyn Error> to dealy construction of error message to when it is actually needed. Additionally Problem can also store backtrace String (if enabled) and a chain of additional context messages as Vec<String>.

In order to support conversion from arbitary types implementing Error trait, Problem does not implement this trait.

Creating Problem

There are multiple ways to crate Problem value.

Using constructor

Using Problem::from_error(error) if error implements Error trait (via Into<Box<dyn Error>>). Note that String and &str implement Into<Box<dyn Error>> so Problem can be constructed directly from strings as well.

use problem::prelude::*;
use std::io;

Problem::from_error(io::Error::new(io::ErrorKind::InvalidInput, "boom!"));
Problem::from_error(Box::new(io::Error::new(io::ErrorKind::InvalidInput, "boom!")));
Problem::from_error("boom!");

Use Problem::from_error_message(error) if you don't want to give up ownership of error or only want to keep final message string in memory.

use problem::prelude::*;
use std::io;

let error = io::Error::new(io::ErrorKind::InvalidInput, "boom!");
let problem = Problem::from_error_message(&error);

drop(error);
drop(problem);

Using macro

Using problem! macro an Err variant of Result containing Problem with formatted message can be constructed.

use problem::prelude::*;
use std::io;

fn foo() -> Result<(), Problem> {
    problem!("Can't count to {}", 4)
}

assert_eq!(foo().unwrap_err().to_string(), "Can't count to 4");

Implicitly

Types implementing Into<Box<dyn Error>> trait can be converted to Problem via From trait. ? will automatically convert types implementing Error to Problem.

use problem::prelude::*;

fn foo() -> Result<String, Problem> {
    let str = String::from_utf8(vec![0, 123, 255])?;
    Ok(str)
}

assert_eq!(foo().unwrap_err().to_string(), "invalid utf-8 sequence of 1 bytes from index 2");

If Error::cause or Error::source is available, the error message from cause chain will be displayed.

use problem::prelude::*;
use std::fmt;
use std::error::Error;

#[derive(Debug)]
struct ErrorWithCause(std::string::FromUtf8Error);

impl fmt::Display for ErrorWithCause {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "bad things happened")
    }
}

impl Error for ErrorWithCause {
    fn cause(&self) -> Option<&dyn Error> {
        Some(&self.0)
    }
}

fn foo() -> Result<String, Problem> {
    let str = String::from_utf8(vec![0, 123, 255]).map_err(ErrorWithCause)?;
    Ok(str)
}

assert_eq!(foo().unwrap_err().to_string(), "bad things happened; caused by: invalid utf-8 sequence of 1 bytes from index 2");

By explicitly mapping Result

Result<T, E> can be mapped into Result<T, Problem> with .map_problem() function.

use problem::prelude::*;

let res: Result<(), &'static str> = Err("oops");
let problem: Result<(), Problem> = res.map_problem();

assert_eq!(problem.unwrap_err().to_string(), "oops");

By conversion of Option into Result

Option<T> can be converted into Result<T, Problem> with .ok_or_problem(problem) function where problem implements Into<Problem>.

use problem::prelude::*;

let opt: Option<()> = None;
let problem: Result<(), Problem> = opt.ok_or_problem("oops");

assert_eq!(problem.unwrap_err().to_string(), "oops");

From Result with Err containing Option

.map_problem_or(problem) method is implemented for Result<O, Option<E>> and will map to Result<O, Problem> with provided problem for Err(None) variant. This may be usefult when working with FFI.

use problem::prelude::*;

let unknown: Result<(), Option<&'static str>> = Err(None);
let known: Result<(), Option<&'static str>> = Err(Some("oops"));

assert_eq!(unknown.map_problem_or("unknown error").unwrap_err().to_string(), "unknown error");
assert_eq!(known.map_problem_or("unknown error").unwrap_err().to_string(), "oops");

There is also .map_problem_or_else(problem_function) variant provided that can be used to defer construction of error to error path.

use problem::prelude::*;

let unknown: Result<(), Option<&'static str>> = Err(None);
let known: Result<(), Option<&'static str>> = Err(Some("oops"));

assert_eq!(unknown.map_problem_or_else(|| "unknown error").unwrap_err().to_string(), "unknown error");
assert_eq!(known.map_problem_or_else(|| "unknown error").unwrap_err().to_string(), "oops");

Adding context to Problem

A context information that provides clues on which good path has been taken that led to error can be added to Problem object.

Adding context is a good way to convert other error types to Problem as well as providing extra information on where that happened.

On Result error

Method .problem_while(message) can be called on any Result value that error type can be converted to Problem to add context message (via Dispaly trait).

use problem::prelude::*;

let res = String::from_utf8(vec![0, 123, 255]);

assert_eq!(res.problem_while("creating string").unwrap_err().to_string(), "while creating string got error caused by: invalid utf-8 sequence of 1 bytes from index 2");

There is also .problem_while_with(message_function) variant provided that can be used to defer construction of context message to error path.

use problem::prelude::*;

let res = String::from_utf8(vec![0, 123, 255]);

assert_eq!(res.problem_while_with(|| "creating string").unwrap_err().to_string(), "while creating string got error caused by: invalid utf-8 sequence of 1 bytes from index 2");

Using scope and ?

Function in_context_of(message, closure) can be used to wrap block of code in a closure that returns Result.

This is useful when you want to add context to any error that can happen in the block of code and use ? operator. The return type of the closure needs to be Result<T, Problem> but ? operator can convert to Problem automatically.

use problem::prelude::*;

let res = in_context_of("processing string", || {
    let _s = String::from_utf8(vec![0, 123, 255])?;
    // do some processing of _s
    Ok(())
});

assert_eq!(res.unwrap_err().to_string(), "while processing string got error caused by: invalid utf-8 sequence of 1 bytes from index 2");

There is also in_context_of_with(message_function, closure) variant provided that can be used to defer construction of context message to error path.

use problem::prelude::*;

let res = in_context_of_with(|| "processing string", || {
    let _s = String::from_utf8(vec![0, 123, 255])?;
    // do some processing of _s
    Ok(())
});

assert_eq!(res.unwrap_err().to_string(), "while processing string got error caused by: invalid utf-8 sequence of 1 bytes from index 2");

Nested context

Context methods can be used multiple times to add layers of context.

use problem::prelude::*;

fn foo() -> Result<String, Problem> {
    let str = String::from_utf8(vec![0, 123, 255])?;
    Ok(str)
}

let res = in_context_of("doing stuff", || {
    let _s = foo().problem_while("running foo")?;
    // do some processing of _s
    Ok(())
});

assert_eq!(res.unwrap_err().to_string(), "while doing stuff, while running foo got error caused by: invalid utf-8 sequence of 1 bytes from index 2");

Aborting program on Problem

panic!(message, problem) macro can be used directly to abort program execution but error message printed on the screen will be formatted with Debug implementation.

This library provides function format_panic_to_stderr() to set up hook that will use eprintln!("{}", message) to report panics. Alternatively if log feature is enabled (default), function format_panic_to_error_log() will set up hook that will log with error!("{}", message) to report panics.

Panic hooks will produce backtrace of panic site if enabled via RUST_BACKTRACE=1 environment variable along of the Problem object backtrace collected at object construction site.

ERROR: Panicked in libcore/slice/mod.rs:2334:5: index 18492 out of range for slice of length 512

Panicking on Result with Problem

Similarly to .expect(message), method .or_failed_to(message) can be used to abort the program via panic!() in case of Err variant with Display formatted message and error converted to Problem to format the error message with Display trait.

use problem::prelude::*;
use problem::format_panic_to_stderr;

format_panic_to_stderr();

// Prints message:
let _s = String::from_utf8(vec![0, 123, 255]).or_failed_to("convert string"); // Failed to convert string due to: invalid utf-8 sequence of 1 bytes from index 2

Panicking on Option

Similarly to .ok_or(error), method .or_failed_to(message) can be used to abort the program via panic!() with Display formatted message on None variant of Option type.

use problem::prelude::*;
use problem::format_panic_to_stderr;

format_panic_to_stderr();
let nothing: Option<&'static str> = None;

let _s = nothing.or_failed_to("get something"); // Failed to get something

Panicking on iterators of Result

Method .or_failed_to(message) can be used to abort the program via panic!() with formatted message on iterators with Result item when first Err is encountered otherwise unwrapping the Ok value. The error type will be converted to Problem just before panicing.

use problem::prelude::*;
use problem::format_panic_to_stderr;

format_panic_to_stderr();

let results = vec![Ok(1u32), Ok(2u32), Err("oops")];

let _ok: Vec<u32> = results.into_iter()
    .or_failed_to("collect numbers")
    .collect(); // Failed to collect numbers due to: oops

Main function exit with error message and custom status

FatalProblem and result::FinalResult types can be used on main function signature to allow programs to terminate with Problem formatted message and custom exit status.

use problem::prelude::*;

fn main() -> FinalResult {
    // Prints "I'm sorry Dave, I'm afraid I can't do that" and exits with status 1
    Err(Problem::from_error("I'm sorry Dave, I'm afraid I can't do that"))?;

    // Prints "I'm sorry Dave, I'm afraid I can't do that" and exits with status 42
    Err(Problem::from_error("I'm sorry Dave, I'm afraid I can't do that")).fatal_with_status(42)?;

    Ok(())
}

Logging errors

If log feature is enabled (default) function .ok_or_log_warn() or .ok_or_log_error() can be used on Result and iterator of Result items to convert Result into Option while logging Err wariants as warnings or errors.

When used on iterators .flatten() addaptor can be used to filter out all Err variant items after they were logged and converted to None.

use problem::prelude::*;

let results = vec![Ok(1u32), Ok(2), Err("oops"), Ok(3), Err("oh"), Ok(4)];

// Logs warning message: Continuing with error oops
// Logs warning message: Continuing with error oh
let ok: Vec<u32> = results.into_iter()
    .ok_or_log_warn()
    .flatten()
    .collect();

assert_eq!(ok.as_slice(), [1, 2, 3, 4]);

Backtraces

When compiled with backtrace feature (default) formatting of backtraces for Problem cause and panic! locations can be enabled via RUST_BACKTRACE=1 environment variable.

Fatal error: thread 'tests::test_panic_format_stderr_problem' panicked at src/lib.rs:657:35 with: Failed to complete processing task due to: while processing object, while processing input data, while parsing input got error caused by: boom!
--- Cause
   0: backtrace::backtrace::trace_unsynchronized::h936094cb968a67c2
             at /Users/jpastuszek/.cargo/registry/src/github.com-1ecc6299db9ec823/backtrace-0.3.13/src/backtrace/mod.rs:57
   1: problem::Problem::from_error::hfdbc5afef77017de
             at /Users/jpastuszek/Documents/problem/src/lib.rs:435
   2: <problem::Problem as core::convert::From<E>>::from::h3b5fdbec33645197
             at /Users/jpastuszek/Documents/problem/src/lib.rs:500
   3: <T as core::convert::Into<U>>::into::h37311b4bc5720d6d
             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/libcore/convert.rs:455
   4: <core::result::Result<O, E> as problem::ProblemWhile>::problem_while::{{closure}}::h97ad232ce9ba14fb
             at /Users/jpastuszek/Documents/problem/src/lib.rs:617
   5: <core::result::Result<T, E>>::map_err::he22546342a0a16ff
             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/libcore/result.rs:530
   6: <core::result::Result<O, E> as problem::ProblemWhile>::problem_while::he5e05f693d81f439
             at /Users/jpastuszek/Documents/problem/src/lib.rs:617
   7: problem::tests::test_panic_format_stderr_problem::he7f271034488edfe
             at /Users/jpastuszek/Documents/problem/src/lib.rs:1053
   8: problem::tests::test_panic_format_stderr_problem::{{closure}}::hd06e112465364a39
             at /Users/jpastuszek/Documents/problem/src/lib.rs:1051
   9: core::ops::function::FnOnce::call_once::hb512c264f284bc3f
             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/libcore/ops/function.rs:238
  10: <F as alloc::boxed::FnBox<A>>::call_box::h0b961cc85c049bee
             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/liballoc/boxed.rs:673
  11: ___rust_maybe_catch_panic
             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/libpanic_unwind/lib.rs:102
  12: std::sys_common::backtrace::__rust_begin_short_backtrace::h07f538384f587585
             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/libtest/lib.rs:1426
  13: std::panicking::try::do_call::h4953be8a0738d6ec
             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/libstd/panicking.rs:310
  14: ___rust_maybe_catch_panic
             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/libpanic_unwind/lib.rs:102
  15: <F as alloc::boxed::FnBox<A>>::call_box::h5a5d3b35dc8857f3
             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/libstd/thread/mod.rs:476
  16: std::sys::unix::thread::Thread::new::thread_start::hb66fd5e16b37cfd7
             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/libstd/sys_common/thread.rs:24
  17: __pthread_body
  18: __pthread_start
--- Panicked
   0: backtrace::backtrace::trace_unsynchronized::h936094cb968a67c2
             at /Users/jpastuszek/.cargo/registry/src/github.com-1ecc6299db9ec823/backtrace-0.3.13/src/backtrace/mod.rs:57
   1: problem::format_panic_to_stderr::{{closure}}::h24ae835f59658f26
             at /Users/jpastuszek/Documents/problem/src/lib.rs:868
   2: std::panicking::rust_panic_with_hook::h3fe6a67edb032589
             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/libstd/panicking.rs:495
   3: std::panicking::continue_panic_fmt::hf7169aba6b1afe9c
             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/libstd/panicking.rs:398
   4: std::panicking::begin_panic_fmt::hb5f6d46d54559b8a
             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/libstd/panicking.rs:353
   5: <core::result::Result<O, E> as problem::FailedTo<O>>::or_failed_to::{{closure}}::h8e0b5d62111b80f4
             at /Users/jpastuszek/Documents/problem/src/lib.rs:657
   6: <core::result::Result<T, E>>::unwrap_or_else::h8bcc063ecb00981e
             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/libcore/result.rs:774
   7: <core::result::Result<O, E> as problem::FailedTo<O>>::or_failed_to::h2e59dbec5efe6366
             at /Users/jpastuszek/Documents/problem/src/lib.rs:657
   8: problem::tests::test_panic_format_stderr_problem::he7f271034488edfe
             at /Users/jpastuszek/Documents/problem/src/lib.rs:1058
   9: problem::tests::test_panic_format_stderr_problem::{{closure}}::hd06e112465364a39
             at /Users/jpastuszek/Documents/problem/src/lib.rs:1051
  10: core::ops::function::FnOnce::call_once::hb512c264f284bc3f
             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/libcore/ops/function.rs:238
  11: <F as alloc::boxed::FnBox<A>>::call_box::h0b961cc85c049bee
             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/liballoc/boxed.rs:673
  12: ___rust_maybe_catch_panic
             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/libpanic_unwind/lib.rs:102
  13: std::sys_common::backtrace::__rust_begin_short_backtrace::h07f538384f587585
             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/libtest/lib.rs:1426
  14: std::panicking::try::do_call::h4953be8a0738d6ec
             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/libstd/panicking.rs:310
  15: ___rust_maybe_catch_panic
             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/libpanic_unwind/lib.rs:102
  16: <F as alloc::boxed::FnBox<A>>::call_box::h5a5d3b35dc8857f3
             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/libstd/thread/mod.rs:476
  17: std::sys::unix::thread::Thread::new::thread_start::hb66fd5e16b37cfd7
             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/libstd/sys_common/thread.rs:24
  18: __pthread_body
  19: __pthread_start

Access

Formatted backtrace &str can be accessed via Problem::backtrace function that will return Some if backtrace feature is enabled and RUST_BACKTRACE=1 environment variable is set.

use problem::prelude::*;

Problem::from_error("foo").backtrace(); // Some("   0: backtrace...")

Modules

logged
prelude

Includes Problem type and related conversion traits and in_context_of* functions

result

Handy Result types using Problem as error.

Macros

problem

Construct Err variant of Result containing Problem with given message formatted with format! macro.

Structs

FatalProblem

This error type is meant to be used as main() result error. It implements Debug display so that the program can terminate with nice message formatted with Problem and custom exit status.

Problem

Wraps error, context and backtrace information and formats it for display. Data is heap allocated to avoid type parameters or lifetimes.

ProblemIter

Iterator that will panic on first error with message displaying Display formatted message

Traits

FailedTo

Extension of Result that allows program to panic with Display message on Err for fatal application errors that are not bugs

FailedToIter

Convert Iterator of Result<O, E> to iterator of O and panic on first E with problem message

Fatal

Extension trait to map Option to Result with Problem

MapProblem

Map type containing error to type containing Problem

MapProblemOr

Map type not containing any error to type containing given Problem

OkOrProblem

Extension trait to map Option to Result with Problem

ProblemWhile

Convert to Problem if needed and add context to it

Functions

format_panic_to_error_log

Set panic hook so that when program panics it will log error massage with error! macro

format_panic_to_stderr

Set panic hook so that formats error message to stderr with more Problem friendly way

in_context_of

Executes closure with problem_while context

in_context_of_with

Executes closure with problem_while_with context