pytauri_core/
utils.rs

1use std::{
2    any::Any,
3    error::Error,
4    fmt::{Display, Formatter},
5    panic::panic_any,
6};
7
8use pyo3::{exceptions::PyRuntimeError, prelude::*};
9
10/// Utility for converting [tauri::Error] to [pyo3::PyErr].
11///
12/// See also: <https://pyo3.rs/v0.23.2/function/error-handling.html#foreign-rust-error-types>.
13///
14/// # Example
15///
16/**
17```rust
18use pyo3::prelude::*;
19use pytauri_core::utils::{TauriError, TauriResult};
20
21fn tauri_result() -> tauri::Result<()> {
22    Ok(())
23}
24
25#[pyfunction]
26fn foo() -> PyResult<()> {
27    tauri_result().map_err(Into::<TauriError>::into)?;
28    Ok(())
29}
30
31#[pyfunction]
32fn bar() -> TauriResult<()> {
33    tauri_result()?;
34    Ok(())
35}
36```
37*/
38
39#[derive(Debug)]
40pub struct TauriError(tauri::Error);
41
42impl Display for TauriError {
43    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
44        write!(f, "{:}", self.0)
45    }
46}
47
48impl Error for TauriError {}
49
50impl From<TauriError> for PyErr {
51    fn from(value: TauriError) -> Self {
52        PyRuntimeError::new_err(format!("{:?}", value.0))
53    }
54}
55
56impl From<tauri::Error> for TauriError {
57    fn from(value: tauri::Error) -> Self {
58        Self(value)
59    }
60}
61
62pub type TauriResult<T> = Result<T, TauriError>;
63
64// keep it private, maybe we will refactor it in the future
65pub(crate) trait PyResultExt {
66    type Output;
67
68    fn unwrap_unraisable_py_result<M>(
69        self,
70        py: Python<'_>,
71        obj: Option<&Bound<'_, PyAny>>,
72        msg: impl FnOnce() -> M,
73    ) -> Self::Output
74    where
75        M: Any + Send + 'static;
76}
77
78impl<T> PyResultExt for PyResult<T> {
79    type Output = T;
80
81    #[inline] // `inline` to allow optimize the `FnOnce` lazy closure
82    fn unwrap_unraisable_py_result<M>(
83        self,
84        py: Python<'_>,
85        obj: Option<&Bound<'_, PyAny>>,
86        msg: impl FnOnce() -> M,
87    ) -> Self::Output
88    where
89        M: Any + Send + 'static,
90    {
91        match self {
92            Ok(v) => v,
93            Err(err) => {
94                // Use [write_unraisable] instead of [restore]:
95                // - Because we are about to panic, Python might abort
96                // - [restore] will not be handled in this case, so it will not be printed to stderr
97                err.write_unraisable(py, obj);
98                // `panic` allows Python to exit `app.run()`,
99                // otherwise the Python main thread will be blocked by `app.run()`
100                // and unable to raise an error
101                panic_any(msg());
102            }
103        }
104    }
105}
106
107macro_rules! delegate_inner {
108    ($slf:expr, $func:ident, $($arg:expr),*) => {
109        $slf.0
110            .inner_ref()
111            .$func($($arg),*)
112            .map_err($crate::utils::TauriError::from)
113            .map_err(pyo3::PyErr::from)
114    };
115}
116
117pub(crate) use delegate_inner;