[−][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 |
result | Handy Result types using |
Macros
problem | Construct |
Structs
FatalProblem | This error type is meant to be used as |
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 |
Traits
FailedTo | Extension of |
FailedToIter | Convert |
Fatal | Extension trait to map |
MapProblem | Map type containing error to type containing |
MapProblemOr | Map type not containing any error to type containing given |
OkOrProblem | Extension trait to map |
ProblemWhile | Convert to |
Functions
format_panic_to_error_log | Set panic hook so that when program panics it will log error massage with |
format_panic_to_stderr | Set panic hook so that formats error message to |
in_context_of | Executes closure with |
in_context_of_with | Executes closure with |