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}