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        Display::fmt(&self.0, f)
45    }
46}
47
48impl Error for TauriError {}
49
50impl From<TauriError> for PyErr {
51    fn from(value: TauriError) -> Self {
52        PyRuntimeError::new_err(value.0.to_string())
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;
118
119pub(crate) fn non_exhaustive_panic() -> ! {
120    panic!("NonExhaustive is reserved for `#[non_exhaustive]`");
121}
122
123/// Only compiles the code block if the cfg is satisfied, otherwise returns a [PyResult<ty>.Err].
124macro_rules! cfg_impl {
125    (|$cfg:meta| -> $ty:ty $body:block) => {
126        {
127            let ret: ::pyo3::PyResult::<$ty> = {
128                #[cfg($cfg)]
129                $body
130                #[cfg(not($cfg))]
131                {
132                    use pyo3::exceptions::PyNotImplementedError;
133
134                    const MSG: &str = concat!("Available on pytauri `#[cfg(", stringify!($cfg), ")]` only.");
135                    Err(PyNotImplementedError::new_err(MSG))
136                }
137            };
138            ret
139        }
140    };
141}
142
143pub(crate) use cfg_impl;