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}