failure_ext/
macros.rs

1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under both the MIT license found in the
5 * LICENSE-MIT file in the root directory of this source tree and the Apache
6 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
7 * of this source tree.
8 */
9
10/// Downcast matching.
11///
12/// Usage:
13/// ```
14/// # use anyhow::Error;
15/// # use std::fmt::{Debug, Display};
16/// # use failure_ext::err_downcast_ref;
17/// # fn foo<
18/// #      Type: Display + Debug + Send + Sync + 'static,
19/// #      YourType: Display + Debug + Send + Sync + 'static
20/// # >(err: Error) {
21/// let res = err_downcast_ref! {
22///    err,
23///    ty: Type => { /* use ty as &Type */ },
24///    yours: YourType => { /* use yours as &YourType */ },
25/// };
26/// # }
27/// # fn main() {}
28/// ```
29///
30/// Where `err` is a `&anyhow::Error`.
31/// When one of the type arms match, then it returns Some(value from expr), otherwise None.
32/// It's like downcast_ref but for multiple types.
33#[macro_export]
34macro_rules! err_downcast_ref {
35    // Base case - all patterns consumed
36    ( $err:expr ) => {
37        { let _ = $err; None }
38    };
39    // Eliminate trailing comma
40    ( $err:expr, $($v:ident : $ty:ty => $action:expr),* , ) => {
41        err_downcast_ref!($err, $($v : $ty => $action),*)
42    };
43    // Default case - match one type pattern, and recur with the rest of the list.
44    // The rest of the list consumes the , separating it from the first pattern and
45    // is itself comma-separated, with no trailing comma
46    ( $err:expr, $v:ident : $ty:ty => $action:expr $(, $rv:ident : $rty:ty => $raction:expr)* ) => {{
47        match $err.downcast_ref::<$ty>() {
48            Some($v) => Some($action),
49            None => err_downcast_ref!($err $(, $rv : $rty => $raction)*),
50        }
51    }};
52}
53
54/// Downcast matching.
55///
56/// Usage:
57/// ```
58/// # use anyhow::Error;
59/// # use std::fmt::{Debug, Display};
60/// # use failure_ext::err_downcast;
61/// # fn foo<
62/// #      Type: Display + Debug + Send + Sync + 'static,
63/// #      YourType: Display + Debug + Send + Sync + 'static
64/// # >(err: Error) {
65/// let res = err_downcast! {
66///    err,
67///    ty: Type => { /* use ty as Type */ },
68///    yours: YourType => { /* use yours as YourType */ },
69/// };
70/// # }
71/// # fn main() {}
72/// ```
73///
74/// Where `err` is a `anyhow::Error`.
75/// When one of the type arms match, then it returns Ok(value from expr), otherwise Err(err).
76/// It's like downcast but for multiple types.
77#[macro_export]
78macro_rules! err_downcast {
79    // Base case - all patterns consumed
80    ( $err:expr ) => {
81        Err($err)
82    };
83    // Eliminate trailing comma
84    ( $err:expr, $($v:ident : $ty:ty => $action:expr),* , ) => {
85        err_downcast!($err, $($v : $ty => $action),*)
86    };
87    // Default case - match one type pattern, and recur with the rest of the list.
88    // The rest of the list consumes the , separating it from the first pattern and
89    // is itself comma-separated, with no trailing comma
90    ( $err:expr, $v:ident : $ty:ty => $action:expr $(, $rv:ident : $rty:ty => $raction:expr)* ) => {{
91        match $err.downcast::<$ty>() {
92            Ok($v) => Ok($action),
93            Err(other) => err_downcast!(other $(, $rv : $rty => $raction)*),
94        }
95    }};
96}
97
98#[allow(clippy::disallowed_names)]
99#[cfg(test)]
100mod test {
101    use anyhow::Error;
102    use thiserror::Error;
103
104    #[derive(Error, Debug)]
105    #[error("Foo badness")]
106    struct Foo;
107    #[derive(Error, Debug)]
108    #[error("Bar badness")]
109    struct Bar;
110    #[derive(Error, Debug)]
111    #[error("Blat badness")]
112    struct Blat;
113    #[derive(Error, Debug)]
114    #[error("Outer badness")]
115    struct Outer;
116
117    #[test]
118    fn downcast_ref_syntax() {
119        let blat = Error::from(Blat);
120
121        // Single, tailing ,
122        let _ = err_downcast_ref! {
123            blat,
124            v: Foo => v.to_string(),
125        };
126
127        // Single, no tailing ,
128        let _ = err_downcast_ref! {
129            blat,
130            v: Foo => v.to_string()
131        };
132
133        // Multi, tailing ,
134        let _ = err_downcast_ref! {
135            blat,
136            v: Foo => v.to_string(),
137            v: Blat => v.to_string(),
138        };
139
140        // Multi, no tailing ,
141        let _ = err_downcast_ref! {
142            blat,
143            v: Foo => v.to_string(),
144            v: Blat => v.to_string()
145        };
146    }
147
148    #[test]
149    fn downcast_ref_basic() {
150        let blat = Error::from(Blat);
151
152        let msg = err_downcast_ref! {
153            blat,
154            foo: Foo => foo.to_string(),
155            bar: Bar => bar.to_string(),
156            blat: Blat => blat.to_string(),
157            outer: Outer => outer.to_string(),
158        };
159
160        assert_eq!(msg.unwrap(), "Blat badness".to_string());
161    }
162
163    #[allow(clippy::cognitive_complexity)]
164    #[test]
165    fn downcast_ref_context() {
166        let foo = Error::from(Foo);
167        let outer = foo.context(Outer);
168
169        let msg1 = err_downcast_ref! {
170            outer,
171            foo: Foo => foo.to_string(), // expected
172            bar: Bar => bar.to_string(),
173            blat: Blat => blat.to_string(),
174            outer: Outer => outer.to_string(),
175        };
176        let msg2 = err_downcast_ref! {
177            outer,
178            blat: Blat => blat.to_string(),
179            outer: Outer => outer.to_string(), // expected
180            foo: Foo => foo.to_string(),
181            bar: Bar => bar.to_string(),
182        };
183
184        assert_eq!(msg1.unwrap(), "Foo badness".to_string());
185        assert_eq!(msg2.unwrap(), "Outer badness".to_string());
186    }
187
188    #[test]
189    fn downcast_ref_miss() {
190        let blat = Error::from(Blat);
191
192        let msg = err_downcast_ref! {
193            blat,
194            v: Foo => { let _: &Foo = v; v.to_string() },
195            v: Bar => { let _: &Bar = v; v.to_string() },
196        };
197
198        assert!(msg.is_none());
199        assert!(blat.downcast_ref::<Blat>().is_some());
200    }
201
202    #[test]
203    fn downcast_syntax() {
204        // Single, tailing ,
205        let blat = Error::from(Blat);
206        let _ = err_downcast! {
207            blat,
208            v: Foo => v.to_string(),
209        };
210
211        // Single, no tailing ,
212        let blat = Error::from(Blat);
213        let _ = err_downcast! {
214            blat,
215            v: Foo => v.to_string()
216        };
217
218        // Multi, tailing ,
219        let blat = Error::from(Blat);
220        let _ = err_downcast! {
221            blat,
222            v: Foo => v.to_string(),
223            v: Blat => v.to_string(),
224        };
225
226        // Multi, no tailing ,
227        let blat = Error::from(Blat);
228        let _ = err_downcast! {
229            blat,
230            v: Foo => v.to_string(),
231            v: Blat => v.to_string()
232        };
233    }
234
235    #[test]
236    fn downcast_basic() {
237        let blat = Error::from(Blat);
238
239        let msg = err_downcast! {
240            blat,
241            foo: Foo => foo.to_string(),
242            bar: Bar => bar.to_string(),
243            blat: Blat => blat.to_string(),
244            outer: Outer => outer.to_string(),
245        };
246
247        assert_eq!(msg.unwrap(), "Blat badness".to_string());
248    }
249
250    #[test]
251    fn downcast_context() {
252        let foo = Error::from(Foo);
253        let outer = foo.context(Outer);
254
255        let msg = err_downcast! {
256            outer,
257            v: Foo => { let _: Foo = v; v.to_string() },
258            v: Bar => { let _: Bar = v; v.to_string() },
259            v: Blat => { let _: Blat = v; v.to_string() },
260            v: Outer => { let _: Outer = v; v.to_string() },
261        };
262
263        assert_eq!(msg.unwrap(), "Foo badness".to_string());
264    }
265
266    #[test]
267    fn downcast_miss() {
268        let blat = Error::from(Blat);
269
270        let msg = err_downcast! {
271            blat,
272            foo: Foo => foo.to_string(),
273            bar: Bar => bar.to_string(),
274            outer: Outer => outer.to_string(),
275        };
276
277        assert!(msg.is_err());
278        assert!(msg.unwrap_err().downcast::<Blat>().is_ok());
279    }
280}