problem/
lib.rs

1/*!
2This 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:
3* reporting error message (e.g. log with `error!` macro),
4* aborting program on error other than a bug (e.g. using `panic!` macro),
5* bubbling up errors (e.g. with `?`),
6* ignoring errors (e.g. using `Result::ok`).
7
8# Usage
9The core type of this crate is `Problem`.
10It is used to capture error, backtrace (if enabled) and any additional context information and present it in user friendly way via `Display` implementation.
11
12This library also provides many additional extension traits and some functions that make it easy to construct `Problem` type in different situations
13as well as report or abort programs on error.
14It is recommended to import all the types and traits via perlude module: `use problem::prelude::*`.
15
16`Problem` stores error cause information as `Box<dyn Error>` to dealy construction of error message to when it is actually needed.
17Additionally `Problem` can also store backtrace `String` (if enabled) and a chain of additional context messages as `Vec<String>`.
18
19In order to support conversion from arbitary types implementing `Error` trait, `Problem` does not implement this trait.
20
21# Creating `Problem`
22There are multiple ways to crate `Problem` value.
23
24## Using constructor
25Using `Problem::from_error(error)` if `error` implements `Error` trait (via `Into<Box<dyn Error>>`).
26Note that `String` and `&str` implement `Into<Box<dyn Error>>` so `Problem` can be constructed directly from strings as well.
27
28```rust
29use problem::prelude::*;
30use std::io;
31
32Problem::from_error(io::Error::new(io::ErrorKind::InvalidInput, "boom!"));
33Problem::from_error(Box::new(io::Error::new(io::ErrorKind::InvalidInput, "boom!")));
34Problem::from_error("boom!");
35```
36
37Use `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.
38
39```rust
40use problem::prelude::*;
41use std::io;
42
43let error = io::Error::new(io::ErrorKind::InvalidInput, "boom!");
44let problem = Problem::from_error_message(&error);
45
46drop(error);
47drop(problem);
48```
49
50## Using macro
51Using `problem!` macro an `Err` variant of `Result` containing `Problem` with formatted message can be constructed.
52
53```rust
54use problem::prelude::*;
55use std::io;
56
57fn foo() -> Result<(), Problem> {
58    problem!("Can't count to {}", 4)
59}
60
61assert_eq!(foo().unwrap_err().to_string(), "Can't count to 4");
62```
63
64## Implicitly
65Types implementing `Into<Box<dyn Error>>` trait can be converted to `Problem` via `From` trait. `?` will automatically convert types implementing `Error` to `Problem`.
66
67```rust
68use problem::prelude::*;
69
70fn foo() -> Result<String, Problem> {
71    let str = String::from_utf8(vec![0, 123, 255])?;
72    Ok(str)
73}
74
75assert_eq!(foo().unwrap_err().to_string(), "invalid utf-8 sequence of 1 bytes from index 2");
76```
77
78If `Error::cause` or `Error::source` is available, the error message from cause chain will be displayed.
79
80```rust
81use problem::prelude::*;
82use std::fmt;
83use std::error::Error;
84
85#[derive(Debug)]
86struct ErrorWithCause(std::string::FromUtf8Error);
87
88impl fmt::Display for ErrorWithCause {
89    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
90        write!(f, "bad things happened")
91    }
92}
93
94impl Error for ErrorWithCause {
95    fn cause(&self) -> Option<&dyn Error> {
96        Some(&self.0)
97    }
98}
99
100fn foo() -> Result<String, Problem> {
101    let str = String::from_utf8(vec![0, 123, 255]).map_err(ErrorWithCause)?;
102    Ok(str)
103}
104
105assert_eq!(foo().unwrap_err().to_string(), "bad things happened; caused by: invalid utf-8 sequence of 1 bytes from index 2");
106```
107
108## By explicitly mapping `Result`
109`Result<T, E>` can be mapped into `Result<T, Problem>` with `.map_problem()` function.
110
111```rust
112use problem::prelude::*;
113
114let res: Result<(), &'static str> = Err("oops");
115let problem: Result<(), Problem> = res.map_problem();
116
117assert_eq!(problem.unwrap_err().to_string(), "oops");
118```
119
120## By conversion of `Option` into `Result`
121`Option<T>` can be converted into `Result<T, Problem>` with `.ok_or_problem(problem)` function where `problem` implements `Into<Problem>`.
122
123```rust
124use problem::prelude::*;
125
126let opt: Option<()> = None;
127let problem: Result<(), Problem> = opt.ok_or_problem("oops");
128
129assert_eq!(problem.unwrap_err().to_string(), "oops");
130```
131
132## From `Result` with `Err` containing `Option`
133`.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.
134This may be usefult when working with FFI.
135
136```rust
137use problem::prelude::*;
138
139let unknown: Result<(), Option<&'static str>> = Err(None);
140let known: Result<(), Option<&'static str>> = Err(Some("oops"));
141
142assert_eq!(unknown.map_problem_or("unknown error").unwrap_err().to_string(), "unknown error");
143assert_eq!(known.map_problem_or("unknown error").unwrap_err().to_string(), "oops");
144```
145
146There is also `.map_problem_or_else(problem_function)` variant provided that can be used to defer construction of error to error path.
147
148```rust
149use problem::prelude::*;
150
151let unknown: Result<(), Option<&'static str>> = Err(None);
152let known: Result<(), Option<&'static str>> = Err(Some("oops"));
153
154assert_eq!(unknown.map_problem_or_else(|| "unknown error").unwrap_err().to_string(), "unknown error");
155assert_eq!(known.map_problem_or_else(|| "unknown error").unwrap_err().to_string(), "oops");
156```
157
158# Adding context to `Problem`
159A context information that provides clues on which good path has been taken that led to error can be added to `Problem` object.
160
161Adding context is a good way to convert other error types to `Problem` as well as providing extra information on where that happened.
162
163## On `Result` error
164Method `.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).
165
166```rust
167use problem::prelude::*;
168
169let res = String::from_utf8(vec![0, 123, 255]);
170
171assert_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");
172```
173
174There is also `.problem_while_with(message_function)` variant provided that can be used to defer construction of context message to error path.
175
176```rust
177use problem::prelude::*;
178
179let res = String::from_utf8(vec![0, 123, 255]);
180
181assert_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");
182```
183
184## Using scope and `?`
185Function `in_context_of(message, closure)` can be used to wrap block of code in a closure that returns `Result`.
186
187This is useful when you want to add context to any error that can happen in the block of code and use `?` operator.
188The return type of the closure needs to be `Result<T, Problem>` but `?` operator can convert to `Problem` automatically.
189
190```rust
191use problem::prelude::*;
192
193let res = in_context_of("processing string", || {
194    let _s = String::from_utf8(vec![0, 123, 255])?;
195    // do some processing of _s
196    Ok(())
197});
198
199assert_eq!(res.unwrap_err().to_string(), "while processing string got error caused by: invalid utf-8 sequence of 1 bytes from index 2");
200```
201
202There is also `in_context_of_with(message_function, closure)` variant provided that can be used to defer construction of context message to error path.
203
204```rust
205use problem::prelude::*;
206
207let res = in_context_of_with(|| "processing string", || {
208    let _s = String::from_utf8(vec![0, 123, 255])?;
209    // do some processing of _s
210    Ok(())
211});
212
213assert_eq!(res.unwrap_err().to_string(), "while processing string got error caused by: invalid utf-8 sequence of 1 bytes from index 2");
214```
215
216## Nested context
217Context methods can be used multiple times to add layers of context.
218
219```rust
220use problem::prelude::*;
221
222fn foo() -> Result<String, Problem> {
223    let str = String::from_utf8(vec![0, 123, 255])?;
224    Ok(str)
225}
226
227let res = in_context_of("doing stuff", || {
228    let _s = foo().problem_while("running foo")?;
229    // do some processing of _s
230    Ok(())
231});
232
233assert_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");
234```
235
236# Aborting program on `Problem`
237`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.
238
239This library provides function `format_panic_to_stderr()` to set up hook that will use `eprintln!("{}", message)` to report panics.
240Alternatively 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.
241
242Panic hooks will produce backtrace of panic site if enabled via `RUST_BACKTRACE=1` environment variable along of the `Problem` object backtrace collected
243at object construction site.
244
245```noformat
246ERROR: Panicked in libcore/slice/mod.rs:2334:5: index 18492 out of range for slice of length 512
247```
248
249## Panicking on `Result` with `Problem`
250Similarly 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
251message and error converted to `Problem` to format the error message with `Display` trait.
252
253```rust,should_panic
254use problem::prelude::*;
255use problem::format_panic_to_stderr;
256
257format_panic_to_stderr();
258
259// Prints message:
260let _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
261```
262
263## Panicking on `Option`
264Similarly 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.
265
266```rust,should_panic
267use problem::prelude::*;
268use problem::format_panic_to_stderr;
269
270format_panic_to_stderr();
271let nothing: Option<&'static str> = None;
272
273let _s = nothing.or_failed_to("get something"); // Failed to get something
274```
275
276## Panicking on iterators of `Result`
277Method `.or_failed_to(message)` can be used to abort the program via `panic!()` with formatted message on iterators with `Result` item when first `Err`
278is encountered otherwise unwrapping the `Ok` value.
279The error type will be converted to `Problem` just before panicing.
280
281```rust,should_panic
282use problem::prelude::*;
283use problem::format_panic_to_stderr;
284
285format_panic_to_stderr();
286
287let results = vec![Ok(1u32), Ok(2u32), Err("oops")];
288
289let _ok: Vec<u32> = results.into_iter()
290    .or_failed_to("collect numbers")
291    .collect(); // Failed to collect numbers due to: oops
292```
293
294# Main function exit with error message and custom status
295`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.
296
297```rust,should_panic
298use problem::prelude::*;
299
300fn main() -> FinalResult {
301    // Prints "I'm sorry Dave, I'm afraid I can't do that" and exits with status 1
302    Err(Problem::from_error("I'm sorry Dave, I'm afraid I can't do that"))?;
303
304    // Prints "I'm sorry Dave, I'm afraid I can't do that" and exits with status 42
305    Err(Problem::from_error("I'm sorry Dave, I'm afraid I can't do that")).fatal_with_status(42)?;
306
307    Ok(())
308}
309```
310
311# Logging errors
312If `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
313`Result` into `Option` while logging `Err` wariants as warnings or errors.
314
315When used on iterators `.flatten()` addaptor can be used to filter out all `Err` variant items after they were logged and converted to `None`.
316
317```rust
318use problem::prelude::*;
319
320# #[cfg(feature = "log")]
321# fn test_with_log_feature() {
322let results = vec![Ok(1u32), Ok(2), Err("oops"), Ok(3), Err("oh"), Ok(4)];
323
324// Logs warning message: Continuing with error oops
325// Logs warning message: Continuing with error oh
326let ok: Vec<u32> = results.into_iter()
327    .ok_or_log_warn()
328    .flatten()
329    .collect();
330
331assert_eq!(ok.as_slice(), [1, 2, 3, 4]);
332# }
333#
334# #[cfg(feature = "log")]
335# test_with_log_feature();
336```
337
338# Backtraces
339When compiled with `backtrace` feature (default) formatting of backtraces for `Problem` cause and `panic!` locations can be enabled via
340`RUST_BACKTRACE=1` environment variable.
341
342```noformat
343Fatal 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!
344--- Cause
345   0: backtrace::backtrace::trace_unsynchronized::h936094cb968a67c2
346             at /Users/jpastuszek/.cargo/registry/src/github.com-1ecc6299db9ec823/backtrace-0.3.13/src/backtrace/mod.rs:57
347   1: problem::Problem::from_error::hfdbc5afef77017de
348             at /Users/jpastuszek/Documents/problem/src/lib.rs:435
349   2: <problem::Problem as core::convert::From<E>>::from::h3b5fdbec33645197
350             at /Users/jpastuszek/Documents/problem/src/lib.rs:500
351   3: <T as core::convert::Into<U>>::into::h37311b4bc5720d6d
352             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/libcore/convert.rs:455
353   4: <core::result::Result<O, E> as problem::ProblemWhile>::problem_while::{{closure}}::h97ad232ce9ba14fb
354             at /Users/jpastuszek/Documents/problem/src/lib.rs:617
355   5: <core::result::Result<T, E>>::map_err::he22546342a0a16ff
356             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/libcore/result.rs:530
357   6: <core::result::Result<O, E> as problem::ProblemWhile>::problem_while::he5e05f693d81f439
358             at /Users/jpastuszek/Documents/problem/src/lib.rs:617
359   7: problem::tests::test_panic_format_stderr_problem::he7f271034488edfe
360             at /Users/jpastuszek/Documents/problem/src/lib.rs:1053
361   8: problem::tests::test_panic_format_stderr_problem::{{closure}}::hd06e112465364a39
362             at /Users/jpastuszek/Documents/problem/src/lib.rs:1051
363   9: core::ops::function::FnOnce::call_once::hb512c264f284bc3f
364             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/libcore/ops/function.rs:238
365  10: <F as alloc::boxed::FnBox<A>>::call_box::h0b961cc85c049bee
366             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/liballoc/boxed.rs:673
367  11: ___rust_maybe_catch_panic
368             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/libpanic_unwind/lib.rs:102
369  12: std::sys_common::backtrace::__rust_begin_short_backtrace::h07f538384f587585
370             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/libtest/lib.rs:1426
371  13: std::panicking::try::do_call::h4953be8a0738d6ec
372             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/libstd/panicking.rs:310
373  14: ___rust_maybe_catch_panic
374             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/libpanic_unwind/lib.rs:102
375  15: <F as alloc::boxed::FnBox<A>>::call_box::h5a5d3b35dc8857f3
376             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/libstd/thread/mod.rs:476
377  16: std::sys::unix::thread::Thread::new::thread_start::hb66fd5e16b37cfd7
378             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/libstd/sys_common/thread.rs:24
379  17: __pthread_body
380  18: __pthread_start
381--- Panicked
382   0: backtrace::backtrace::trace_unsynchronized::h936094cb968a67c2
383             at /Users/jpastuszek/.cargo/registry/src/github.com-1ecc6299db9ec823/backtrace-0.3.13/src/backtrace/mod.rs:57
384   1: problem::format_panic_to_stderr::{{closure}}::h24ae835f59658f26
385             at /Users/jpastuszek/Documents/problem/src/lib.rs:868
386   2: std::panicking::rust_panic_with_hook::h3fe6a67edb032589
387             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/libstd/panicking.rs:495
388   3: std::panicking::continue_panic_fmt::hf7169aba6b1afe9c
389             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/libstd/panicking.rs:398
390   4: std::panicking::begin_panic_fmt::hb5f6d46d54559b8a
391             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/libstd/panicking.rs:353
392   5: <core::result::Result<O, E> as problem::FailedTo<O>>::or_failed_to::{{closure}}::h8e0b5d62111b80f4
393             at /Users/jpastuszek/Documents/problem/src/lib.rs:657
394   6: <core::result::Result<T, E>>::unwrap_or_else::h8bcc063ecb00981e
395             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/libcore/result.rs:774
396   7: <core::result::Result<O, E> as problem::FailedTo<O>>::or_failed_to::h2e59dbec5efe6366
397             at /Users/jpastuszek/Documents/problem/src/lib.rs:657
398   8: problem::tests::test_panic_format_stderr_problem::he7f271034488edfe
399             at /Users/jpastuszek/Documents/problem/src/lib.rs:1058
400   9: problem::tests::test_panic_format_stderr_problem::{{closure}}::hd06e112465364a39
401             at /Users/jpastuszek/Documents/problem/src/lib.rs:1051
402  10: core::ops::function::FnOnce::call_once::hb512c264f284bc3f
403             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/libcore/ops/function.rs:238
404  11: <F as alloc::boxed::FnBox<A>>::call_box::h0b961cc85c049bee
405             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/liballoc/boxed.rs:673
406  12: ___rust_maybe_catch_panic
407             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/libpanic_unwind/lib.rs:102
408  13: std::sys_common::backtrace::__rust_begin_short_backtrace::h07f538384f587585
409             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/libtest/lib.rs:1426
410  14: std::panicking::try::do_call::h4953be8a0738d6ec
411             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/libstd/panicking.rs:310
412  15: ___rust_maybe_catch_panic
413             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/libpanic_unwind/lib.rs:102
414  16: <F as alloc::boxed::FnBox<A>>::call_box::h5a5d3b35dc8857f3
415             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/libstd/thread/mod.rs:476
416  17: std::sys::unix::thread::Thread::new::thread_start::hb66fd5e16b37cfd7
417             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/libstd/sys_common/thread.rs:24
418  18: __pthread_body
419  19: __pthread_start
420```
421
422## Access
423Formatted backtrace `&str` can be accessed via `Problem::backtrace` function that will return `Some` if `backtrace` feature is enabled and `RUST_BACKTRACE=1`
424environment variable is set.
425
426```rust
427use problem::prelude::*;
428
429Problem::from_error("foo").backtrace(); // Some("   0: backtrace...")
430```
431 */
432#[cfg(feature = "log")]
433#[macro_use]
434extern crate log;
435use std::error::Error;
436use std::fmt::{self, Display, Write};
437use std::panic;
438
439const DEFAULT_FATAL_STATUS: i32 = 1;
440
441/// Includes `Problem` type and related conversion traits and `in_context_of*` functions
442pub mod prelude {
443    pub use super::{
444        in_context_of, in_context_of_with, problem, FailedTo, FailedToIter, Fatal, FatalProblem,
445        MapProblem, MapProblemOr, OkOrProblem, Problem, ProblemWhile,
446    };
447
448    pub use super::result::FinalResult;
449    // Note that `result::Result` is not part of prelude as it may conflict with standard library or
450    // custom library result types.
451
452    #[cfg(feature = "log")]
453    pub use super::logged::{OkOrLog, OkOrLogIter};
454}
455
456/// Wraps error, context and backtrace information and formats it for display.
457/// Data is heap allocated to avoid type parameters or lifetimes.
458#[derive(Debug)]
459pub struct Problem {
460    error: Box<dyn Error>,
461    context: Vec<String>,
462    backtrace: Option<String>,
463}
464
465impl Problem {
466    /// Create `Problem` from types implementing `Into<Box<dyn Error>>` (including `String` and `&str`) so that `Error::cause`
467    /// chain is followed through in the `Display` message
468    pub fn from_error(error: impl Into<Box<dyn Error>>) -> Problem {
469        Problem {
470            error: error.into(),
471            context: Vec::new(),
472            backtrace: format_backtrace(),
473        }
474    }
475
476    /// Same as `Problem::from_error` but stores only final message as `String` and does not take ownership of the error
477    pub fn from_error_message(error: &impl Error) -> Problem {
478        let mut message = String::new();
479        write_error_message(error, &mut message).unwrap();
480
481        Problem {
482            error: message.into(),
483            context: Vec::new(),
484            backtrace: format_backtrace(),
485        }
486    }
487
488    /// Get backtrace associated with this `Problem` instance if available
489    pub fn backtrace(&self) -> Option<&str> {
490        self.backtrace.as_ref().map(String::as_str)
491    }
492}
493
494#[allow(deprecated)]
495fn write_error_message(error: &dyn Error, w: &mut impl Write) -> fmt::Result {
496    write!(w, "{}", error)?;
497
498    let mut error_cause = error;
499    loop {
500        // Note: using Error::cause() here to be backward compatible with older errors
501        if let Some(cause) = error_cause.cause() {
502            write!(w, "; caused by: {}", cause)?;
503            error_cause = cause;
504        } else {
505            break;
506        }
507    }
508    Ok(())
509}
510
511impl Display for Problem {
512    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
513        if let Some(context) = self.context.last() {
514            write!(f, "while {}", context)?;
515        }
516        for context in self.context.iter().rev().skip(1) {
517            write!(f, ", while {}", context)?;
518        }
519        if !self.context.is_empty() {
520            write!(f, " got error caused by: ")?;
521        }
522
523        write_error_message(self.error.as_ref(), f)?;
524
525        if let Some(backtrace) = self.backtrace.as_ref() {
526            write!(f, "\n--- Cause\n{}", backtrace)?;
527        }
528
529        Ok(())
530    }
531}
532
533/// Every type implementing `Into<Box<dyn Error>>` trait (including `String` and `&str` types) can be converted to `Problem` via `?` operator
534impl<E> From<E> for Problem
535where
536    E: Into<Box<dyn Error>>,
537{
538    fn from(error: E) -> Problem {
539        Problem::from_error(error)
540    }
541}
542
543/// Construct `Err` variant of `Result` containing `Problem` with given message formatted with
544/// `format!` macro.
545#[macro_export]
546macro_rules! problem {
547    ($ ($ arg : tt) *) => { Err(Problem::from_error(format!($($arg)*))) };
548}
549
550/// This error type is meant to be used as `main()` result error. It implements `Debug` display so
551/// that the program can terminate with nice message formatted with `Problem` and custom exit
552/// status.
553pub struct FatalProblem {
554    status: i32,
555    problem: Problem,
556}
557
558impl From<Problem> for FatalProblem {
559    fn from(problem: Problem) -> FatalProblem {
560        FatalProblem {
561            status: DEFAULT_FATAL_STATUS,
562            problem,
563        }
564    }
565}
566
567impl<E> From<E> for FatalProblem
568where
569    E: Into<Box<dyn std::error::Error>>,
570{
571    fn from(error: E) -> FatalProblem {
572        FatalProblem {
573            status: DEFAULT_FATAL_STATUS,
574            problem: Problem::from_error(error),
575        }
576    }
577}
578
579impl fmt::Debug for FatalProblem {
580    fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result {
581        eprintln!("{}", self.problem);
582        std::process::exit(self.status)
583    }
584}
585
586/// Extension trait to map `Option` to `Result` with `Problem`
587pub trait Fatal<O> {
588    fn fatal(self) -> Result<O, FatalProblem>;
589    fn fatal_with_status(self, status: i32) -> Result<O, FatalProblem>;
590}
591
592/// Extension trait that allow to convert to `Result` with `FatalProblem`
593impl<O> Fatal<O> for Result<O, Problem> {
594    /// Converts to `Result` with `FatalProblem` and default exit status
595    fn fatal(self) -> Result<O, FatalProblem> {
596        self.fatal_with_status(DEFAULT_FATAL_STATUS)
597    }
598
599    /// Converts to `Result` with `FatalProblem` and given exit status
600    fn fatal_with_status(self, status: i32) -> Result<O, FatalProblem> {
601        self.map_err(|problem| FatalProblem { status, problem })
602    }
603}
604
605pub mod result {
606    //! Handy Result types using `Problem` as error.
607    use super::{FatalProblem, Problem};
608
609    /// `Result` with `Problem`
610    pub type Result<T> = std::result::Result<T, Problem>;
611
612    /// `Result` with `FatalProblem` to be used as `main()` return value
613    pub type FinalResult = std::result::Result<(), FatalProblem>;
614}
615
616/// Map type containing error to type containing `Problem`
617pub trait MapProblem {
618    type ProblemCarrier;
619    fn map_problem(self) -> Self::ProblemCarrier;
620}
621
622/// Mapping `Result` with error to `Result` with `Problem`
623impl<O, E> MapProblem for Result<O, E>
624where
625    E: Into<Problem>,
626{
627    type ProblemCarrier = Result<O, Problem>;
628    fn map_problem(self) -> Result<O, Problem> {
629        self.map_err(|e| e.into())
630    }
631}
632
633/// Map type not containing any error to type containing given `Problem`
634pub trait MapProblemOr {
635    type ProblemCarrier;
636    fn map_problem_or(self, problem: impl Into<Problem>) -> Self::ProblemCarrier;
637    fn map_problem_or_else<F, P>(self, problem: F) -> Self::ProblemCarrier
638    where
639        F: FnOnce() -> P,
640        P: Into<Problem>;
641}
642
643/// Mapping `Result` with `Option<E>` to `Result` with `Problem` where `E` implements `Into<Problem>`
644impl<O, E> MapProblemOr for Result<O, Option<E>>
645where
646    E: Into<Problem>,
647{
648    type ProblemCarrier = Result<O, Problem>;
649
650    fn map_problem_or(self, problem: impl Into<Problem>) -> Result<O, Problem> {
651        self.map_err(|e| e.map(Into::into).unwrap_or_else(|| problem.into()))
652    }
653
654    fn map_problem_or_else<F, P>(self, problem: F) -> Self::ProblemCarrier
655    where
656        F: FnOnce() -> P,
657        P: Into<Problem>,
658    {
659        self.map_err(|e| e.map(Into::into).unwrap_or_else(|| problem().into()))
660    }
661}
662
663/// Extension trait to map `Option` to `Result` with `Problem`
664pub trait OkOrProblem<O> {
665    fn ok_or_problem<P>(self, problem: P) -> Result<O, Problem>
666    where
667        P: Into<Problem>;
668    fn ok_or_problem_with<F, P>(self, problem: F) -> Result<O, Problem>
669    where
670        F: FnOnce() -> P,
671        P: Into<Problem>;
672}
673
674impl<O> OkOrProblem<O> for Option<O> {
675    fn ok_or_problem<P>(self, problem: P) -> Result<O, Problem>
676    where
677        P: Into<Problem>,
678    {
679        self.ok_or_else(|| problem.into())
680    }
681
682    fn ok_or_problem_with<F, P>(self, problem: F) -> Result<O, Problem>
683    where
684        F: FnOnce() -> P,
685        P: Into<Problem>,
686    {
687        self.ok_or_else(|| problem().into())
688    }
689}
690
691/// Convert to `Problem` if needed and add context to it
692pub trait ProblemWhile {
693    type WithContext;
694
695    /// Add context information
696    fn problem_while(self, message: impl ToString) -> Self::WithContext;
697
698    /// Add context information from function call
699    fn problem_while_with<F, M>(self, message: F) -> Self::WithContext
700    where
701        F: FnOnce() -> M,
702        M: ToString;
703}
704
705impl ProblemWhile for Problem {
706    type WithContext = Problem;
707
708    fn problem_while(mut self, message: impl ToString) -> Problem {
709        self.context.push(message.to_string());
710        self
711    }
712
713    fn problem_while_with<F, M>(self, message: F) -> Problem
714    where
715        F: FnOnce() -> M,
716        M: ToString,
717    {
718        self.problem_while(message())
719    }
720}
721
722impl<O, E> ProblemWhile for Result<O, E>
723where
724    E: Into<Problem>,
725{
726    type WithContext = Result<O, Problem>;
727
728    fn problem_while(self, message: impl ToString) -> Result<O, Problem> {
729        self.map_err(|err| err.into().problem_while(message))
730    }
731
732    fn problem_while_with<F, M>(self, message: F) -> Result<O, Problem>
733    where
734        F: FnOnce() -> M,
735        M: ToString,
736    {
737        self.map_err(|err| err.into().problem_while_with(message))
738    }
739}
740
741/// Executes closure with `problem_while` context
742pub fn in_context_of<O, B>(message: &str, body: B) -> Result<O, Problem>
743where
744    B: FnOnce() -> Result<O, Problem>,
745{
746    body().problem_while(message)
747}
748
749/// Executes closure with `problem_while_with` context
750pub fn in_context_of_with<O, F, M, B>(message: F, body: B) -> Result<O, Problem>
751where
752    F: FnOnce() -> M,
753    M: ToString,
754    B: FnOnce() -> Result<O, Problem>,
755{
756    body().problem_while_with(message)
757}
758
759/// Extension of `Result` that allows program to panic with `Display` message on `Err` for fatal application errors that are not bugs
760pub trait FailedTo<O> {
761    fn or_failed_to(self, message: impl Display) -> O;
762}
763
764impl<O, E> FailedTo<O> for Result<O, E>
765where
766    E: Into<Problem>,
767{
768    fn or_failed_to(self, message: impl Display) -> O {
769        self.unwrap_or_else(|err| panic!("Failed to {} due to: {}", message, err.into()))
770    }
771}
772
773impl<O> FailedTo<O> for Option<O> {
774    fn or_failed_to(self, message: impl Display) -> O {
775        self.unwrap_or_else(|| panic!("Failed to {}", message))
776    }
777}
778
779/// Iterator that will panic on first error with message displaying `Display` formatted message
780pub struct ProblemIter<I, M> {
781    inner: I,
782    message: M,
783}
784
785impl<I, O, E, M> Iterator for ProblemIter<I, M>
786where
787    I: Iterator<Item = Result<O, E>>,
788    E: Into<Problem>,
789    M: Display,
790{
791    type Item = O;
792
793    fn next(&mut self) -> Option<Self::Item> {
794        self.inner.next().map(|res| res.or_failed_to(&self.message))
795    }
796}
797
798/// Convert `Iterator` of `Result<O, E>` to iterator of `O` and panic on first `E` with problem message
799pub trait FailedToIter<O, E, M>: Sized {
800    fn or_failed_to(self, message: M) -> ProblemIter<Self, M>;
801}
802
803impl<I, O, E, M> FailedToIter<O, E, M> for I
804where
805    I: Iterator<Item = Result<O, E>>,
806    E: Into<Problem>,
807    M: Display,
808{
809    fn or_failed_to(self, message: M) -> ProblemIter<Self, M> {
810        ProblemIter {
811            inner: self,
812            message: message,
813        }
814    }
815}
816
817#[cfg(feature = "log")]
818pub mod logged {
819    use super::*;
820    use log::{error, warn};
821
822    /// Extension of `Result` that allows program to log on `Err` with `Display` message for application errors that are not critical
823    pub trait OkOrLog<O> {
824        fn ok_or_log_warn(self) -> Option<O>;
825        fn ok_or_log_error(self) -> Option<O>;
826    }
827
828    impl<O, E> OkOrLog<O> for Result<O, E>
829    where
830        E: Into<Problem>,
831    {
832        fn ok_or_log_warn(self) -> Option<O> {
833            self.map_err(|err| warn!("Continuing with error: {}", err.into()))
834                .ok()
835        }
836
837        fn ok_or_log_error(self) -> Option<O> {
838            self.map_err(|err| error!("Continuing with error: {}", err.into()))
839                .ok()
840        }
841    }
842
843    /// Iterator that will log as warn `Display` formatted message on `Err` and skip to next item; it can be flattened to skip failed items
844    pub struct ProblemWarnLoggingIter<I> {
845        inner: I,
846    }
847
848    impl<I, O, E> Iterator for ProblemWarnLoggingIter<I>
849    where
850        I: Iterator<Item = Result<O, E>>,
851        E: Into<Problem>,
852    {
853        type Item = Option<O>;
854
855        fn next(&mut self) -> Option<Self::Item> {
856            self.inner.next().map(|res| res.ok_or_log_warn())
857        }
858    }
859
860    /// Iterator that will log as error `Display` formatted message on `Err` and skip to next item; it can be flattened to skip failed items
861    pub struct ProblemErrorLoggingIter<I> {
862        inner: I,
863    }
864
865    impl<I, O, E> Iterator for ProblemErrorLoggingIter<I>
866    where
867        I: Iterator<Item = Result<O, E>>,
868        E: Into<Problem>,
869    {
870        type Item = Option<O>;
871
872        fn next(&mut self) -> Option<Self::Item> {
873            self.inner.next().map(|res| res.ok_or_log_error())
874        }
875    }
876
877    /// Convert `Iterator` of `Result<O, E>` to iterator of `Option<O>` and log any `Err` variants
878    pub trait OkOrLogIter<O, E>: Sized {
879        fn ok_or_log_warn(self) -> ProblemWarnLoggingIter<Self>;
880        fn ok_or_log_error(self) -> ProblemErrorLoggingIter<Self>;
881    }
882
883    impl<I, O, E> OkOrLogIter<O, E> for I
884    where
885        I: Iterator<Item = Result<O, E>>,
886        E: Into<Problem>,
887    {
888        fn ok_or_log_warn(self) -> ProblemWarnLoggingIter<Self> {
889            ProblemWarnLoggingIter { inner: self }
890        }
891
892        fn ok_or_log_error(self) -> ProblemErrorLoggingIter<Self> {
893            ProblemErrorLoggingIter { inner: self }
894        }
895    }
896}
897
898#[cfg(not(feature = "backtrace"))]
899fn format_backtrace() -> Option<String> {
900    None
901}
902
903/* Use default Rust format like:
904   0: std::sys_common::backtrace::_print
905             at C:\projects\rust\src\libstd\sys_common\backtrace.rs:94
906   1: std::sys_common::backtrace::_print
907             at C:\projects\rust\src\libstd\sys_common\backtrace.rs:71
908*/
909#[cfg(feature = "backtrace")]
910#[inline(always)]
911fn format_backtrace() -> Option<String> {
912    if let Ok("1") = std::env::var("RUST_BACKTRACE").as_ref().map(String::as_str) {
913        let mut backtrace = String::new();
914        let mut frame_no: u32 = 0;
915
916        backtrace::trace(|frame| {
917            let ip = frame.ip();
918
919            if frame_no > 0 {
920                backtrace.push_str("\n");
921            }
922
923            backtrace::resolve(ip, |symbol| {
924                if let Some(name) = symbol.name() {
925                    write!(backtrace, "{:4}: {}", frame_no, name).unwrap();
926                }
927                if let (Some(filename), Some(lineno)) = (symbol.filename(), symbol.lineno()) {
928                    write!(
929                        backtrace,
930                        "\n             at {}:{}",
931                        filename.display(),
932                        lineno
933                    )
934                    .unwrap();
935                }
936            });
937
938            frame_no += 1;
939            true // keep going to the next frame
940        });
941
942        Some(backtrace)
943    } else {
944        None
945    }
946}
947
948fn format_panic(panic: &std::panic::PanicInfo, backtrace: Option<String>) -> String {
949    let mut message = String::new();
950
951    let thread = std::thread::current();
952    let name = thread.name().unwrap_or("<unnamed>");
953
954    // taken from libstd
955    let msg = match panic.payload().downcast_ref::<&'static str>() {
956        Some(s) => *s,
957        None => match panic.payload().downcast_ref::<String>() {
958            Some(s) => &s[..],
959            None => "Box<Any>",
960        },
961    };
962
963    match (backtrace.is_some(), panic.location()) {
964        (true, Some(location)) => write!(
965            message,
966            "thread '{}' panicked at {} with: {}",
967            name, location, msg
968        )
969        .ok(),
970        (true, None) => write!(message, "thread '{}' panicked with: {}", name, msg).ok(),
971        (false, _) => write!(message, "{}", msg).ok(),
972    };
973
974    if let Some(backtrace) = backtrace {
975        message.push_str("\n--- Panicked\n");
976        message.push_str(&backtrace);
977    }
978
979    message
980}
981
982/// Set panic hook so that formats error message to `stderr` with more `Problem` friendly way
983pub fn format_panic_to_stderr() {
984    panic::set_hook(Box::new(|panic_info| {
985        let backtrace = format_backtrace();
986        eprintln!("Fatal error: {}", format_panic(panic_info, backtrace));
987    }));
988}
989
990/// Set panic hook so that when program panics it will log error massage with `error!` macro
991#[cfg(feature = "log")]
992pub fn format_panic_to_error_log() {
993    panic::set_hook(Box::new(|panic_info| {
994        let backtrace = format_backtrace();
995        error!("{}", format_panic(panic_info, backtrace));
996    }));
997}
998
999#[cfg(test)]
1000mod tests {
1001    use super::prelude::*;
1002    use super::{format_panic_to_stderr, in_context_of};
1003    use std::error::Error;
1004    use std::fmt::{self, Display};
1005    use std::io;
1006
1007    #[derive(Debug)]
1008    struct Foo;
1009
1010    impl Display for Foo {
1011        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1012            write!(f, "Foo error")
1013        }
1014    }
1015
1016    impl Error for Foo {}
1017
1018    #[derive(Debug)]
1019    struct Bar(Foo);
1020
1021    impl Display for Bar {
1022        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1023            write!(f, "Bar error")
1024        }
1025    }
1026
1027    impl Error for Bar {
1028        fn source(&self) -> Option<&(dyn Error + 'static)> {
1029            Some(&self.0)
1030        }
1031    }
1032
1033    #[derive(Debug)]
1034    struct Baz(Bar);
1035
1036    impl Display for Baz {
1037        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1038            write!(f, "Baz error")
1039        }
1040    }
1041
1042    impl Error for Baz {
1043        fn source(&self) -> Option<&(dyn Error + 'static)> {
1044            Some(&self.0)
1045        }
1046    }
1047
1048    #[test]
1049    #[should_panic(expected = "Failed to test due to: foo: 1")]
1050    fn test_problem_macro() {
1051        let err: Result<(), Problem> = problem!("foo: {}", 1);
1052        err.or_failed_to("test");
1053    }
1054
1055    #[test]
1056    fn test_convertion() {
1057        let _: Problem = io::Error::new(io::ErrorKind::InvalidInput, "boom!").into();
1058        let _: Problem = "boom!".into(); // via impl<'a> From<&'a str> for Box<dyn Error>
1059    }
1060
1061    #[test]
1062    #[should_panic(
1063        expected = "Failed to complete processing task due to: while processing object, while processing input data, while parsing input got error caused by: boom!"
1064    )]
1065    fn test_integration() {
1066        Err(io::Error::new(io::ErrorKind::InvalidInput, "boom!"))
1067            .problem_while("parsing input")
1068            .problem_while("processing input data")
1069            .problem_while("processing object")
1070            .or_failed_to("complete processing task")
1071    }
1072
1073    #[test]
1074    #[should_panic(
1075        expected = "Failed to complete processing task due to: while processing object, while processing input data, while parsing input got error caused by: boom!"
1076    )]
1077    fn test_integration_message() {
1078        Err("boom!")
1079            .problem_while("parsing input")
1080            .problem_while("processing input data")
1081            .problem_while("processing object")
1082            .or_failed_to("complete processing task")
1083    }
1084
1085    #[test]
1086    #[should_panic(
1087        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"
1088    )]
1089    fn test_integration_cause_chain() {
1090        Err(Baz(Bar(Foo)))
1091            .problem_while("parsing input")
1092            .problem_while("processing input data")
1093            .problem_while("processing object")
1094            .or_failed_to("complete processing task")
1095    }
1096
1097    #[test]
1098    #[should_panic(
1099        expected = "Failed to complete processing task due to: while doing stuff got error caused by: boom!"
1100    )]
1101    fn test_in_context_of() {
1102        in_context_of("doing stuff", || {
1103            Err(io::Error::new(io::ErrorKind::InvalidInput, "boom!"))?
1104        })
1105        .or_failed_to("complete processing task")
1106    }
1107
1108    #[test]
1109    #[should_panic(expected = "Failed to foo due to: boom!")]
1110    fn test_result() {
1111        Err(io::Error::new(io::ErrorKind::InvalidInput, "boom!")).or_failed_to("foo")
1112    }
1113
1114    #[test]
1115    #[should_panic(
1116        expected = "Failed to quix due to: Baz error; caused by: Bar error; caused by: Foo error"
1117    )]
1118    fn test_result_cause_chain() {
1119        Err(Baz(Bar(Foo))).or_failed_to("quix")
1120    }
1121
1122    #[test]
1123    #[should_panic(
1124        expected = "Failed to quix due to: Baz error; caused by: Bar error; caused by: Foo error"
1125    )]
1126    fn test_result_cause_chain_message() {
1127        let error = Baz(Bar(Foo));
1128        Err(Problem::from_error_message(&error)).or_failed_to("quix")
1129    }
1130
1131    #[test]
1132    #[should_panic(expected = "Failed to foo")]
1133    fn test_option() {
1134        None.or_failed_to("foo")
1135    }
1136
1137    #[test]
1138    #[should_panic(expected = "Failed to foo due to: boom!")]
1139    fn test_option_errors() {
1140        Err(Some(io::Error::new(io::ErrorKind::InvalidInput, "boom!")))
1141            .map_problem_or("<unknown error>")
1142            .or_failed_to("foo")
1143    }
1144
1145    #[test]
1146    #[should_panic(expected = "Failed to foo due to: <unknown error>")]
1147    fn test_result_option_errors_unknown() {
1148        let err: Result<(), Option<io::Error>> = Err(None);
1149        err.map_problem_or("<unknown error>").or_failed_to("foo")
1150    }
1151
1152    #[test]
1153    #[should_panic(expected = "Failed to foo due to: nothing here")]
1154    fn test_result_ok_or_problem() {
1155        None.ok_or_problem("nothing here").or_failed_to("foo")
1156    }
1157
1158    #[test]
1159    #[should_panic(expected = "Failed to foo due to: omg!")]
1160    fn test_result_iter_or_failed_to() {
1161        let results = vec![Ok(1u32), Ok(2u32), Err("omg!")];
1162        let _ok = results.into_iter().or_failed_to("foo").collect::<Vec<_>>();
1163    }
1164
1165    #[test]
1166    #[should_panic]
1167    fn test_panic_format_stderr() {
1168        format_panic_to_stderr();
1169        panic!("foo bar!");
1170    }
1171
1172    #[test]
1173    #[should_panic]
1174    fn test_panic_format_stderr_problem() {
1175        format_panic_to_stderr();
1176        let result: Result<(), Problem> = Err(io::Error::new(io::ErrorKind::InvalidInput, "boom!"))
1177            .problem_while("parsing input")
1178            .problem_while("processing input data")
1179            .problem_while("processing object");
1180
1181        result.or_failed_to("complete processing task");
1182    }
1183
1184    #[test]
1185    #[should_panic]
1186    fn test_panic_format_stderr_unwrap() {
1187        format_panic_to_stderr();
1188        let result: Result<(), io::Error> =
1189            Err(io::Error::new(io::ErrorKind::InvalidInput, "boom!"));
1190        result.unwrap();
1191    }
1192
1193    #[test]
1194    #[should_panic]
1195    fn test_panic_format_stderr_expect() {
1196        format_panic_to_stderr();
1197        let result: Result<(), io::Error> =
1198            Err(io::Error::new(io::ErrorKind::InvalidInput, "boom!"));
1199        result.expect("foo");
1200    }
1201
1202    #[test]
1203    #[cfg(feature = "backtrace")]
1204    fn test_problem_backtrace() {
1205        let p = Problem::from_error("foo")
1206            .problem_while("bar")
1207            .problem_while("baz");
1208
1209        if let Ok("1") = std::env::var("RUST_BACKTRACE").as_ref().map(String::as_str) {
1210            assert!(p.backtrace().is_some());
1211            println!("{}", p.backtrace().unwrap());
1212        } else {
1213            assert!(p.backtrace().is_none());
1214        }
1215    }
1216
1217    #[test]
1218    #[cfg(feature = "log")]
1219    fn test_problem_log_error() {
1220        loggerv::init_quiet().ok();
1221        let error: Result<(), _> = Err(Baz(Bar(Foo)));
1222        error.ok_or_log_error();
1223    }
1224
1225    #[test]
1226    #[cfg(feature = "log")]
1227    fn test_problem_log_warn() {
1228        loggerv::init_quiet().ok();
1229        let error: Result<(), _> = Err(Baz(Bar(Foo)));
1230        error.ok_or_log_warn();
1231    }
1232
1233    #[test]
1234    #[cfg(feature = "log")]
1235    fn test_problem_log_iter_error() {
1236        loggerv::init_quiet().ok();
1237        assert_eq!(
1238            vec![Ok(1), Err(Foo), Err(Foo), Ok(2), Err(Foo), Ok(3)]
1239                .into_iter()
1240                .ok_or_log_error()
1241                .flatten()
1242                .collect::<Vec<_>>(),
1243            vec![1, 2, 3]
1244        );
1245    }
1246
1247    #[test]
1248    #[cfg(feature = "log")]
1249    fn test_problem_log_iter_warn() {
1250        loggerv::init_quiet().ok();
1251        assert_eq!(
1252            vec![Ok(1), Err(Foo), Err(Foo), Ok(2), Err(Foo), Ok(3)]
1253                .into_iter()
1254                .ok_or_log_warn()
1255                .flatten()
1256                .collect::<Vec<_>>(),
1257            vec![1, 2, 3]
1258        );
1259    }
1260}