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# Converting `Problem` into type implementing `std::error::Error` trait
237Use `Problem::into_error()` or `.into_error()` on `Result` type to convert `Problem` into type implementing `std::error::Error`.
238This allows to return erors to layers that require `Error` trait implementation for errors.
239
240```rust
241use problem::prelude::*;
242
243fn foo<E: std::error::Error>(err: E) {
244    assert_eq!(err.to_string(), "while bar got error caused by: foo");
245}
246
247foo(Problem::from("foo").problem_while("bar").into_error());
248```
249
250```rust
251use problem::prelude::*;
252
253struct LibError(String);
254
255impl<E: std::error::Error> From<E> for LibError {
256    fn from(e: E) -> LibError {
257        LibError(e.to_string())
258    }
259}
260
261fn foo() -> Result<(), LibError> {
262    problem!("foo").problem_while("bar").into_error()?;
263    Ok(())
264}
265
266assert_eq!(foo().unwrap_err().0.to_string(), "while bar got error caused by: foo");
267```
268
269# Aborting program on `Problem`
270`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.
271
272This library provides function `format_panic_to_stderr()` to set up hook that will use `eprintln!("{}", message)` to report panics.
273Alternatively 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.
274
275Panic hooks will produce backtrace of panic site if enabled via `RUST_BACKTRACE=1` environment variable along of the `Problem` object backtrace collected
276at object construction site.
277
278```noformat
279ERROR: Panicked in libcore/slice/mod.rs:2334:5: index 18492 out of range for slice of length 512
280```
281
282## Panicking on `Result` with `Problem`
283Similarly 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
284message and error converted to `Problem` to format the error message with `Display` trait.
285
286```rust,should_panic
287use problem::prelude::*;
288use problem::format_panic_to_stderr;
289
290format_panic_to_stderr();
291
292// Prints message:
293let _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
294```
295
296## Panicking on `Option`
297Similarly 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.
298
299```rust,should_panic
300use problem::prelude::*;
301use problem::format_panic_to_stderr;
302
303format_panic_to_stderr();
304let nothing: Option<&'static str> = None;
305
306let _s = nothing.or_failed_to("get something"); // Failed to get something
307```
308
309## Panicking on iterators of `Result`
310Method `.or_failed_to(message)` can be used to abort the program via `panic!()` with formatted message on iterators with `Result` item when first `Err`
311is encountered otherwise unwrapping the `Ok` value.
312The error type will be converted to `Problem` just before panicing.
313
314```rust,should_panic
315use problem::prelude::*;
316use problem::format_panic_to_stderr;
317
318format_panic_to_stderr();
319
320let results = vec![Ok(1u32), Ok(2u32), Err("oops")];
321
322let _ok: Vec<u32> = results.into_iter()
323    .or_failed_to("collect numbers")
324    .collect(); // Failed to collect numbers due to: oops
325```
326
327# Main function exit with error message and custom status
328`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.
329
330```rust,should_panic
331use problem::prelude::*;
332
333fn main() -> FinalResult {
334    // Prints "I'm sorry Dave, I'm afraid I can't do that" and exits with status 1
335    Err(Problem::from_error("I'm sorry Dave, I'm afraid I can't do that"))?;
336
337    // Prints "I'm sorry Dave, I'm afraid I can't do that" and exits with status 42
338    Err(Problem::from_error("I'm sorry Dave, I'm afraid I can't do that")).fatal_with_status(42)?;
339
340    Ok(())
341}
342```
343
344# Logging errors
345If `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
346`Result` into `Option` while logging `Err` wariants as warnings or errors.
347
348When used on iterators `.flatten()` addaptor can be used to filter out all `Err` variant items after they were logged and converted to `None`.
349
350```rust
351use problem::prelude::*;
352
353# #[cfg(feature = "log")]
354# fn test_with_log_feature() {
355let results = vec![Ok(1u32), Ok(2), Err("oops"), Ok(3), Err("oh"), Ok(4)];
356
357// Logs warning message: Continuing with error oops
358// Logs warning message: Continuing with error oh
359let ok: Vec<u32> = results.into_iter()
360    .ok_or_log_warn()
361    .flatten()
362    .collect();
363
364assert_eq!(ok.as_slice(), [1, 2, 3, 4]);
365# }
366#
367# #[cfg(feature = "log")]
368# test_with_log_feature();
369```
370
371# Backtraces
372When compiled with `backtrace` feature (default) formatting of backtraces for `Problem` cause and `panic!` locations can be enabled via
373`RUST_BACKTRACE=1` environment variable.
374
375```noformat
376Fatal 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!
377--- Cause
378   0: backtrace::backtrace::trace_unsynchronized::h936094cb968a67c2
379             at /Users/jpastuszek/.cargo/registry/src/github.com-1ecc6299db9ec823/backtrace-0.3.13/src/backtrace/mod.rs:57
380   1: problem::Problem::from_error::hfdbc5afef77017de
381             at /Users/jpastuszek/Documents/problem/src/lib.rs:435
382   2: <problem::Problem as core::convert::From<E>>::from::h3b5fdbec33645197
383             at /Users/jpastuszek/Documents/problem/src/lib.rs:500
384   3: <T as core::convert::Into<U>>::into::h37311b4bc5720d6d
385             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/libcore/convert.rs:455
386   4: <core::result::Result<O, E> as problem::ProblemWhile>::problem_while::{{closure}}::h97ad232ce9ba14fb
387             at /Users/jpastuszek/Documents/problem/src/lib.rs:617
388   5: <core::result::Result<T, E>>::map_err::he22546342a0a16ff
389             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/libcore/result.rs:530
390   6: <core::result::Result<O, E> as problem::ProblemWhile>::problem_while::he5e05f693d81f439
391             at /Users/jpastuszek/Documents/problem/src/lib.rs:617
392   7: problem::tests::test_panic_format_stderr_problem::he7f271034488edfe
393             at /Users/jpastuszek/Documents/problem/src/lib.rs:1053
394   8: problem::tests::test_panic_format_stderr_problem::{{closure}}::hd06e112465364a39
395             at /Users/jpastuszek/Documents/problem/src/lib.rs:1051
396   9: core::ops::function::FnOnce::call_once::hb512c264f284bc3f
397             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/libcore/ops/function.rs:238
398  10: <F as alloc::boxed::FnBox<A>>::call_box::h0b961cc85c049bee
399             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/liballoc/boxed.rs:673
400  11: ___rust_maybe_catch_panic
401             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/libpanic_unwind/lib.rs:102
402  12: std::sys_common::backtrace::__rust_begin_short_backtrace::h07f538384f587585
403             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/libtest/lib.rs:1426
404  13: std::panicking::try::do_call::h4953be8a0738d6ec
405             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/libstd/panicking.rs:310
406  14: ___rust_maybe_catch_panic
407             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/libpanic_unwind/lib.rs:102
408  15: <F as alloc::boxed::FnBox<A>>::call_box::h5a5d3b35dc8857f3
409             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/libstd/thread/mod.rs:476
410  16: std::sys::unix::thread::Thread::new::thread_start::hb66fd5e16b37cfd7
411             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/libstd/sys_common/thread.rs:24
412  17: __pthread_body
413  18: __pthread_start
414--- Panicked
415   0: backtrace::backtrace::trace_unsynchronized::h936094cb968a67c2
416             at /Users/jpastuszek/.cargo/registry/src/github.com-1ecc6299db9ec823/backtrace-0.3.13/src/backtrace/mod.rs:57
417   1: problem::format_panic_to_stderr::{{closure}}::h24ae835f59658f26
418             at /Users/jpastuszek/Documents/problem/src/lib.rs:868
419   2: std::panicking::rust_panic_with_hook::h3fe6a67edb032589
420             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/libstd/panicking.rs:495
421   3: std::panicking::continue_panic_fmt::hf7169aba6b1afe9c
422             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/libstd/panicking.rs:398
423   4: std::panicking::begin_panic_fmt::hb5f6d46d54559b8a
424             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/libstd/panicking.rs:353
425   5: <core::result::Result<O, E> as problem::FailedTo<O>>::or_failed_to::{{closure}}::h8e0b5d62111b80f4
426             at /Users/jpastuszek/Documents/problem/src/lib.rs:657
427   6: <core::result::Result<T, E>>::unwrap_or_else::h8bcc063ecb00981e
428             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/libcore/result.rs:774
429   7: <core::result::Result<O, E> as problem::FailedTo<O>>::or_failed_to::h2e59dbec5efe6366
430             at /Users/jpastuszek/Documents/problem/src/lib.rs:657
431   8: problem::tests::test_panic_format_stderr_problem::he7f271034488edfe
432             at /Users/jpastuszek/Documents/problem/src/lib.rs:1058
433   9: problem::tests::test_panic_format_stderr_problem::{{closure}}::hd06e112465364a39
434             at /Users/jpastuszek/Documents/problem/src/lib.rs:1051
435  10: core::ops::function::FnOnce::call_once::hb512c264f284bc3f
436             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/libcore/ops/function.rs:238
437  11: <F as alloc::boxed::FnBox<A>>::call_box::h0b961cc85c049bee
438             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/liballoc/boxed.rs:673
439  12: ___rust_maybe_catch_panic
440             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/libpanic_unwind/lib.rs:102
441  13: std::sys_common::backtrace::__rust_begin_short_backtrace::h07f538384f587585
442             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/libtest/lib.rs:1426
443  14: std::panicking::try::do_call::h4953be8a0738d6ec
444             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/libstd/panicking.rs:310
445  15: ___rust_maybe_catch_panic
446             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/libpanic_unwind/lib.rs:102
447  16: <F as alloc::boxed::FnBox<A>>::call_box::h5a5d3b35dc8857f3
448             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/libstd/thread/mod.rs:476
449  17: std::sys::unix::thread::Thread::new::thread_start::hb66fd5e16b37cfd7
450             at /rustc/9fda7c2237db910e41d6a712e9a2139b352e558b/src/libstd/sys_common/thread.rs:24
451  18: __pthread_body
452  19: __pthread_start
453```
454
455## Access
456Formatted backtrace `&str` can be accessed via `Problem::backtrace` function that will return `Some` if `backtrace` feature is enabled and `RUST_BACKTRACE=1`
457environment variable is set.
458
459```rust
460use problem::prelude::*;
461
462Problem::from_error("foo").backtrace(); // Some("   0: backtrace...")
463```
464
465# [Send] and [Sync]
466[Problem] will implement [Send] and [Sync] traits when feature `send-sync` is enabled.
467This feature is disabled by default to allow `!Sync` or `!Send` error types to be wrapped by [Problem].
468 */
469#[cfg(feature = "log")]
470#[macro_use]
471extern crate log;
472use std::fmt::{self, Display, Write};
473use std::panic;
474
475const DEFAULT_FATAL_STATUS: i32 = 1;
476
477/// Includes `Problem` type and related conversion traits and `in_context_of*` functions
478pub mod prelude {
479    pub use super::{
480        in_context_of, in_context_of_with, problem, FailedTo, FailedToIter, Fatal, FatalProblem,
481        IntoError, MapProblem, MapProblemOr, OkOrProblem, Problem, ProblemWhile,
482    };
483
484    pub use super::result::FinalResult;
485    // Note that `result::Result` is not part of prelude as it may conflict with standard library or
486    // custom library result types.
487
488    #[cfg(feature = "log")]
489    pub use super::logged::{OkOrLog, OkOrLogIter};
490}
491
492#[cfg(feature = "send-sync")]
493type DynError = dyn std::error::Error + Send + Sync;
494#[cfg(not(feature = "send-sync"))]
495type DynError = dyn std::error::Error;
496
497/// Wraps error, context and backtrace information and formats it for display.
498/// Data is heap allocated to avoid type parameters or lifetimes.
499#[derive(Debug)]
500pub struct Problem {
501    error: Box<DynError>,
502    context: Vec<String>,
503    backtrace: Option<String>,
504}
505
506impl Problem {
507    /// Create `Problem` from types implementing `Into<Box<dyn Error>>` (including `String` and `&str`) so that `Error::cause`
508    /// chain is followed through in the `Display` message
509    pub fn from_error(error: impl Into<Box<DynError>>) -> Problem {
510        Problem {
511            error: error.into(),
512            context: Vec::new(),
513            backtrace: format_backtrace(),
514        }
515    }
516
517    /// Same as `Problem::from_error` but stores only final message as `String` and does not take ownership of the error
518    pub fn from_error_message(error: &impl std::error::Error) -> Problem {
519        let mut message = String::new();
520        write_error_message(error, &mut message).unwrap();
521
522        Problem {
523            error: message.into(),
524            context: Vec::new(),
525            backtrace: format_backtrace(),
526        }
527    }
528
529    /// Get backtrace associated with this `Problem` instance if available
530    pub fn backtrace(&self) -> Option<&str> {
531        self.backtrace.as_ref().map(String::as_str)
532    }
533
534    /// Wraps Problem into type implementing Error trait
535    pub fn into_error(self) -> Error {
536        Error(self)
537    }
538}
539
540#[allow(deprecated)]
541fn write_error_message(error: &dyn std::error::Error, w: &mut impl Write) -> fmt::Result {
542    write!(w, "{}", error)?;
543
544    let mut error_cause = error;
545    loop {
546        // Note: using Error::cause() here to be backward compatible with older errors
547        if let Some(cause) = error_cause.cause() {
548            write!(w, "; caused by: {}", cause)?;
549            error_cause = cause;
550        } else {
551            break;
552        }
553    }
554    Ok(())
555}
556
557impl Display for Problem {
558    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
559        if let Some(context) = self.context.last() {
560            write!(f, "while {}", context)?;
561        }
562        for context in self.context.iter().rev().skip(1) {
563            write!(f, ", while {}", context)?;
564        }
565        if !self.context.is_empty() {
566            write!(f, " got error caused by: ")?;
567        }
568
569        write_error_message(self.error.as_ref(), f)?;
570
571        if let Some(backtrace) = self.backtrace.as_ref() {
572            write!(f, "\n--- Cause\n{}", backtrace)?;
573        }
574
575        Ok(())
576    }
577}
578
579/// Every type implementing `Into<Box<dyn Error>>` trait (including `String` and `&str` types) can be converted to `Problem` via `?` operator
580impl<E> From<E> for Problem
581where
582    E: Into<Box<DynError>>,
583{
584    fn from(error: E) -> Problem {
585        Problem::from_error(error)
586    }
587}
588
589/// Construct `Err` variant of `Result` containing `Problem` with given message formatted with
590/// `format!` macro.
591#[macro_export]
592macro_rules! problem {
593    ($ ($ arg : tt) *) => { Err(Problem::from_error(format!($($arg)*))) };
594}
595
596/// This error type is meant to be used as `main()` result error. It implements `Debug` display so
597/// that the program can terminate with nice message formatted with `Problem` and custom exit
598/// status.
599pub struct FatalProblem {
600    status: i32,
601    problem: Problem,
602}
603
604impl From<Problem> for FatalProblem {
605    fn from(problem: Problem) -> FatalProblem {
606        FatalProblem {
607            status: DEFAULT_FATAL_STATUS,
608            problem,
609        }
610    }
611}
612
613impl<E> From<E> for FatalProblem
614where
615    E: Into<Box<DynError>>,
616{
617    fn from(error: E) -> FatalProblem {
618        FatalProblem {
619            status: DEFAULT_FATAL_STATUS,
620            problem: Problem::from_error(error),
621        }
622    }
623}
624
625impl fmt::Debug for FatalProblem {
626    fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result {
627        eprintln!("{}", self.problem);
628        std::process::exit(self.status)
629    }
630}
631
632/// Extension trait to map `Option` to `Result` with `Problem`
633pub trait Fatal<O> {
634    fn fatal(self) -> Result<O, FatalProblem>;
635    fn fatal_with_status(self, status: i32) -> Result<O, FatalProblem>;
636}
637
638/// Extension trait that allow to convert to `Result` with `FatalProblem`
639impl<O> Fatal<O> for Result<O, Problem> {
640    /// Converts to `Result` with `FatalProblem` and default exit status
641    fn fatal(self) -> Result<O, FatalProblem> {
642        self.fatal_with_status(DEFAULT_FATAL_STATUS)
643    }
644
645    /// Converts to `Result` with `FatalProblem` and given exit status
646    fn fatal_with_status(self, status: i32) -> Result<O, FatalProblem> {
647        self.map_err(|problem| FatalProblem { status, problem })
648    }
649}
650
651pub mod result {
652    //! Handy Result types using `Problem` as error.
653    use super::{FatalProblem, Problem};
654
655    /// `Result` with `Problem`
656    pub type Result<T> = std::result::Result<T, Problem>;
657
658    /// `Result` with `FatalProblem` to be used as `main()` return value
659    pub type FinalResult = std::result::Result<(), FatalProblem>;
660}
661
662/// Map type containing error to type containing `Problem`
663pub trait MapProblem {
664    type ProblemCarrier;
665    fn map_problem(self) -> Self::ProblemCarrier;
666}
667
668/// Mapping `Result` with error to `Result` with `Problem`
669impl<O, E> MapProblem for Result<O, E>
670where
671    E: Into<Problem>,
672{
673    type ProblemCarrier = Result<O, Problem>;
674    fn map_problem(self) -> Result<O, Problem> {
675        self.map_err(|e| e.into())
676    }
677}
678
679/// Map type not containing any error to type containing given `Problem`
680pub trait MapProblemOr {
681    type ProblemCarrier;
682    fn map_problem_or(self, problem: impl Into<Problem>) -> Self::ProblemCarrier;
683    fn map_problem_or_else<F, P>(self, problem: F) -> Self::ProblemCarrier
684    where
685        F: FnOnce() -> P,
686        P: Into<Problem>;
687}
688
689/// Mapping `Result` with `Option<E>` to `Result` with `Problem` where `E` implements `Into<Problem>`
690impl<O, E> MapProblemOr for Result<O, Option<E>>
691where
692    E: Into<Problem>,
693{
694    type ProblemCarrier = Result<O, Problem>;
695
696    fn map_problem_or(self, problem: impl Into<Problem>) -> Result<O, Problem> {
697        self.map_err(|e| e.map(Into::into).unwrap_or_else(|| problem.into()))
698    }
699
700    fn map_problem_or_else<F, P>(self, problem: F) -> Self::ProblemCarrier
701    where
702        F: FnOnce() -> P,
703        P: Into<Problem>,
704    {
705        self.map_err(|e| e.map(Into::into).unwrap_or_else(|| problem().into()))
706    }
707}
708
709/// Extension trait to map `Option` to `Result` with `Problem`
710pub trait OkOrProblem<O> {
711    fn ok_or_problem<P>(self, problem: P) -> Result<O, Problem>
712    where
713        P: Into<Problem>;
714    fn ok_or_problem_with<F, P>(self, problem: F) -> Result<O, Problem>
715    where
716        F: FnOnce() -> P,
717        P: Into<Problem>;
718}
719
720impl<O> OkOrProblem<O> for Option<O> {
721    fn ok_or_problem<P>(self, problem: P) -> Result<O, Problem>
722    where
723        P: Into<Problem>,
724    {
725        self.ok_or_else(|| problem.into())
726    }
727
728    fn ok_or_problem_with<F, P>(self, problem: F) -> Result<O, Problem>
729    where
730        F: FnOnce() -> P,
731        P: Into<Problem>,
732    {
733        self.ok_or_else(|| problem().into())
734    }
735}
736
737/// Convert to `Problem` if needed and add context to it
738pub trait ProblemWhile {
739    type WithContext;
740
741    /// Add context information
742    fn problem_while(self, message: impl ToString) -> Self::WithContext;
743
744    /// Add context information from function call
745    fn problem_while_with<F, M>(self, message: F) -> Self::WithContext
746    where
747        F: FnOnce() -> M,
748        M: ToString;
749}
750
751impl ProblemWhile for Problem {
752    type WithContext = Problem;
753
754    fn problem_while(mut self, message: impl ToString) -> Problem {
755        self.context.push(message.to_string());
756        self
757    }
758
759    fn problem_while_with<F, M>(self, message: F) -> Problem
760    where
761        F: FnOnce() -> M,
762        M: ToString,
763    {
764        self.problem_while(message())
765    }
766}
767
768impl<O, E> ProblemWhile for Result<O, E>
769where
770    E: Into<Problem>,
771{
772    type WithContext = Result<O, Problem>;
773
774    fn problem_while(self, message: impl ToString) -> Result<O, Problem> {
775        self.map_err(|err| err.into().problem_while(message))
776    }
777
778    fn problem_while_with<F, M>(self, message: F) -> Result<O, Problem>
779    where
780        F: FnOnce() -> M,
781        M: ToString,
782    {
783        self.map_err(|err| err.into().problem_while_with(message))
784    }
785}
786
787/// Executes closure with `problem_while` context
788pub fn in_context_of<O, B>(message: &str, body: B) -> Result<O, Problem>
789where
790    B: FnOnce() -> Result<O, Problem>,
791{
792    body().problem_while(message)
793}
794
795/// Executes closure with `problem_while_with` context
796pub fn in_context_of_with<O, F, M, B>(message: F, body: B) -> Result<O, Problem>
797where
798    F: FnOnce() -> M,
799    M: ToString,
800    B: FnOnce() -> Result<O, Problem>,
801{
802    body().problem_while_with(message)
803}
804
805/// Wraps `Problem` into type implementing `Error` trait.
806#[derive(Debug)]
807pub struct Error(Problem);
808
809impl Error {
810    /// Returns wrapped `Problem`
811    pub fn unwrap(self) -> Problem {
812        self.0
813    }
814}
815
816impl Display for Error {
817    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
818        self.0.fmt(f)
819    }
820}
821
822impl std::error::Error for Error {}
823
824pub trait IntoError {
825    type ErrorResult<E>;
826
827    /// Wrap into `Error` type implementing `std::error::Error` trait
828    fn into_error(self) -> Self::ErrorResult<Error>;
829}
830
831impl<O, E> IntoError for Result<O, E>
832where
833    E: Into<Problem>,
834{
835    type ErrorResult<EE> = Result<O, EE>;
836    fn into_error(self) -> Self::ErrorResult<Error> {
837        self.map_err(|err| Error(err.into()))
838    }
839}
840
841/// Extension of `Result` that allows program to panic with `Display` message on `Err` for fatal application errors that are not bugs
842pub trait FailedTo<O> {
843    fn or_failed_to(self, message: impl Display) -> O;
844}
845
846impl<O, E> FailedTo<O> for Result<O, E>
847where
848    E: Into<Problem>,
849{
850    fn or_failed_to(self, message: impl Display) -> O {
851        self.unwrap_or_else(|err| panic!("Failed to {} due to: {}", message, err.into()))
852    }
853}
854
855impl<O> FailedTo<O> for Option<O> {
856    fn or_failed_to(self, message: impl Display) -> O {
857        self.unwrap_or_else(|| panic!("Failed to {}", message))
858    }
859}
860
861/// Iterator that will panic on first error with message displaying `Display` formatted message
862pub struct ProblemIter<I, M> {
863    inner: I,
864    message: M,
865}
866
867impl<I, O, E, M> Iterator for ProblemIter<I, M>
868where
869    I: Iterator<Item = Result<O, E>>,
870    E: Into<Problem>,
871    M: Display,
872{
873    type Item = O;
874
875    fn next(&mut self) -> Option<Self::Item> {
876        self.inner.next().map(|res| res.or_failed_to(&self.message))
877    }
878}
879
880/// Convert `Iterator` of `Result<O, E>` to iterator of `O` and panic on first `E` with problem message
881pub trait FailedToIter<O, E, M>: Sized {
882    fn or_failed_to(self, message: M) -> ProblemIter<Self, M>;
883}
884
885impl<I, O, E, M> FailedToIter<O, E, M> for I
886where
887    I: Iterator<Item = Result<O, E>>,
888    E: Into<Problem>,
889    M: Display,
890{
891    fn or_failed_to(self, message: M) -> ProblemIter<Self, M> {
892        ProblemIter {
893            inner: self,
894            message,
895        }
896    }
897}
898
899#[cfg(feature = "log")]
900pub mod logged {
901    use super::*;
902    use log::{error, warn};
903
904    /// Extension of `Result` that allows program to log on `Err` with `Display` message for application errors that are not critical
905    pub trait OkOrLog<O> {
906        fn ok_or_log_warn(self) -> Option<O>;
907        fn ok_or_log_error(self) -> Option<O>;
908    }
909
910    impl<O, E> OkOrLog<O> for Result<O, E>
911    where
912        E: Into<Problem>,
913    {
914        fn ok_or_log_warn(self) -> Option<O> {
915            self.map_err(|err| warn!("Continuing with error: {}", err.into()))
916                .ok()
917        }
918
919        fn ok_or_log_error(self) -> Option<O> {
920            self.map_err(|err| error!("Continuing with error: {}", err.into()))
921                .ok()
922        }
923    }
924
925    /// Iterator that will log as warn `Display` formatted message on `Err` and skip to next item; it can be flattened to skip failed items
926    pub struct ProblemWarnLoggingIter<I> {
927        inner: I,
928    }
929
930    impl<I, O, E> Iterator for ProblemWarnLoggingIter<I>
931    where
932        I: Iterator<Item = Result<O, E>>,
933        E: Into<Problem>,
934    {
935        type Item = Option<O>;
936
937        fn next(&mut self) -> Option<Self::Item> {
938            self.inner.next().map(|res| res.ok_or_log_warn())
939        }
940    }
941
942    /// Iterator that will log as error `Display` formatted message on `Err` and skip to next item; it can be flattened to skip failed items
943    pub struct ProblemErrorLoggingIter<I> {
944        inner: I,
945    }
946
947    impl<I, O, E> Iterator for ProblemErrorLoggingIter<I>
948    where
949        I: Iterator<Item = Result<O, E>>,
950        E: Into<Problem>,
951    {
952        type Item = Option<O>;
953
954        fn next(&mut self) -> Option<Self::Item> {
955            self.inner.next().map(|res| res.ok_or_log_error())
956        }
957    }
958
959    /// Convert `Iterator` of `Result<O, E>` to iterator of `Option<O>` and log any `Err` variants
960    pub trait OkOrLogIter<O, E>: Sized {
961        fn ok_or_log_warn(self) -> ProblemWarnLoggingIter<Self>;
962        fn ok_or_log_error(self) -> ProblemErrorLoggingIter<Self>;
963    }
964
965    impl<I, O, E> OkOrLogIter<O, E> for I
966    where
967        I: Iterator<Item = Result<O, E>>,
968        E: Into<Problem>,
969    {
970        fn ok_or_log_warn(self) -> ProblemWarnLoggingIter<Self> {
971            ProblemWarnLoggingIter { inner: self }
972        }
973
974        fn ok_or_log_error(self) -> ProblemErrorLoggingIter<Self> {
975            ProblemErrorLoggingIter { inner: self }
976        }
977    }
978}
979
980#[cfg(not(feature = "backtrace"))]
981fn format_backtrace() -> Option<String> {
982    None
983}
984
985/* Use default Rust format like:
986   0: std::sys_common::backtrace::_print
987             at C:\projects\rust\src\libstd\sys_common\backtrace.rs:94
988   1: std::sys_common::backtrace::_print
989             at C:\projects\rust\src\libstd\sys_common\backtrace.rs:71
990*/
991#[cfg(feature = "backtrace")]
992#[inline(always)]
993fn format_backtrace() -> Option<String> {
994    if let Ok("1") = std::env::var("RUST_BACKTRACE").as_ref().map(String::as_str) {
995        let mut backtrace = String::new();
996        let mut frame_no: u32 = 0;
997
998        backtrace::trace(|frame| {
999            let ip = frame.ip();
1000
1001            if frame_no > 0 {
1002                backtrace.push_str("\n");
1003            }
1004
1005            backtrace::resolve(ip, |symbol| {
1006                if let Some(name) = symbol.name() {
1007                    write!(backtrace, "{:4}: {}", frame_no, name).unwrap();
1008                }
1009                if let (Some(filename), Some(lineno)) = (symbol.filename(), symbol.lineno()) {
1010                    write!(
1011                        backtrace,
1012                        "\n             at {}:{}",
1013                        filename.display(),
1014                        lineno
1015                    )
1016                    .unwrap();
1017                }
1018            });
1019
1020            frame_no += 1;
1021            true // keep going to the next frame
1022        });
1023
1024        Some(backtrace)
1025    } else {
1026        None
1027    }
1028}
1029
1030fn format_panic(panic: &std::panic::PanicHookInfo, backtrace: Option<String>) -> String {
1031    let mut message = String::new();
1032
1033    let thread = std::thread::current();
1034    let name = thread.name().unwrap_or("<unnamed>");
1035
1036    // taken from libstd
1037    let msg = match panic.payload().downcast_ref::<&'static str>() {
1038        Some(s) => *s,
1039        None => match panic.payload().downcast_ref::<String>() {
1040            Some(s) => &s[..],
1041            None => "Box<Any>",
1042        },
1043    };
1044
1045    match (backtrace.is_some(), panic.location()) {
1046        (true, Some(location)) => write!(
1047            message,
1048            "thread '{}' panicked at {} with: {}",
1049            name, location, msg
1050        )
1051        .ok(),
1052        (true, None) => write!(message, "thread '{}' panicked with: {}", name, msg).ok(),
1053        (false, _) => write!(message, "{}", msg).ok(),
1054    };
1055
1056    if let Some(backtrace) = backtrace {
1057        message.push_str("\n--- Panicked\n");
1058        message.push_str(&backtrace);
1059    }
1060
1061    message
1062}
1063
1064/// Set panic hook so that formats error message to `stderr` with more `Problem` friendly way
1065pub fn format_panic_to_stderr() {
1066    panic::set_hook(Box::new(|panic_info| {
1067        let backtrace = format_backtrace();
1068        eprintln!("Fatal error: {}", format_panic(panic_info, backtrace));
1069    }));
1070}
1071
1072/// Set panic hook so that when program panics it will log error massage with `error!` macro
1073#[cfg(feature = "log")]
1074pub fn format_panic_to_error_log() {
1075    panic::set_hook(Box::new(|panic_info| {
1076        let backtrace = format_backtrace();
1077        error!("{}", format_panic(panic_info, backtrace));
1078    }));
1079}
1080
1081#[cfg(test)]
1082mod tests {
1083    use super::prelude::*;
1084    use super::{format_panic_to_stderr, in_context_of};
1085    use std::error::Error;
1086    use std::fmt::{self, Display};
1087    use std::io;
1088
1089    #[derive(Debug)]
1090    struct Foo;
1091
1092    impl Display for Foo {
1093        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1094            write!(f, "Foo error")
1095        }
1096    }
1097
1098    impl Error for Foo {}
1099
1100    #[derive(Debug)]
1101    struct Bar(Foo);
1102
1103    impl Display for Bar {
1104        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1105            write!(f, "Bar error")
1106        }
1107    }
1108
1109    impl Error for Bar {
1110        fn source(&self) -> Option<&(dyn Error + 'static)> {
1111            Some(&self.0)
1112        }
1113    }
1114
1115    #[derive(Debug)]
1116    struct Baz(Bar);
1117
1118    impl Display for Baz {
1119        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1120            write!(f, "Baz error")
1121        }
1122    }
1123
1124    impl Error for Baz {
1125        fn source(&self) -> Option<&(dyn Error + 'static)> {
1126            Some(&self.0)
1127        }
1128    }
1129
1130    #[test]
1131    #[should_panic(expected = "Failed to test due to: foo: 1")]
1132    fn test_problem_macro() {
1133        let err: Result<(), Problem> = problem!("foo: {}", 1);
1134        err.or_failed_to("test");
1135    }
1136
1137    #[test]
1138    fn test_convertion() {
1139        let _: Problem = io::Error::new(io::ErrorKind::InvalidInput, "boom!").into();
1140        let _: Problem = "boom!".into(); // via impl<'a> From<&'a str> for Box<dyn Error>
1141    }
1142
1143    #[test]
1144    #[should_panic(
1145        expected = "Failed to complete processing task due to: while processing object, while processing input data, while parsing input got error caused by: boom!"
1146    )]
1147    fn test_integration() {
1148        Err(io::Error::new(io::ErrorKind::InvalidInput, "boom!"))
1149            .problem_while("parsing input")
1150            .problem_while("processing input data")
1151            .problem_while("processing object")
1152            .or_failed_to("complete processing task")
1153    }
1154
1155    #[test]
1156    #[should_panic(
1157        expected = "Failed to complete processing task due to: while processing object, while processing input data, while parsing input got error caused by: boom!"
1158    )]
1159    fn test_integration_message() {
1160        Err("boom!")
1161            .problem_while("parsing input")
1162            .problem_while("processing input data")
1163            .problem_while("processing object")
1164            .or_failed_to("complete processing task")
1165    }
1166
1167    #[test]
1168    #[should_panic(
1169        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"
1170    )]
1171    fn test_integration_cause_chain() {
1172        Err(Baz(Bar(Foo)))
1173            .problem_while("parsing input")
1174            .problem_while("processing input data")
1175            .problem_while("processing object")
1176            .or_failed_to("complete processing task")
1177    }
1178
1179    #[test]
1180    #[should_panic(
1181        expected = "Failed to complete processing task due to: while doing stuff got error caused by: boom!"
1182    )]
1183    fn test_in_context_of() {
1184        in_context_of("doing stuff", || {
1185            Err(io::Error::new(io::ErrorKind::InvalidInput, "boom!"))?
1186        })
1187        .or_failed_to("complete processing task")
1188    }
1189
1190    #[test]
1191    #[should_panic(expected = "Failed to foo due to: boom!")]
1192    fn test_result() {
1193        Err(io::Error::new(io::ErrorKind::InvalidInput, "boom!")).or_failed_to("foo")
1194    }
1195
1196    #[test]
1197    #[should_panic(
1198        expected = "Failed to quix due to: Baz error; caused by: Bar error; caused by: Foo error"
1199    )]
1200    fn test_result_cause_chain() {
1201        Err(Baz(Bar(Foo))).or_failed_to("quix")
1202    }
1203
1204    #[test]
1205    #[should_panic(
1206        expected = "Failed to quix due to: Baz error; caused by: Bar error; caused by: Foo error"
1207    )]
1208    fn test_result_cause_chain_message() {
1209        let error = Baz(Bar(Foo));
1210        Err(Problem::from_error_message(&error)).or_failed_to("quix")
1211    }
1212
1213    #[test]
1214    #[should_panic(expected = "Failed to foo")]
1215    fn test_option() {
1216        None.or_failed_to("foo")
1217    }
1218
1219    #[test]
1220    #[should_panic(expected = "Failed to foo due to: boom!")]
1221    fn test_option_errors() {
1222        Err(Some(io::Error::new(io::ErrorKind::InvalidInput, "boom!")))
1223            .map_problem_or("<unknown error>")
1224            .or_failed_to("foo")
1225    }
1226
1227    #[test]
1228    #[should_panic(expected = "Failed to foo due to: <unknown error>")]
1229    fn test_result_option_errors_unknown() {
1230        let err: Result<(), Option<io::Error>> = Err(None);
1231        err.map_problem_or("<unknown error>").or_failed_to("foo")
1232    }
1233
1234    #[test]
1235    #[should_panic(expected = "Failed to foo due to: nothing here")]
1236    fn test_result_ok_or_problem() {
1237        None.ok_or_problem("nothing here").or_failed_to("foo")
1238    }
1239
1240    #[test]
1241    #[should_panic(expected = "Failed to foo due to: omg!")]
1242    fn test_result_iter_or_failed_to() {
1243        let results = vec![Ok(1u32), Ok(2u32), Err("omg!")];
1244        let _ok = results.into_iter().or_failed_to("foo").collect::<Vec<_>>();
1245    }
1246
1247    #[test]
1248    #[should_panic]
1249    fn test_panic_format_stderr() {
1250        format_panic_to_stderr();
1251        panic!("foo bar!");
1252    }
1253
1254    #[test]
1255    #[should_panic]
1256    fn test_panic_format_stderr_problem() {
1257        format_panic_to_stderr();
1258        let result: Result<(), Problem> = Err(io::Error::new(io::ErrorKind::InvalidInput, "boom!"))
1259            .problem_while("parsing input")
1260            .problem_while("processing input data")
1261            .problem_while("processing object");
1262
1263        result.or_failed_to("complete processing task");
1264    }
1265
1266    #[test]
1267    #[should_panic]
1268    fn test_panic_format_stderr_unwrap() {
1269        format_panic_to_stderr();
1270        let result: Result<(), io::Error> =
1271            Err(io::Error::new(io::ErrorKind::InvalidInput, "boom!"));
1272        result.unwrap();
1273    }
1274
1275    #[test]
1276    #[should_panic]
1277    fn test_panic_format_stderr_expect() {
1278        format_panic_to_stderr();
1279        let result: Result<(), io::Error> =
1280            Err(io::Error::new(io::ErrorKind::InvalidInput, "boom!"));
1281        result.expect("foo");
1282    }
1283
1284    #[test]
1285    #[cfg(feature = "backtrace")]
1286    fn test_problem_backtrace() {
1287        let p = Problem::from_error("foo")
1288            .problem_while("bar")
1289            .problem_while("baz");
1290
1291        if let Ok("1") = std::env::var("RUST_BACKTRACE").as_ref().map(String::as_str) {
1292            assert!(p.backtrace().is_some());
1293            println!("{}", p.backtrace().unwrap());
1294        } else {
1295            assert!(p.backtrace().is_none());
1296        }
1297    }
1298
1299    #[test]
1300    #[cfg(feature = "log")]
1301    fn test_problem_log_error() {
1302        loggerv::init_quiet().ok();
1303        let error: Result<(), _> = Err(Baz(Bar(Foo)));
1304        error.ok_or_log_error();
1305    }
1306
1307    #[test]
1308    #[cfg(feature = "log")]
1309    fn test_problem_log_warn() {
1310        loggerv::init_quiet().ok();
1311        let error: Result<(), _> = Err(Baz(Bar(Foo)));
1312        error.ok_or_log_warn();
1313    }
1314
1315    #[test]
1316    #[cfg(feature = "log")]
1317    fn test_problem_log_iter_error() {
1318        loggerv::init_quiet().ok();
1319        assert_eq!(
1320            vec![Ok(1), Err(Foo), Err(Foo), Ok(2), Err(Foo), Ok(3)]
1321                .into_iter()
1322                .ok_or_log_error()
1323                .flatten()
1324                .collect::<Vec<_>>(),
1325            vec![1, 2, 3]
1326        );
1327    }
1328
1329    #[test]
1330    #[cfg(feature = "log")]
1331    fn test_problem_log_iter_warn() {
1332        loggerv::init_quiet().ok();
1333        assert_eq!(
1334            vec![Ok(1), Err(Foo), Err(Foo), Ok(2), Err(Foo), Ok(3)]
1335                .into_iter()
1336                .ok_or_log_warn()
1337                .flatten()
1338                .collect::<Vec<_>>(),
1339            vec![1, 2, 3]
1340        );
1341    }
1342
1343    #[test]
1344    #[cfg(feature = "send-sync")]
1345    fn test_problem_send_sync() {
1346        fn foo<S: Send + Sync>(_s: S) {}
1347        foo(Problem::from("foo"));
1348        assert!(true)
1349    }
1350}