Expand description
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");§Converting Problem into type implementing std::error::Error trait
Use Problem::into_error() or .into_error() on Result type to convert Problem into type implementing std::error::Error.
This allows to return erors to layers that require Error trait implementation for errors.
use problem::prelude::*;
fn foo<E: std::error::Error>(err: E) {
assert_eq!(err.to_string(), "while bar got error caused by: foo");
}
foo(Problem::from("foo").problem_while("bar").into_error());use problem::prelude::*;
struct LibError(String);
impl<E: std::error::Error> From<E> for LibError {
fn from(e: E) -> LibError {
LibError(e.to_string())
}
}
fn foo() -> Result<(), LibError> {
problem!("foo").problem_while("bar").into_error()?;
Ok(())
}
assert_eq!(foo().unwrap_err().0.to_string(), "while bar got error caused by: foo");§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...")§Send and Sync
Problem will implement Send and Sync traits when feature send-sync is enabled.
This feature is disabled by default to allow !Sync or !Send error types to be wrapped by Problem.
Modules§
- logged
- prelude
- Includes
Problemtype and related conversion traits andin_context_of*functions - result
- Handy Result types using
Problemas error.
Macros§
- problem
- Construct
Errvariant ofResultcontainingProblemwith given message formatted withformat!macro.
Structs§
- Error
- Wraps
Probleminto type implementingErrortrait. - Fatal
Problem - This error type is meant to be used as
main()result error. It implementsDebugdisplay so that the program can terminate with nice message formatted withProblemand 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.
- Problem
Iter - Iterator that will panic on first error with message displaying
Displayformatted message
Traits§
- Failed
To - Extension of
Resultthat allows program to panic withDisplaymessage onErrfor fatal application errors that are not bugs - Failed
ToIter - Convert
IteratorofResult<O, E>to iterator ofOand panic on firstEwith problem message - Fatal
- Extension trait to map
OptiontoResultwithProblem - Into
Error - MapProblem
- Map type containing error to type containing
Problem - MapProblem
Or - Map type not containing any error to type containing given
Problem - OkOr
Problem - Extension trait to map
OptiontoResultwithProblem - Problem
While - Convert to
Problemif 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
stderrwith moreProblemfriendly way - in_
context_ of - Executes closure with
problem_whilecontext - in_
context_ of_ with - Executes closure with
problem_while_withcontext