Pārlūkot izejas kodu

feat(core): improve and cleanup the `Error` enum (#3748)

Lucas Fernandes Nogueira 3 gadi atpakaļ
vecāks
revīzija
da1e879358

+ 5 - 0
.changes/error-send-sync.md

@@ -0,0 +1,5 @@
+---
+"tauri": patch
+---
+
+The `Error` enum is now `Send + Sync`.

+ 5 - 0
.changes/runtime-error-sync.md

@@ -0,0 +1,5 @@
+---
+"tauri-runtime": patch
+---
+
+Force `Error` boxed errors to be `Sync`.

+ 5 - 0
.changes/simplify-setup-requirement-error.md

@@ -0,0 +1,5 @@
+---
+"tauri": patch
+---
+
+The `App::setup` closure can now return a boxed error directly.

+ 1 - 1
core/tauri-runtime-wry/src/lib.rs

@@ -550,7 +550,7 @@ impl<T: UserEvent> ClipboardManager for ClipboardManagerWrapper<T> {
 /// Wrapper around a [`wry::application::window::Icon`] that can be created from an [`WindowIcon`].
 pub struct WryIcon(WryWindowIcon);
 
-fn icon_err<E: std::error::Error + Send + 'static>(e: E) -> Error {
+fn icon_err<E: std::error::Error + Send + Sync + 'static>(e: E) -> Error {
   Error::InvalidIcon(Box::new(e))
 }
 

+ 4 - 4
core/tauri-runtime/src/lib.rs

@@ -98,7 +98,7 @@ pub enum UserAttentionType {
 pub enum Error {
   /// Failed to create webview.
   #[error("failed to create webview: {0}")]
-  CreateWebview(Box<dyn std::error::Error + Send>),
+  CreateWebview(Box<dyn std::error::Error + Send + Sync>),
   /// Failed to create window.
   #[error("failed to create window")]
   CreateWindow,
@@ -118,16 +118,16 @@ pub enum Error {
   #[cfg(feature = "system-tray")]
   #[cfg_attr(doc_cfg, doc(cfg(feature = "system-tray")))]
   #[error("error encountered during tray setup: {0}")]
-  SystemTray(Box<dyn std::error::Error + Send>),
+  SystemTray(Box<dyn std::error::Error + Send + Sync>),
   /// Failed to load window icon.
   #[error("invalid icon: {0}")]
-  InvalidIcon(Box<dyn std::error::Error + Send>),
+  InvalidIcon(Box<dyn std::error::Error + Send + Sync>),
   /// Failed to get monitor on window operation.
   #[error("failed to get monitor")]
   FailedToGetMonitor,
   /// Global shortcut error.
   #[error(transparent)]
-  GlobalShortcut(Box<dyn std::error::Error + Send>),
+  GlobalShortcut(Box<dyn std::error::Error + Send + Sync>),
   #[error("Invalid header name: {0}")]
   InvalidHeaderName(#[from] InvalidHeaderName),
   #[error("Invalid header value: {0}")]

+ 1 - 1
core/tauri/src/api/cli.rs

@@ -90,7 +90,7 @@ impl Matches {
 /// use tauri::api::cli::get_matches;
 /// tauri::Builder::default()
 ///   .setup(|app| {
-///     let matches = get_matches(app.config().tauri.cli.as_ref().unwrap(), app.package_info()).unwrap();
+///     let matches = get_matches(app.config().tauri.cli.as_ref().unwrap(), app.package_info())?;
 ///     Ok(())
 ///   });
 /// ```

+ 1 - 1
core/tauri/src/api/ipc.rs

@@ -125,7 +125,7 @@ pub fn serialize_js_with<T: Serialize, F: FnOnce(&str) -> String>(
 ///       "console.log({}, {})",
 ///       serialize_js(&Foo { bar: "bar".to_string() }).unwrap(),
 ///       serialize_js(&Bar { baz: 0 }).unwrap()),
-///     ).unwrap();
+///     )?;
 ///     Ok(())
 ///   });
 /// ```

+ 2 - 2
core/tauri/src/api/path.rs

@@ -125,7 +125,7 @@ impl BaseDirectory {
 /// use tauri::Manager;
 /// tauri::Builder::default()
 ///   .setup(|app| {
-///     let path = tauri::api::path::parse(&app.config(), app.package_info(), &app.env(), "$HOME/.bashrc").unwrap();
+///     let path = tauri::api::path::parse(&app.config(), app.package_info(), &app.env(), "$HOME/.bashrc")?;
 ///     assert_eq!(path.to_str().unwrap(), "/home/${whoami}/.bashrc");
 ///     Ok(())
 ///   });
@@ -202,7 +202,7 @@ pub fn parse<P: AsRef<Path>>(
 ///       &app.env(),
 ///       "path/to/something",
 ///       Some(BaseDirectory::Config)
-///     ).expect("failed to resolve path");
+///     )?;
 ///     assert_eq!(path.to_str().unwrap(), "/home/${whoami}/.config/path/to/something");
 ///     Ok(())
 ///   });

+ 1 - 1
core/tauri/src/api/process.rs

@@ -46,7 +46,7 @@ pub use command::*;
 ///
 /// tauri::Builder::default()
 ///   .setup(|app| {
-///     let current_binary_path = current_binary(&app.env()).unwrap();
+///     let current_binary_path = current_binary(&app.env())?;
 ///     Ok(())
 ///   });
 /// ```

+ 1 - 1
core/tauri/src/api/shell.rs

@@ -101,7 +101,7 @@ impl Program {
 /// tauri::Builder::default()
 ///   .setup(|app| {
 ///     // open the given URL on the system default browser
-///     open(&app.shell_scope(), "https://github.com/tauri-apps/tauri", None).unwrap();
+///     open(&app.shell_scope(), "https://github.com/tauri-apps/tauri", None)?;
 ///     Ok(())
 ///   });
 /// ```

+ 6 - 6
core/tauri/src/app.rs

@@ -775,7 +775,7 @@ impl<R: Runtime> Builder<R> {
   #[must_use]
   pub fn setup<F>(mut self, setup: F) -> Self
   where
-    F: FnOnce(&mut App<R>) -> Result<(), Box<dyn std::error::Error + Send>> + Send + 'static,
+    F: FnOnce(&mut App<R>) -> Result<(), Box<dyn std::error::Error>> + Send + 'static,
   {
     self.setup = Box::new(setup);
     self
@@ -1099,18 +1099,18 @@ impl<R: Runtime> Builder<R> {
         use std::io::{Error, ErrorKind};
         #[cfg(target_os = "linux")]
         if let Some(TrayIcon::Raw(..)) = icon {
-          return Err(crate::Error::InvalidIcon(Box::new(Error::new(
+          return Err(crate::Error::InvalidIcon(Error::new(
             ErrorKind::InvalidInput,
             "system tray icons on linux must be a file path",
-          ))));
+          )));
         }
 
         #[cfg(not(target_os = "linux"))]
         if let Some(TrayIcon::File(_)) = icon {
-          return Err(crate::Error::InvalidIcon(Box::new(Error::new(
+          return Err(crate::Error::InvalidIcon(Error::new(
             ErrorKind::InvalidInput,
             "system tray icons on non-linux platforms must be the raw bytes",
-          ))));
+          )));
         }
       }
 
@@ -1338,7 +1338,7 @@ impl<R: Runtime> Builder<R> {
       let _window = app.manager.attach_window(app.handle(), detached);
     }
 
-    (self.setup)(&mut app).map_err(|e| crate::Error::Setup(e))?;
+    (self.setup)(&mut app).map_err(|e| crate::Error::Setup(e.into()))?;
 
     #[cfg(updater)]
     app.run_updater();

+ 2 - 3
core/tauri/src/async_runtime.rs

@@ -156,7 +156,7 @@ impl<T> Future for JoinHandle<T> {
   type Output = crate::Result<T>;
   fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
     match self.get_mut() {
-      Self::Tokio(t) => t.poll(cx).map_err(|e| crate::Error::JoinError(Box::new(e))),
+      Self::Tokio(t) => t.poll(cx).map_err(Into::into),
     }
   }
 }
@@ -334,8 +334,7 @@ mod tests {
       5
     });
     join.abort();
-    if let crate::Error::JoinError(raw_box) = join.await.unwrap_err() {
-      let raw_error = raw_box.downcast::<tokio::task::JoinError>().unwrap();
+    if let crate::Error::JoinError(raw_error) = join.await.unwrap_err() {
       assert!(raw_error.is_cancelled());
     } else {
       panic!("Abort did not result in the expected `JoinError`");

+ 27 - 10
core/tauri/src/error.rs

@@ -2,7 +2,30 @@
 // SPDX-License-Identifier: Apache-2.0
 // SPDX-License-Identifier: MIT
 
-use std::path::PathBuf;
+use std::{fmt, path::PathBuf};
+
+/// A generic boxed error.
+#[derive(Debug)]
+pub struct SetupError(Box<dyn std::error::Error>);
+
+impl From<Box<dyn std::error::Error>> for SetupError {
+  fn from(error: Box<dyn std::error::Error>) -> Self {
+    Self(error)
+  }
+}
+
+// safety: the setup error is only used on the main thread
+// and we exit the process immediately.
+unsafe impl Send for SetupError {}
+unsafe impl Sync for SetupError {}
+
+impl fmt::Display for SetupError {
+  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+    self.0.fmt(f)
+  }
+}
+
+impl std::error::Error for SetupError {}
 
 /// Runtime errors that can happen inside a Tauri application.
 #[derive(Debug, thiserror::Error)]
@@ -11,9 +34,6 @@ pub enum Error {
   /// Runtime error.
   #[error("runtime error: {0}")]
   Runtime(#[from] tauri_runtime::Error),
-  /// Failed to create webview.
-  #[error("failed to create webview: {0}")]
-  CreateWebview(Box<dyn std::error::Error + Send>),
   /// Failed to create window.
   #[error("failed to create window")]
   CreateWindow,
@@ -47,7 +67,7 @@ pub enum Error {
   Base64Decode(#[from] base64::DecodeError),
   /// Failed to load window icon.
   #[error("invalid icon: {0}")]
-  InvalidIcon(Box<dyn std::error::Error + Send>),
+  InvalidIcon(std::io::Error),
   /// Client with specified ID not found.
   #[error("http client dropped or not initialized")]
   HttpClientNotInitialized,
@@ -62,7 +82,7 @@ pub enum Error {
   InvalidArgs(&'static str, &'static str, serde_json::Error),
   /// Encountered an error in the setup hook,
   #[error("error encountered during setup hook: {0}")]
-  Setup(Box<dyn std::error::Error + Send>),
+  Setup(SetupError),
   /// Tauri updater error.
   #[cfg(updater)]
   #[cfg_attr(doc_cfg, doc(cfg(feature = "updater")))]
@@ -71,16 +91,13 @@ pub enum Error {
   /// Error initializing plugin.
   #[error("failed to initialize plugin `{0}`: {1}")]
   PluginInitialization(String, String),
-  /// Encountered an error creating the app system tray,
-  #[error("error encountered during tray setup: {0}")]
-  SystemTray(Box<dyn std::error::Error + Send>),
   /// A part of the URL is malformed or invalid. This may occur when parsing and combining
   /// user-provided URLs and paths.
   #[error("invalid url: {0}")]
   InvalidUrl(url::ParseError),
   /// Task join error.
   #[error(transparent)]
-  JoinError(Box<dyn std::error::Error + Send>),
+  JoinError(#[from] tokio::task::JoinError),
   /// Path not allowed by the scope.
   #[error("path not allowed on the configured scope: {0}")]
   PathNotAllowed(PathBuf),

+ 1 - 1
core/tauri/src/hooks.rs

@@ -16,7 +16,7 @@ use tauri_macros::default_runtime;
 
 /// A closure that is run when the Tauri application is setting up.
 pub type SetupHook<R> =
-  Box<dyn FnOnce(&mut App<R>) -> Result<(), Box<dyn std::error::Error + Send>> + Send>;
+  Box<dyn FnOnce(&mut App<R>) -> Result<(), Box<dyn std::error::Error>> + Send>;
 
 /// A closure that is run everytime Tauri receives a message it doesn't explicitly handle.
 pub type InvokeHandler<R> = dyn Fn(Invoke<R>) + Send + Sync + 'static;

+ 1 - 2
core/tauri/src/window.rs

@@ -199,8 +199,7 @@ impl<R: Runtime> WindowBuilder<R> {
   ///           }
   ///         }
   ///       })
-  ///       .build()
-  ///       .unwrap();
+  ///       .build()?;
   ///     Ok(())
   ///   });
   /// ```