windows_helpers/win32_app/
error.rs

1use super::msg_loop;
2use crate::windows;
3use std::cell::RefCell;
4use windows::Win32::UI::WindowsAndMessaging::PostQuitMessage;
5
6thread_local! {
7    static APP_ERROR: RefCell<Option<Box<dyn std::error::Error + Send + Sync>>> = RefCell::new(None);
8}
9
10pub fn set_app_error_if_absent<E>(error: E)
11where
12    E: Into<Box<dyn std::error::Error + Send + Sync>>,
13{
14    //! Sets a thread-local error that can be retrieved with [`take_app_error()`], if one wasn't set already.
15
16    APP_ERROR.with_borrow_mut(|app_error| {
17        if app_error.is_none() {
18            *app_error = Some(error.into());
19        }
20    });
21}
22
23pub fn clear_app_error() {
24    APP_ERROR.with_borrow_mut(|app_error| {
25        *app_error = None;
26    });
27}
28
29pub fn take_app_error() -> Option<Box<dyn std::error::Error + Send + Sync>> {
30    //! Clears the app error and returns it.
31
32    APP_ERROR.with_borrow_mut(|app_error| app_error.take())
33}
34
35// pub fn take_app_error_or<E>(error: E) -> Box<dyn std::error::Error + Send + Sync>
36// where
37//     E: Into<Box<dyn std::error::Error + Send + Sync>>,
38// {
39//     //! Clears the app error and returns it, or, if not present, the provided error.
40//
41//     take_app_error().unwrap_or(error.into())
42// }
43
44pub fn just_try<F, T, E>(action: F) -> Result<T, E>
45where
46    F: FnOnce() -> Result<T, E>,
47{
48    //! Just returns the `Result`, so you can easily gather `?` uses.
49
50    action()
51}
52
53pub fn try_or_set_app_error<F, T, E>(action: F) -> Option<T>
54where
55    F: FnOnce() -> Result<T, E>,
56    E: Into<Box<dyn std::error::Error + Send + Sync>>,
57{
58    //! Calls [`set_app_error_if_absent()`] on `Err`. Returns the `Ok` value in `Some`.
59
60    match action() {
61        Ok(t) => Some(t),
62        Err(error) => {
63            set_app_error_if_absent(error);
64            None
65        }
66    }
67}
68
69pub fn try_or_post_quit<F, T, E>(action: F) -> Option<T>
70where
71    F: FnOnce() -> Result<T, E>,
72    E: Into<Box<dyn std::error::Error + Send + Sync>>,
73{
74    //! Calls [`set_app_error_if_absent()`] and [`PostQuitMessage(1)`][1] on `Err`. Returns the `Ok` value in `Some`.
75    //!
76    //! [1]: https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-postquitmessage
77
78    let option = try_or_set_app_error(action);
79    if option.is_none() {
80        unsafe { PostQuitMessage(1) };
81    }
82    option
83}
84
85pub fn try_or_quit_now<F, T, E>(action: F) -> Option<T>
86where
87    F: FnOnce() -> Result<T, E>,
88    E: Into<Box<dyn std::error::Error + Send + Sync>>,
89{
90    //! Calls [`set_app_error_if_absent()`] and <code>[super::msg_loop::quit_now]\(1\)</code> on `Err`. Returns the `Ok` value in `Some`.
91
92    let option = try_or_set_app_error(action);
93    if option.is_none() {
94        msg_loop::quit_now(1);
95    }
96    option
97}
98
99pub fn try_or_panic<F, T, E>(action: F) -> T
100where
101    F: FnOnce() -> Result<T, E>,
102    E: Into<Box<dyn std::error::Error + Send + Sync>>,
103{
104    //! Panics on `Err`, with debug-stringified payload. Returns the `Ok` value.
105
106    action().unwrap_or_else(|e| {
107        let error: Box<dyn std::error::Error> = e.into();
108        panic!("{:?}", error);
109    })
110}
111
112pub fn try_then_favor_app_error<F, T, E>(
113    action: F,
114) -> Result<T, Box<dyn std::error::Error + Send + Sync>>
115where
116    F: FnOnce() -> Result<T, E>,
117    E: Into<Box<dyn std::error::Error + Send + Sync>>,
118{
119    //! After running the action, returns the error from [`take_app_error()`], or, if not present, the action's `Result`.
120    //!
121    //! Can be used with fundamental actions including running the message loop. Even when the actions don't fail, there might still be an app error.
122    //!
123    //! # Usage with `anyhow`
124    //! [As of Dec. 2023][1], you can't just use the `?` operator to convert a returned error to an `anyhow::Error`. Use `.map_err(|e| anyhow!(e))?` instead of `?`.
125    //!
126    //! [1]: https://github.com/dtolnay/anyhow/issues/83
127
128    let result = action();
129    if let Some(error) = take_app_error() {
130        Err(error)
131    } else {
132        result.map_err(|e| e.into())
133    }
134}