cli_failure/lib.rs
1/*!
2# cli-failure
3
4Provides `Failure(String)` implementing `std::error::Error`. Includes convenience macros making it perfect for usage with `wrap-match` in CLIs.
5
6**This crate should not be used in libraries.** Instead, use something like [`thiserror`](https://docs.rs/thiserror). For libraries, it is much better to have specific errors so library users can
7handle them better.
8
9[Documentation](https://docs.rs/cli-failure) | [crates.io](https://crates.io/crates/cli-failure)
10
11## Example
12
13```
14// wrap-match is not required, but it is highly recommended
15fn example() -> Result<(), Box<dyn Error>> {
16 let result = "bad";
17 // With convenience macros
18
19 // These two lines are the same
20 bail!("something {result} happened");
21 return failure!("something {result} happened");
22
23 return failure_raw!("something {result} happened").err_boxed();
24 return Err(failure_raw!("something {result} happened").boxed());
25 // Manually
26 return Failure(format!("something {result} happened")).err_boxed();
27 return Err(Failure(format!("something {result} happened")).boxed());
28 Ok(())
29}
30```
31 */
32
33use std::error::Error;
34use std::fmt::{Debug, Display};
35
36#[macro_export]
37/// Returns a [`Failure`] after calling [`Failure::err_boxed()`]. Any arguments to this macro will be passed to `format_args!`, allowing formatting specifiers to be used.
38///
39/// See [module level documentation](crate) for more docs
40macro_rules! bail {
41 ($($args:tt)*) => {
42 return $crate::failure!($($args)*)
43 }
44}
45
46#[macro_export]
47/// Constructs a [`Failure`] and calls [`Failure::err_boxed()`]. Any arguments to this macro will be passed to `format_args!`, allowing formatting specifiers to be used.
48///
49/// See [module level documentation](crate) for more docs
50macro_rules! failure {
51 ($($args:tt)*) => {
52 $crate::Failure(::std::fmt::format(::std::format_args!($($args)*))).err_boxed()
53 }
54}
55
56#[macro_export]
57/// Constructs a [`Failure`]. Any arguments to this macro will be passed to `format_args!`, allowing formatting specifiers to be used.
58///
59/// See [module level documentation](crate) for more docs
60macro_rules! failure_raw {
61 ($($args:tt)*) => {
62 $crate::Failure(::std::fmt::format(::std::format_args!($($args)*)))
63 }
64}
65
66/// It is recommend to use [`failure!`] or [`failure_raw!`] to construct a `Failure` as this saves typing `"...".to_owned()` or `format!("...")`.
67///
68/// See [module level documentation](crate) for more docs
69pub struct Failure(pub String);
70
71impl Failure {
72 #[inline]
73 /// Transforms the `Failure` into a `Box<dyn Error>`
74 pub fn boxed(self) -> Box<dyn Error> {
75 Box::new(self) as Box<dyn Error>
76 }
77
78 #[inline]
79 /// Transforms the `Failure` into a `Result::Err(E)` when `E` implements `From<Box<dyn Error>>`
80 pub fn err_boxed<T, E: From<Box<dyn Error>>>(self) -> Result<T, E> {
81 Err(self.boxed().into())
82 }
83
84 #[inline]
85 /// Transforms the `Failure` into a `Result::Err(Failure)`
86 pub fn err<T>(self) -> Result<T, Self> {
87 Err(self)
88 }
89}
90
91impl Error for Failure {}
92
93impl Display for Failure {
94 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95 Display::fmt(&self.0, f)
96 }
97}
98
99impl Debug for Failure {
100 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
101 Debug::fmt(&self.0, f)
102 }
103}
104
105#[cfg(test)]
106#[test]
107#[allow(
108 unreachable_code,
109 unused_variables // since result is used in unreachable code the compiler says it's unused
110)]
111fn tests() {
112 assert_eq!(Failure("a".into()).to_string(), "a".to_owned());
113 fn failure() -> Result<(), Failure> {
114 let result = "bad";
115 return Failure("test".to_owned()).err();
116 return Err(Failure("test".to_owned()));
117 return failure_raw!("{result}").err();
118 return Err(failure_raw!("{result}"));
119 Ok(())
120 }
121 failure().unwrap_err();
122
123 fn box_dyn_error() -> Result<(), Box<dyn Error>> {
124 let result = "bad";
125 return Failure("test".to_owned()).err_boxed();
126 return Err(Failure("test".to_owned()).boxed());
127 bail!("{result}");
128 return failure!("{result}");
129 return failure_raw!("{}", result).err_boxed();
130 return Err(failure_raw!("{result}").boxed());
131 Ok(())
132 }
133 box_dyn_error().unwrap_err();
134}