1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
use super::msg_loop;
use crate::windows;
use std::cell::RefCell;
use windows::Win32::UI::WindowsAndMessaging::PostQuitMessage;

thread_local! {
    static APP_ERROR: RefCell<Option<Box<dyn std::error::Error + Send + Sync>>> = RefCell::new(None);
}

pub fn set_app_error_if_absent<E>(error: E)
where
    E: Into<Box<dyn std::error::Error + Send + Sync>>,
{
    //! Sets a thread-local error that can be retrieved with [`take_app_error()`], if one wasn't set already.

    APP_ERROR.with_borrow_mut(|app_error| {
        if app_error.is_none() {
            *app_error = Some(error.into());
        }
    });
}

pub fn clear_app_error() {
    APP_ERROR.with_borrow_mut(|app_error| {
        *app_error = None;
    });
}

pub fn take_app_error() -> Option<Box<dyn std::error::Error + Send + Sync>> {
    //! Clears the app error and returns it.

    APP_ERROR.with_borrow_mut(|app_error| app_error.take())
}

// pub fn take_app_error_or<E>(error: E) -> Box<dyn std::error::Error + Send + Sync>
// where
//     E: Into<Box<dyn std::error::Error + Send + Sync>>,
// {
//     //! Clears the app error and returns it, or, if not present, the provided error.
//
//     take_app_error().unwrap_or(error.into())
// }

pub fn just_try<F, T, E>(action: F) -> Result<T, E>
where
    F: FnOnce() -> Result<T, E>,
{
    //! Just returns the `Result`, so you can easily gather `?` uses.

    action()
}

pub fn try_or_set_app_error<F, T, E>(action: F) -> Option<T>
where
    F: FnOnce() -> Result<T, E>,
    E: Into<Box<dyn std::error::Error + Send + Sync>>,
{
    //! Calls [`set_app_error_if_absent()`] on `Err`. Returns the `Ok` value in `Some`.

    match action() {
        Ok(t) => Some(t),
        Err(error) => {
            set_app_error_if_absent(error);
            None
        }
    }
}

pub fn try_or_post_quit<F, T, E>(action: F) -> Option<T>
where
    F: FnOnce() -> Result<T, E>,
    E: Into<Box<dyn std::error::Error + Send + Sync>>,
{
    //! Calls [`set_app_error_if_absent()`] and [`PostQuitMessage(1)`][1] on `Err`. Returns the `Ok` value in `Some`.
    //!
    //! [1]: https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-postquitmessage

    let option = try_or_set_app_error(action);
    if option.is_none() {
        unsafe { PostQuitMessage(1) };
    }
    option
}

pub fn try_or_quit_now<F, T, E>(action: F) -> Option<T>
where
    F: FnOnce() -> Result<T, E>,
    E: Into<Box<dyn std::error::Error + Send + Sync>>,
{
    //! Calls [`set_app_error_if_absent()`] and <code>[super::msg_loop::quit_now]\(1\)</code> on `Err`. Returns the `Ok` value in `Some`.

    let option = try_or_set_app_error(action);
    if option.is_none() {
        msg_loop::quit_now(1);
    }
    option
}

pub fn try_or_panic<F, T, E>(action: F) -> T
where
    F: FnOnce() -> Result<T, E>,
    E: Into<Box<dyn std::error::Error + Send + Sync>>,
{
    //! Panics on `Err`, with debug-stringified payload. Returns the `Ok` value.

    action().unwrap_or_else(|e| {
        let error: Box<dyn std::error::Error> = e.into();
        panic!("{:?}", error);
    })
}

pub fn try_then_favor_app_error<F, T, E>(
    action: F,
) -> Result<T, Box<dyn std::error::Error + Send + Sync>>
where
    F: FnOnce() -> Result<T, E>,
    E: Into<Box<dyn std::error::Error + Send + Sync>>,
{
    //! After running the action, returns the error from [`take_app_error()`], or, if not present, the action's `Result`.
    //!
    //! 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.
    //!
    //! # Usage with `anyhow`
    //! [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 `?`.
    //!
    //! [1]: https://github.com/dtolnay/anyhow/issues/83

    let result = action();
    if let Some(error) = take_app_error() {
        Err(error)
    } else {
        result.map_err(|e| e.into())
    }
}