浏览代码

refactor(tauri): use explicit error types instead of anyhow (#1209)

Lucas Fernandes Nogueira 4 年之前
父节点
当前提交
156a0ad5cb

+ 5 - 0
.changes/core-errors.md

@@ -0,0 +1,5 @@
+---
+"tauri": minor
+---
+
+Tauri now uses explicit Error variants with `thiserror` instead of relying on `anyhow`.

+ 0 - 1
tauri-api/Cargo.toml

@@ -24,7 +24,6 @@ semver = "0.11"
 tempfile = "3"
 either = "1.6.1"
 tar = "0.4"
-anyhow = "1.0.38"
 flate2 = "1.0"
 thiserror = "1.0.23"
 rand = "0.8"

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

@@ -58,7 +58,7 @@ pub fn get_matches(config: &Config) -> crate::Result<Matches> {
     .tauri
     .cli
     .as_ref()
-    .ok_or_else(|| anyhow::anyhow!("CLI configuration not defined"))?;
+    .ok_or_else(|| crate::Error::CliNotConfigured)?;
 
   let about = cli
     .description()

+ 19 - 8
tauri-api/src/command.rs

@@ -16,7 +16,9 @@ pub fn get_output(cmd: String, args: Vec<String>, stdout: Stdio) -> crate::Resul
   if output.status.success() {
     Ok(String::from_utf8_lossy(&output.stdout).to_string())
   } else {
-    Err(crate::Error::Command(String::from_utf8_lossy(&output.stderr).to_string()).into())
+    Err(crate::Error::Command(
+      String::from_utf8_lossy(&output.stderr).to_string(),
+    ))
   }
 }
 
@@ -32,7 +34,9 @@ pub fn get_output(cmd: String, args: Vec<String>, stdout: Stdio) -> crate::Resul
   if output.status.success() {
     Ok(String::from_utf8_lossy(&output.stdout).to_string())
   } else {
-    Err(crate::Error::Command(String::from_utf8_lossy(&output.stderr).to_string()).into())
+    Err(crate::Error::Command(
+      String::from_utf8_lossy(&output.stderr).to_string(),
+    ))
   }
 }
 
@@ -41,7 +45,9 @@ pub fn get_output(cmd: String, args: Vec<String>, stdout: Stdio) -> crate::Resul
 pub fn command_path(command: String) -> crate::Result<String> {
   match std::env::current_exe()?.parent() {
     Some(exe_dir) => Ok(format!("{}/{}", exe_dir.display().to_string(), command)),
-    None => Err(crate::Error::Command("Could not evaluate executable dir".to_string()).into()),
+    None => Err(crate::Error::Command(
+      "Could not evaluate executable dir".to_string(),
+    )),
   }
 }
 
@@ -50,7 +56,9 @@ pub fn command_path(command: String) -> crate::Result<String> {
 pub fn command_path(command: String) -> crate::Result<String> {
   match std::env::current_exe()?.parent() {
     Some(exe_dir) => Ok(format!("{}/{}.exe", exe_dir.display().to_string(), command)),
-    None => Err(crate::Error::Command("Could not evaluate executable dir".to_string()).into()),
+    None => Err(crate::Error::Command(
+      "Could not evaluate executable dir".to_string(),
+    )),
   }
 }
 
@@ -86,14 +94,17 @@ pub fn spawn_relative_command(
 
 /// Gets the binary command with the current target triple.
 pub fn binary_command(binary_name: String) -> crate::Result<String> {
-  Ok(format!("{}-{}", binary_name, platform::target_triple()?))
+  Ok(format!(
+    "{}-{}",
+    binary_name,
+    platform::target_triple().map_err(|e| crate::Error::FailedToDetectPlatform(e.to_string()))?
+  ))
 }
 
 // tests for the commands functions.
 #[cfg(test)]
 mod test {
   use super::*;
-  use std::io;
 
   #[cfg(not(windows))]
   #[test]
@@ -131,7 +142,7 @@ mod test {
     assert!(res.is_err());
 
     // destruct the Error to check the ErrorKind and test that it is a Command type.
-    if let Some(Error::Command(e)) = res.unwrap_err().downcast_ref::<Error>() {
+    if let Error::Command(e) = res.unwrap_err() {
       // assert that the message in the error matches this string.
       assert_eq!(*e, "cat: test/: Is a directory\n".to_string());
     }
@@ -163,7 +174,7 @@ mod test {
     assert!(res.is_err());
 
     // after asserting that the result is an error, check that the error kind is ErrorKind::Io
-    if let Some(s) = res.unwrap_err().downcast_ref::<io::Error>() {
+    if let crate::Error::Io(s) = res.unwrap_err() {
       // assert that the ErrorKind inside of the ErrorKind Io is ErrorKind::NotFound
       assert_eq!(s.kind(), std::io::ErrorKind::NotFound);
     }

+ 3 - 2
tauri-api/src/dialog.rs

@@ -16,9 +16,10 @@ fn open_dialog_internal(
       .map(|s| s.as_ref().to_string_lossy().to_string())
       .as_deref(),
     dialog_type,
-  )?;
+  )
+  .map_err(|e| crate::Error::Dialog(e.to_string()))?;
   match response {
-    Response::Cancel => Err(crate::Error::Dialog("user cancelled".into()).into()),
+    Response::Cancel => Err(crate::Error::DialogCancelled),
     _ => Ok(response),
   }
 }

+ 58 - 0
tauri-api/src/error.rs

@@ -0,0 +1,58 @@
+/// The error types.
+#[derive(thiserror::Error, Debug)]
+pub enum Error {
+  /// The extract archive error.
+  #[error("Extract Error: {0}")]
+  Extract(String),
+  /// The Command (spawn process) error.
+  #[error("Command Error: {0}")]
+  Command(String),
+  /// The path operation error.
+  #[error("Path Error: {0}")]
+  Path(String),
+  /// Error showing the dialog.
+  #[error("Dialog Error: {0}")]
+  Dialog(String),
+  /// The dialog operation was cancelled by the user.
+  #[error("user cancelled the dialog")]
+  DialogCancelled,
+  /// CLI config not set.
+  #[error("CLI configuration not set on tauri.conf.json")]
+  CliNotConfigured,
+  /// The HTTP response error.
+  #[error("HTTP Response Error: {0}")]
+  Response(attohttpc::StatusCode),
+  /// The network error.
+  #[error("Network Error: {0}")]
+  Network(#[from] attohttpc::Error),
+  /// HTTP method error.
+  #[error("{0}")]
+  HttpMethod(#[from] http::method::InvalidMethod),
+  /// Invalid HTTO header.
+  #[error("{0}")]
+  HttpHeader(#[from] attohttpc::header::InvalidHeaderName),
+  /// Semver error.
+  #[error("{0}")]
+  Semver(#[from] semver::SemVerError),
+  /// JSON error.
+  #[error("{0}")]
+  Json(#[from] serde_json::Error),
+  /// IO error.
+  #[error("{0}")]
+  Io(#[from] std::io::Error),
+  /// ZIP error.
+  #[error("{0}")]
+  Zip(#[from] zip::result::ZipError),
+  /// Notification error.
+  #[error("{0}")]
+  Notification(#[from] notify_rust::error::Error),
+  /// failed to detect the current platform.
+  #[error("failed to detect platform: {0}")]
+  FailedToDetectPlatform(String),
+}
+
+impl From<attohttpc::StatusCode> for Error {
+  fn from(error: attohttpc::StatusCode) -> Self {
+    Self::Response(error)
+  }
+}

+ 8 - 22
tauri-api/src/file.rs

@@ -4,19 +4,17 @@ mod file_move;
 use std::fs;
 use std::path::Path;
 
-use crate::Error;
-
 pub use extract::*;
 pub use file_move::*;
 
 /// Reads a string file.
 pub fn read_string<P: AsRef<Path>>(file: P) -> crate::Result<String> {
-  fs::read_to_string(file).map_err(|err| Error::File(format!("Read_string failed: {}", err)).into())
+  fs::read_to_string(file).map_err(|e| e.into())
 }
 
 /// Reads a binary file.
 pub fn read_binary<P: AsRef<Path>>(file: P) -> crate::Result<Vec<u8>> {
-  fs::read(file).map_err(|err| Error::File(format!("Read_binary failed: {}", err)).into())
+  fs::read(file).map_err(|e| e.into())
 }
 
 #[cfg(test)]
@@ -45,17 +43,11 @@ mod test {
 
     assert!(res.is_err());
 
-    if let Some(Error::File(e)) = res.unwrap_err().downcast_ref::<Error>() {
+    if let Error::Io(e) = res.unwrap_err() {
       #[cfg(windows)]
-      assert_eq!(
-        *e,
-        "Read_string failed: Access is denied. (os error 5)".to_string()
-      );
+      assert_eq!(e.to_string(), "Access is denied. (os error 5)".to_string());
       #[cfg(not(windows))]
-      assert_eq!(
-        *e,
-        "Read_string failed: Is a directory (os error 21)".to_string()
-      );
+      assert_eq!(e.to_string(), "Is a directory (os error 21)".to_string());
     }
   }
 
@@ -91,17 +83,11 @@ mod test {
 
     assert!(res.is_err());
 
-    if let Some(Error::File(e)) = res.unwrap_err().downcast_ref::<Error>() {
+    if let Error::Io(e) = res.unwrap_err() {
       #[cfg(windows)]
-      assert_eq!(
-        *e,
-        "Read_binary failed: Access is denied. (os error 5)".to_string()
-      );
+      assert_eq!(e.to_string(), "Access is denied. (os error 5)".to_string());
       #[cfg(not(windows))]
-      assert_eq!(
-        *e,
-        "Read_binary failed: Is a directory (os error 21)".to_string()
-      );
+      assert_eq!(e.to_string(), "Is a directory (os error 21)".to_string());
     }
   }
 }

+ 4 - 2
tauri-api/src/http.rs

@@ -265,7 +265,9 @@ pub fn make_request(options: HttpRequestOptions) -> crate::Result<Value> {
         if let Some(path) = body.as_str() {
           builder.file(File::open(path)?).send()
         } else {
-          return Err(crate::Error::Path("Body must be the path to the file".into()).into());
+          return Err(crate::Error::Path(
+            "Body must be the path to the file".into(),
+          ));
         }
       }
       BodyType::Auto => {
@@ -297,6 +299,6 @@ pub fn make_request(options: HttpRequestOptions) -> crate::Result<Value> {
     };
     Ok(response_data)
   } else {
-    Err(crate::Error::Network(response.status()).into())
+    Err(response.status().into())
   }
 }

+ 5 - 25
tauri-api/src/lib.rs

@@ -36,32 +36,12 @@ pub mod notification;
 
 pub use tauri_utils::*;
 
-/// Alias for a Result with error type anyhow::Error.
-pub use anyhow::Result;
-use thiserror::Error;
+mod error;
 
-/// The error types.
-#[derive(Error, Debug)]
-pub enum Error {
-  /// The extract archive error.
-  #[error("Extract Error:{0}")]
-  Extract(String),
-  /// The Command (spawn process) error.
-  #[error("Command Error:{0}")]
-  Command(String),
-  /// The file operation error.
-  #[error("File Error:{0}")]
-  File(String),
-  /// The path operation error.
-  #[error("Path Error:{0}")]
-  Path(String),
-  /// The dialog error.
-  #[error("Dialog Error:{0}")]
-  Dialog(String),
-  /// The network error.
-  #[error("Network Error:{0}")]
-  Network(attohttpc::StatusCode),
-}
+/// Tauri API error.
+pub use error::Error;
+/// Tauri API result type.
+pub type Result<T> = std::result::Result<T, Error>;
 
 // Not public API
 #[doc(hidden)]

+ 2 - 4
tauri-api/src/notification.rs

@@ -77,9 +77,7 @@ impl Notification {
         notification.app_id(&self.identifier);
       }
     }
-    notification
-      .show()
-      .map(|_| ())
-      .map_err(|e| anyhow::anyhow!(e.to_string()))
+    notification.show()?;
+    Ok(())
   }
 }

+ 3 - 1
tauri-api/src/path.rs

@@ -84,7 +84,9 @@ pub fn resolve_path<P: AsRef<Path>>(path: P, dir: Option<BaseDirectory>) -> crat
       base_dir_path_value.push(path);
       Ok(base_dir_path_value)
     } else {
-      Err(crate::Error::Path("unable to determine base dir path".to_string()).into())
+      Err(crate::Error::Path(
+        "unable to determine base dir path".to_string(),
+      ))
     }
   } else {
     let mut dir_path = PathBuf::new();

+ 2 - 4
tauri-macros/src/expand.rs

@@ -21,10 +21,8 @@ pub(crate) fn load_context(input: DeriveInput) -> Result<TokenStream, Error> {
     .iter()
     .find(|attr| attr.path.is_ident("config_path"));
   if let Some(attr) = config_path_attr {
-    if let Ok(meta) = attr.parse_meta() {
-      if let NameValue(MetaNameValue { lit: Str(path), .. }) = meta {
-        config_file_path = path.value()
-      }
+    if let Ok(NameValue(MetaNameValue { lit: Str(path), .. })) = attr.parse_meta() {
+      config_file_path = path.value()
     }
   }
 

+ 0 - 1
tauri-utils/Cargo.toml

@@ -12,7 +12,6 @@ edition = "2018"
 serde = "1.0"
 serde_json = "1.0"
 sysinfo = "0.10"
-anyhow = "1.0.31"
 thiserror = "1.0.19"
 phf = { version = "0.8", features = ["macros"] }
 flate2 = "1"

+ 9 - 6
tauri-utils/src/lib.rs

@@ -10,11 +10,11 @@ pub mod platform;
 /// Process helpers
 pub mod process;
 
-pub use anyhow::Result;
-use thiserror::Error;
+/// Result type alias using the crate's error type.
+pub type Result<T> = std::result::Result<T, Error>;
 
 /// The error types.
-#[derive(Error, Debug)]
+#[derive(Debug, thiserror::Error)]
 pub enum Error {
   /// Target triple architecture error
   #[error("Unable to determine target-architecture")]
@@ -25,9 +25,9 @@ pub enum Error {
   /// Target triple environment error
   #[error("Unable to determine target-environment")]
   Environment,
-  /// Target triple unknown target-os error
-  #[error("Unknown target_os")]
-  Unknown,
+  /// Tried to get resource on an unsupported platform.
+  #[error("Unsupported platform for reading resources")]
+  UnsupportedPlatform,
   /// Get parent process error
   #[error("Could not get parent process")]
   ParentProcess,
@@ -37,4 +37,7 @@ pub enum Error {
   /// Get child process error
   #[error("Could not get child process")]
   ChildProcess,
+  /// IO error.
+  #[error("{0}")]
+  Io(#[from] std::io::Error),
 }

+ 6 - 8
tauri-utils/src/platform.rs

@@ -1,7 +1,5 @@
 use std::path::{PathBuf, MAIN_SEPARATOR};
 
-use anyhow::Result;
-
 /// Try to determine the current target triple.
 ///
 /// Returns a target triple (e.g. `x86_64-unknown-linux-gnu` or `i686-pc-windows-msvc`) or an
@@ -11,7 +9,7 @@ use anyhow::Result;
 ///
 /// * Errors:
 ///     * Unexpected system config
-pub fn target_triple() -> Result<String> {
+pub fn target_triple() -> crate::Result<String> {
   let arch = if cfg!(target_arch = "x86") {
     "i686"
   } else if cfg!(target_arch = "x86_64") {
@@ -19,7 +17,7 @@ pub fn target_triple() -> Result<String> {
   } else if cfg!(target_arch = "arm") {
     "armv7"
   } else {
-    return Err(crate::Error::Architecture.into());
+    return Err(crate::Error::Architecture);
   };
 
   let os = if cfg!(target_os = "linux") {
@@ -31,7 +29,7 @@ pub fn target_triple() -> Result<String> {
   } else if cfg!(target_os = "freebsd") {
     "unknown-freebsd"
   } else {
-    return Err(crate::Error::OS.into());
+    return Err(crate::Error::OS);
   };
 
   let os = if cfg!(target_os = "macos") || cfg!(target_os = "freebsd") {
@@ -44,7 +42,7 @@ pub fn target_triple() -> Result<String> {
     } else if cfg!(target_env = "msvc") {
       "msvc"
     } else {
-      return Err(crate::Error::Environment.into());
+      return Err(crate::Error::Environment);
     };
 
     format!("{}-{}", os, env)
@@ -61,7 +59,7 @@ pub fn target_triple() -> Result<String> {
 /// and `${exe_dir}/../lib/${exe_name}` when running the app from `src-tauri/target/(debug|release)/`.
 ///
 /// On MacOS, it's `${exe_dir}../Resources` (inside .app).
-pub fn resource_dir() -> Result<PathBuf> {
+pub fn resource_dir() -> crate::Result<PathBuf> {
   let exe = std::env::current_exe()?;
   let exe_dir = exe.parent().expect("failed to get exe directory");
   let app_name = exe
@@ -89,6 +87,6 @@ pub fn resource_dir() -> Result<PathBuf> {
   } else if cfg!(target_os = "macos") {
     Ok(exe_dir.join("../Resources"))
   } else {
-    Err(crate::Error::Unknown.into())
+    Err(crate::Error::UnsupportedPlatform)
   }
 }

+ 0 - 1
tauri/Cargo.toml

@@ -28,7 +28,6 @@ tokio = { version = "1.2", features = ["rt", "rt-multi-thread", "sync"] }
 futures = "0.3"
 async-trait = "0.1"
 uuid = { version = "0.8.2", features = [ "v4" ] }
-anyhow = "1.0.38"
 thiserror = "1.0.23"
 once_cell = "1.5.2"
 tauri-api = { version = "0.7.5", path = "../tauri-api" }

+ 16 - 34
tauri/src/app/runner.rs

@@ -37,7 +37,7 @@ pub(crate) fn run<A: ApplicationExt + 'static>(application: App<A>) -> crate::Re
 
     // spawn the embedded server on our server url
     #[cfg(embedded_server)]
-    spawn_server(server_url, &application.context)?;
+    spawn_server(server_url, &application.context);
   }
 
   let splashscreen_content = if application.splashscreen_html().is_some() {
@@ -114,15 +114,13 @@ fn setup_content(context: &Context) -> crate::Result<Content<String>> {
 // setup content for embedded server
 #[cfg(all(embedded_server, not(no_server)))]
 fn setup_content(context: &Context) -> crate::Result<Content<String>> {
-  let (port, valid) = setup_port(&context)?;
-  let url = (if valid {
-    setup_server_url(port, &context)
+  let (port, valid) = setup_port(&context);
+  if valid {
+    let url = setup_server_url(port, &context);
+    Ok(Content::Url(url))
   } else {
-    Err(anyhow::anyhow!("invalid port"))
-  })
-  .expect("Unable to setup URL");
-
-  Ok(Content::Url(url))
+    Err(crate::Error::PortNotAvailable(port))
+  }
 }
 
 // setup content for no-server
@@ -137,16 +135,16 @@ fn setup_content(context: &Context) -> crate::Result<Content<String>> {
 // get the port for the embedded server
 #[cfg(embedded_server)]
 #[allow(dead_code)]
-fn setup_port(context: &Context) -> crate::Result<(String, bool)> {
+fn setup_port(context: &Context) -> (String, bool) {
   let config = &context.config;
   match config.tauri.embedded_server.port {
     tauri_api::config::Port::Random => match get_available_port() {
-      Some(available_port) => Ok((available_port.to_string(), true)),
-      None => Ok(("0".to_string(), false)),
+      Some(available_port) => (available_port.to_string(), true),
+      None => ("0".to_string(), false),
     },
     tauri_api::config::Port::Value(port) => {
       let port_valid = port_is_available(port);
-      Ok((port.to_string(), port_valid))
+      (port.to_string(), port_valid)
     }
   }
 }
@@ -154,18 +152,18 @@ fn setup_port(context: &Context) -> crate::Result<(String, bool)> {
 // setup the server url for embedded server
 #[cfg(embedded_server)]
 #[allow(dead_code)]
-fn setup_server_url(port: String, context: &Context) -> crate::Result<String> {
+fn setup_server_url(port: String, context: &Context) -> String {
   let config = &context.config;
   let mut url = format!("{}:{}", config.tauri.embedded_server.host, port);
   if !url.starts_with("http") {
     url = format!("http://{}", url);
   }
-  Ok(url)
+  url
 }
 
 // spawn the embedded server
 #[cfg(embedded_server)]
-fn spawn_server(server_url: String, context: &Context) -> crate::Result<()> {
+fn spawn_server(server_url: String, context: &Context) {
   let assets = context.assets;
   let public_path = context.config.tauri.embedded_server.public_path.clone();
   std::thread::spawn(move || {
@@ -193,7 +191,6 @@ fn spawn_server(server_url: String, context: &Context) -> crate::Result<()> {
         .expect("unable to setup response");
     }
   });
-  Ok(())
 }
 
 // spawn an updater process.
@@ -440,17 +437,6 @@ mod test {
     }
   }
 
-  #[cfg(embedded_server)]
-  #[test]
-  fn check_setup_port() {
-    let context = Context::new::<TauriContext>().unwrap();
-    let res = super::setup_port(&context);
-    match res {
-      Ok((_s, _b)) => {}
-      _ => panic!("setup port failed"),
-    }
-  }
-
   proptest! {
     #![proptest_config(ProptestConfig::with_cases(10000))]
     #[cfg(embedded_server)]
@@ -459,12 +445,8 @@ mod test {
       let p = port.clone();
       let context = Context::new::<TauriContext>().unwrap();
 
-      let res = super::setup_server_url(port, &context);
-
-      match res {
-        Ok(url) => assert!(url.contains(&p)),
-        Err(e) => panic!("setup_server_url Err {:?}", e.to_string())
-      }
+      let url = super::setup_server_url(port, &context);
+      assert!(url.contains(&p));
     }
   }
 }

+ 1 - 1
tauri/src/endpoints.rs

@@ -283,7 +283,7 @@ pub(crate) async fn handle<D: ApplicationDispatcherExt + 'static>(
         CliMatches { callback, error } => {
           #[cfg(cli)]
           {
-            let matches = tauri_api::cli::get_matches(&context.config);
+            let matches = tauri_api::cli::get_matches(&context.config).map_err(|e| e.into());
             crate::execute_promise(dispatcher, async move { matches }, callback, error).await;
           }
           #[cfg(not(cli))]

+ 7 - 10
tauri/src/endpoints/asset.rs

@@ -1,7 +1,8 @@
 use crate::{ApplicationDispatcherExt, Context};
-use std::io::Read;
 use tauri_api::assets::{AssetFetch, Assets};
 
+use std::io::Read;
+
 #[allow(clippy::option_env_unwrap)]
 pub async fn load<D: ApplicationDispatcherExt + 'static>(
   dispatcher: &mut D,
@@ -18,11 +19,7 @@ pub async fn load<D: ApplicationDispatcherExt + 'static>(
     dispatcher,
     async move {
       // strip "about:" uri scheme if it exists
-      let asset = if asset.starts_with("about:") {
-        &asset[6..]
-      } else {
-        &asset
-      };
+      let asset = asset.strip_prefix("about:").unwrap_or(&asset);
 
       // handle public path setting from tauri.conf > tauri > embeddedServer > publicPath
       let asset = if asset.starts_with(&public_path) {
@@ -37,9 +34,9 @@ pub async fn load<D: ApplicationDispatcherExt + 'static>(
       .to_string();
 
       // how should that condition be handled now?
-      let asset_bytes = assets
+      let asset_bytes: Vec<u8> = assets
         .get(&Assets::format_key(&asset), AssetFetch::Decompress)
-        .ok_or_else(|| anyhow::anyhow!("Asset '{}' not found", asset))
+        .ok_or_else(|| crate::Error::AssetNotFound(asset.clone()))
         .and_then(|(read, _)| {
           read
             .bytes()
@@ -63,7 +60,7 @@ pub async fn load<D: ApplicationDispatcherExt + 'static>(
         } else {
           "jpeg"
         };
-        Ok(format!(
+        crate::Result::Ok(format!(
           r#""data:image/{};base64,{}""#,
           mime_type,
           base64::encode(&asset_bytes)
@@ -90,7 +87,7 @@ pub async fn load<D: ApplicationDispatcherExt + 'static>(
         } else {
           dispatcher_.eval(asset_str);
         }
-        Ok("Asset loaded successfully".to_string())
+        crate::Result::Ok("Asset loaded successfully".to_string())
       }
     },
     callback,

+ 9 - 4
tauri/src/endpoints/dialog.rs

@@ -34,7 +34,8 @@ pub fn open<D: ApplicationDispatcherExt + 'static>(
       } else {
         select(options.filter, options.default_path)
       };
-      response.map(map_response)
+      let res = response.map(map_response)?;
+      Ok(res)
     },
     callback,
     error,
@@ -52,7 +53,11 @@ pub fn save<D: ApplicationDispatcherExt + 'static>(
 ) -> crate::Result<()> {
   crate::execute_promise_sync(
     dispatcher,
-    move || save_file(options.filter, options.default_path).map(map_response),
+    move || {
+      save_file(options.filter, options.default_path)
+        .map(map_response)
+        .map_err(|e| e.into())
+    },
     callback,
     error,
   );
@@ -75,8 +80,8 @@ pub fn ask<D: ApplicationDispatcherExt + 'static>(
   crate::execute_promise_sync(
     dispatcher,
     move || match ask_dialog(message, title) {
-      DialogSelection::Yes => Ok(true),
-      _ => Ok(false),
+      DialogSelection::Yes => crate::Result::Ok(true),
+      _ => crate::Result::Ok(false),
     },
     callback,
     error,

+ 30 - 20
tauri/src/endpoints/file_system.rs

@@ -28,7 +28,7 @@ pub async fn read_dir<D: ApplicationDispatcherExt>(
       } else {
         (false, None)
       };
-      dir::read_dir(resolve_path(path, dir)?, recursive)
+      dir::read_dir(resolve_path(path, dir)?, recursive).map_err(crate::Error::FailedToExecuteApi)
     },
     callback,
     error,
@@ -56,7 +56,8 @@ pub async fn copy_file<D: ApplicationDispatcherExt>(
         ),
         None => (source, destination),
       };
-      fs::copy(src, dest).map_err(|e| e.into())
+      fs::copy(src, dest)?;
+      crate::Result::Ok(())
     },
     callback,
     error,
@@ -82,13 +83,13 @@ pub async fn create_dir<D: ApplicationDispatcherExt>(
         (false, None)
       };
       let resolved_path = resolve_path(path, dir)?;
-      let response = if recursive {
-        fs::create_dir_all(resolved_path)
+      if recursive {
+        fs::create_dir_all(resolved_path)?;
       } else {
-        fs::create_dir(resolved_path)
-      };
+        fs::create_dir(resolved_path)?;
+      }
 
-      response.map_err(|e| e.into())
+      crate::Result::Ok(())
     },
     callback,
     error,
@@ -114,13 +115,13 @@ pub async fn remove_dir<D: ApplicationDispatcherExt>(
         (false, None)
       };
       let resolved_path = resolve_path(path, dir)?;
-      let response = if recursive {
-        fs::remove_dir_all(resolved_path)
+      if recursive {
+        fs::remove_dir_all(resolved_path)?;
       } else {
-        fs::remove_dir(resolved_path)
-      };
+        fs::remove_dir(resolved_path)?;
+      }
 
-      response.map_err(|e| e.into())
+      crate::Result::Ok(())
     },
     callback,
     error,
@@ -141,7 +142,8 @@ pub async fn remove_file<D: ApplicationDispatcherExt>(
     dispatcher,
     async move {
       let resolved_path = resolve_path(path, options.and_then(|o| o.dir))?;
-      fs::remove_file(resolved_path).map_err(|e| e.into())
+      fs::remove_file(resolved_path)?;
+      crate::Result::Ok(())
     },
     callback,
     error,
@@ -169,7 +171,7 @@ pub async fn rename_file<D: ApplicationDispatcherExt>(
         ),
         None => (old_path, new_path),
       };
-      fs::rename(old, new).map_err(|e| e.into())
+      fs::rename(old, new).map_err(crate::Error::Io)
     },
     callback,
     error,
@@ -191,8 +193,9 @@ pub async fn write_file<D: ApplicationDispatcherExt>(
     dispatcher,
     async move {
       File::create(resolve_path(path, options.and_then(|o| o.dir))?)
-        .map_err(|e| e.into())
-        .and_then(|mut f| f.write_all(contents.as_bytes()).map_err(|err| err.into()))
+        .map_err(crate::Error::Io)
+        .and_then(|mut f| f.write_all(contents.as_bytes()).map_err(|err| err.into()))?;
+      crate::Result::Ok(())
     },
     callback,
     error,
@@ -214,12 +217,13 @@ pub async fn write_binary_file<D: ApplicationDispatcherExt>(
     dispatcher,
     async move {
       base64::decode(contents)
-        .map_err(|e| e.into())
+        .map_err(crate::Error::Base64Decode)
         .and_then(|c| {
           File::create(resolve_path(path, options.and_then(|o| o.dir))?)
             .map_err(|e| e.into())
             .and_then(|mut f| f.write_all(&c).map_err(|err| err.into()))
-        })
+        })?;
+      crate::Result::Ok(())
     },
     callback,
     error,
@@ -238,7 +242,10 @@ pub async fn read_text_file<D: ApplicationDispatcherExt>(
 ) {
   crate::execute_promise(
     dispatcher,
-    async move { file::read_string(resolve_path(path, options.and_then(|o| o.dir))?) },
+    async move {
+      file::read_string(resolve_path(path, options.and_then(|o| o.dir))?)
+        .map_err(crate::Error::FailedToExecuteApi)
+    },
     callback,
     error,
   )
@@ -256,7 +263,10 @@ pub async fn read_binary_file<D: ApplicationDispatcherExt>(
 ) {
   crate::execute_promise(
     dispatcher,
-    async move { file::read_binary(resolve_path(path, options.and_then(|o| o.dir))?) },
+    async move {
+      file::read_binary(resolve_path(path, options.and_then(|o| o.dir))?)
+        .map_err(crate::Error::FailedToExecuteApi)
+    },
     callback,
     error,
   )

+ 7 - 1
tauri/src/endpoints/http.rs

@@ -8,5 +8,11 @@ pub async fn make_request<D: ApplicationDispatcherExt>(
   callback: String,
   error: String,
 ) {
-  crate::execute_promise(dispatcher, async move { request(options) }, callback, error).await;
+  crate::execute_promise(
+    dispatcher,
+    async move { request(options).map_err(|e| e.into()) },
+    callback,
+    error,
+  )
+  .await;
 }

+ 7 - 7
tauri/src/endpoints/notification.rs

@@ -24,7 +24,7 @@ pub async fn send<D: ApplicationDispatcherExt>(
         notification = notification.icon(icon);
       }
       notification.show()?;
-      Ok(JsonValue::Null)
+      crate::Result::Ok(JsonValue::Null)
     },
     callback,
     error,
@@ -42,9 +42,9 @@ pub async fn is_permission_granted<D: ApplicationDispatcherExt>(
     async move {
       let settings = crate::settings::read_settings()?;
       if let Some(allow_notification) = settings.allow_notification {
-        Ok(JsonValue::String(allow_notification.to_string()))
+        crate::Result::Ok(JsonValue::String(allow_notification.to_string()))
       } else {
-        Ok(JsonValue::Null)
+        crate::Result::Ok(JsonValue::Null)
       }
     },
     callback,
@@ -65,7 +65,7 @@ pub fn request_permission<D: ApplicationDispatcherExt + 'static>(
       let granted = "granted".to_string();
       let denied = "denied".to_string();
       if let Some(allow_notification) = settings.allow_notification {
-        return Ok(if allow_notification { granted } else { denied });
+        return crate::Result::Ok(if allow_notification { granted } else { denied });
       }
       let answer = tauri_api::dialog::ask(
         "This app wants to show notifications. Do you allow?",
@@ -75,14 +75,14 @@ pub fn request_permission<D: ApplicationDispatcherExt + 'static>(
         tauri_api::dialog::DialogSelection::Yes => {
           settings.allow_notification = Some(true);
           crate::settings::write_settings(settings)?;
-          Ok(granted)
+          crate::Result::Ok(granted)
         }
         tauri_api::dialog::DialogSelection::No => {
           settings.allow_notification = Some(false);
           crate::settings::write_settings(settings)?;
-          Ok(denied)
+          crate::Result::Ok(denied)
         }
-        _ => Ok("default".to_string()),
+        _ => crate::Result::Ok("default".to_string()),
       }
     },
     callback,

+ 1 - 1
tauri/src/endpoints/path.rs

@@ -12,7 +12,7 @@ pub async fn resolve_path<D: ApplicationDispatcherExt>(
 ) {
   crate::execute_promise(
     dispatcher,
-    async move { path::resolve_path(path, directory) },
+    async move { path::resolve_path(path, directory).map_err(|e| e.into()) },
     callback,
     error,
   )

+ 41 - 0
tauri/src/error.rs

@@ -0,0 +1,41 @@
+/// The plugin error type.
+#[derive(Debug, thiserror::Error)]
+pub enum Error {
+  /// Failed to create webview.
+  #[error("failed to create webview")]
+  CreateWebview,
+  /// Failed to create window.
+  #[error("failed to create window")]
+  CreateWindow,
+  /// Embedded asset not found.
+  #[error("asset not found: {0}")]
+  AssetNotFound(String),
+  /// Embedded server port not available.
+  #[error("failed to setup server, port {0} not available")]
+  PortNotAvailable(String),
+  /// Failed to serialize/deserialize.
+  #[error("JSON error: {0}")]
+  Json(serde_json::Error),
+  /// Unknown API type.
+  #[error("unknown API")]
+  UnknownApi,
+  /// Failed to execute tauri API.
+  #[error("failed to execute API: {0}")]
+  FailedToExecuteApi(#[from] tauri_api::Error),
+  /// IO error.
+  #[error("{0}")]
+  Io(#[from] std::io::Error),
+  /// Failed to decode base64.
+  #[error("Failed to decode base64 string: {0}")]
+  Base64Decode(#[from] base64::DecodeError),
+}
+
+impl From<serde_json::Error> for Error {
+  fn from(error: serde_json::Error) -> Self {
+    if error.to_string().contains("unknown variant") {
+      Self::UnknownApi
+    } else {
+      Self::Json(error)
+    }
+  }
+}

+ 13 - 7
tauri/src/lib.rs

@@ -18,6 +18,7 @@ pub mod settings;
 mod app;
 /// The Tauri API endpoints.
 mod endpoints;
+mod error;
 /// The plugin manager module contains helpers to manage runtime plugins.
 pub mod plugin;
 /// The salt helpers.
@@ -25,13 +26,16 @@ mod salt;
 /// Webview interface.
 mod webview;
 
+/// The Tauri error enum.
+pub use error::Error;
+/// Tauri result type.
+pub type Result<T> = std::result::Result<T, Error>;
+
 pub(crate) mod async_runtime;
 
 /// A task to run on the main thread.
 pub type SyncTask = Box<dyn FnOnce() + Send>;
 
-/// Alias for a Result with error type anyhow::Error.
-pub use anyhow::Result;
 pub use app::*;
 pub use tauri_api as api;
 pub use tauri_macros::FromTauriContext;
@@ -66,10 +70,12 @@ pub fn execute_promise_sync<
     let callback_string =
       match format_callback_result(task().map_err(|err| err.to_string()), &callback, &error) {
         Ok(js) => js,
-        Err(e) => {
-          format_callback_result(Result::<(), String>::Err(e.to_string()), &callback, &error)
-            .unwrap()
-        }
+        Err(e) => format_callback_result(
+          std::result::Result::<(), String>::Err(e.to_string()),
+          &callback,
+          &error,
+        )
+        .unwrap(),
       };
     dispatcher_.eval(callback_string.as_str());
   })));
@@ -111,7 +117,7 @@ pub async fn call<D: ApplicationDispatcherExt>(
 ) {
   execute_promise(
     dispatcher,
-    async move { api::command::get_output(command, args, Stdio::piped()) },
+    async move { api::command::get_output(command, args, Stdio::piped()).map_err(|e| e.into()) },
     callback,
     error,
   )

+ 5 - 26
tauri/src/plugin.rs

@@ -6,27 +6,6 @@ use futures::future::join_all;
 
 use std::sync::Arc;
 
-/// The plugin error type.
-#[derive(Debug, thiserror::Error)]
-pub enum Error {
-  /// Failed to serialize/deserialize.
-  #[error("JSON error: {0}")]
-  Json(serde_json::Error),
-  /// Unknown API type.
-  #[error("unknown API")]
-  UnknownApi,
-}
-
-impl From<serde_json::Error> for Error {
-  fn from(error: serde_json::Error) -> Self {
-    if error.to_string().contains("unknown variant") {
-      Self::UnknownApi
-    } else {
-      Self::Json(error)
-    }
-  }
-}
-
 /// The plugin interface.
 #[async_trait::async_trait]
 pub trait Plugin<D: ApplicationDispatcherExt + 'static>: Send + Sync {
@@ -35,7 +14,7 @@ pub trait Plugin<D: ApplicationDispatcherExt + 'static>: Send + Sync {
 
   /// Initialize the plugin.
   #[allow(unused_variables)]
-  async fn initialize(&mut self, config: String) -> Result<(), Error> {
+  async fn initialize(&mut self, config: String) -> crate::Result<()> {
     Ok(())
   }
 
@@ -58,8 +37,8 @@ pub trait Plugin<D: ApplicationDispatcherExt + 'static>: Send + Sync {
 
   /// Add invoke_handler API extension commands.
   #[allow(unused_variables)]
-  async fn extend_api(&mut self, dispatcher: D, payload: &str) -> Result<(), Error> {
-    Err(Error::UnknownApi)
+  async fn extend_api(&mut self, dispatcher: D, payload: &str) -> crate::Result<()> {
+    Err(crate::Error::UnknownApi)
   }
 }
 
@@ -142,7 +121,7 @@ pub(crate) async fn extend_api<D: ApplicationDispatcherExt + 'static>(
   store: &PluginStore<D>,
   dispatcher: &mut D,
   arg: &str,
-) -> Result<bool, Error> {
+) -> crate::Result<bool> {
   let mut plugins = store.lock().await;
   for ext in plugins.iter_mut() {
     match ext.extend_api(dispatcher.clone(), arg).await {
@@ -150,7 +129,7 @@ pub(crate) async fn extend_api<D: ApplicationDispatcherExt + 'static>(
         return Ok(true);
       }
       Err(e) => match e {
-        Error::UnknownApi => {}
+        crate::Error::UnknownApi => {}
         _ => return Err(e),
       },
     }

+ 4 - 4
tauri/src/settings.rs

@@ -1,4 +1,3 @@
-use anyhow::anyhow;
 use serde::{Deserialize, Serialize};
 use std::fs::File;
 use std::io::Write;
@@ -27,10 +26,10 @@ pub(crate) fn write_settings(settings: Settings) -> crate::Result<()> {
     std::fs::create_dir(settings_folder)?;
   }
   File::create(settings_path)
-    .map_err(|e| anyhow!(e))
+    .map_err(|e| e.into())
     .and_then(|mut f| {
       f.write_all(serde_json::to_string(&settings)?.as_bytes())
-        .map_err(|err| anyhow!(err))
+        .map_err(|e| e.into())
     })
 }
 
@@ -39,7 +38,8 @@ pub fn read_settings() -> crate::Result<Settings> {
   let settings_path = get_settings_path()?;
   if settings_path.exists() {
     read_string(settings_path)
-      .and_then(|settings| serde_json::from_str(settings.as_str()).map_err(|e| anyhow!(e)))
+      .and_then(|settings| serde_json::from_str(settings.as_str()).map_err(|e| e.into()))
+      .map_err(|e| e.into())
   } else {
     Ok(Default::default())
   }

+ 3 - 4
tauri/src/webview/wry.rs

@@ -152,8 +152,7 @@ impl ApplicationExt for WryApplication {
   }
 
   fn new() -> crate::Result<Self> {
-    let app =
-      wry::Application::new().map_err(|_| anyhow::anyhow!("failed to create application"))?;
+    let app = wry::Application::new().map_err(|_| crate::Error::CreateWebview)?;
     let dispatcher = app.dispatcher();
     let windows = Arc::new(Mutex::new(HashMap::new()));
 
@@ -176,7 +175,7 @@ impl ApplicationExt for WryApplication {
     let window = self
       .inner
       .create_window(window_builder.finish()?)
-      .map_err(|_| anyhow::anyhow!("failed to create window"))?;
+      .map_err(|_| crate::Error::CreateWindow)?;
     Ok(window)
   }
 
@@ -212,7 +211,7 @@ impl ApplicationExt for WryApplication {
     self
       .inner
       .create_webview(window, webview_builder.finish()?, Some(wry_callbacks))
-      .map_err(|_| anyhow::anyhow!("failed to create webview"))?;
+      .map_err(|_| crate::Error::CreateWebview)?;
     Ok(())
   }