diff --git a/.changes/core-errors.md b/.changes/core-errors.md new file mode 100644 index 000000000..b317278ce --- /dev/null +++ b/.changes/core-errors.md @@ -0,0 +1,5 @@ +--- +"tauri": minor +--- + +Tauri now uses explicit Error variants with `thiserror` instead of relying on `anyhow`. diff --git a/tauri-api/Cargo.toml b/tauri-api/Cargo.toml index 882216dd2..46bc268a8 100644 --- a/tauri-api/Cargo.toml +++ b/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" diff --git a/tauri-api/src/cli.rs b/tauri-api/src/cli.rs index 4b1af5c0a..a7e731161 100644 --- a/tauri-api/src/cli.rs +++ b/tauri-api/src/cli.rs @@ -58,7 +58,7 @@ pub fn get_matches(config: &Config) -> crate::Result { .tauri .cli .as_ref() - .ok_or_else(|| anyhow::anyhow!("CLI configuration not defined"))?; + .ok_or_else(|| crate::Error::CliNotConfigured)?; let about = cli .description() diff --git a/tauri-api/src/command.rs b/tauri-api/src/command.rs index a8c8fa459..b3001e071 100644 --- a/tauri-api/src/command.rs +++ b/tauri-api/src/command.rs @@ -16,7 +16,9 @@ pub fn get_output(cmd: String, args: Vec, 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, 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, stdout: Stdio) -> crate::Resul pub fn command_path(command: String) -> crate::Result { 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 { pub fn command_path(command: String) -> crate::Result { 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 { - 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::() { + 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::() { + 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); } diff --git a/tauri-api/src/dialog.rs b/tauri-api/src/dialog.rs index f9a8311ff..3f58edc34 100644 --- a/tauri-api/src/dialog.rs +++ b/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), } } diff --git a/tauri-api/src/error.rs b/tauri-api/src/error.rs new file mode 100644 index 000000000..b9054d764 --- /dev/null +++ b/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 for Error { + fn from(error: attohttpc::StatusCode) -> Self { + Self::Response(error) + } +} diff --git a/tauri-api/src/file.rs b/tauri-api/src/file.rs index 146532a4a..524c1535f 100644 --- a/tauri-api/src/file.rs +++ b/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>(file: P) -> crate::Result { - 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>(file: P) -> crate::Result> { - 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::() { + 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::() { + 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()); } } } diff --git a/tauri-api/src/http.rs b/tauri-api/src/http.rs index 9e9d72a47..2e61afbb7 100644 --- a/tauri-api/src/http.rs +++ b/tauri-api/src/http.rs @@ -265,7 +265,9 @@ pub fn make_request(options: HttpRequestOptions) -> crate::Result { 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 { }; Ok(response_data) } else { - Err(crate::Error::Network(response.status()).into()) + Err(response.status().into()) } } diff --git a/tauri-api/src/lib.rs b/tauri-api/src/lib.rs index efffd0a01..48710ac70 100644 --- a/tauri-api/src/lib.rs +++ b/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 = std::result::Result; // Not public API #[doc(hidden)] diff --git a/tauri-api/src/notification.rs b/tauri-api/src/notification.rs index 5e97c3142..4751868ea 100644 --- a/tauri-api/src/notification.rs +++ b/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(()) } } diff --git a/tauri-api/src/path.rs b/tauri-api/src/path.rs index 9c930c195..73471b569 100644 --- a/tauri-api/src/path.rs +++ b/tauri-api/src/path.rs @@ -84,7 +84,9 @@ pub fn resolve_path>(path: P, dir: Option) -> 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(); diff --git a/tauri-macros/src/expand.rs b/tauri-macros/src/expand.rs index cb0ebd767..fdf45807d 100644 --- a/tauri-macros/src/expand.rs +++ b/tauri-macros/src/expand.rs @@ -21,10 +21,8 @@ pub(crate) fn load_context(input: DeriveInput) -> Result { .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() } } diff --git a/tauri-utils/Cargo.toml b/tauri-utils/Cargo.toml index 2aa1bf431..911838d39 100644 --- a/tauri-utils/Cargo.toml +++ b/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" diff --git a/tauri-utils/src/lib.rs b/tauri-utils/src/lib.rs index b4568fa20..d360301fd 100644 --- a/tauri-utils/src/lib.rs +++ b/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 = std::result::Result; /// 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), } diff --git a/tauri-utils/src/platform.rs b/tauri-utils/src/platform.rs index f19fed3e9..4a02c0ba2 100644 --- a/tauri-utils/src/platform.rs +++ b/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 { +pub fn target_triple() -> crate::Result { let arch = if cfg!(target_arch = "x86") { "i686" } else if cfg!(target_arch = "x86_64") { @@ -19,7 +17,7 @@ pub fn target_triple() -> Result { } 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 { } 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 { } 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 { /// 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 { +pub fn resource_dir() -> crate::Result { 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 { } else if cfg!(target_os = "macos") { Ok(exe_dir.join("../Resources")) } else { - Err(crate::Error::Unknown.into()) + Err(crate::Error::UnsupportedPlatform) } } diff --git a/tauri/Cargo.toml b/tauri/Cargo.toml index 35c850826..2210bcd06 100644 --- a/tauri/Cargo.toml +++ b/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" } diff --git a/tauri/src/app/runner.rs b/tauri/src/app/runner.rs index 71e9b69c2..05abe67d9 100644 --- a/tauri/src/app/runner.rs +++ b/tauri/src/app/runner.rs @@ -37,7 +37,7 @@ pub(crate) fn run(application: App) -> 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> { // setup content for embedded server #[cfg(all(embedded_server, not(no_server)))] fn setup_content(context: &Context) -> crate::Result> { - 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> { // 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 { +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::().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::().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)); } } } diff --git a/tauri/src/endpoints.rs b/tauri/src/endpoints.rs index cc2465c65..47264dbd2 100644 --- a/tauri/src/endpoints.rs +++ b/tauri/src/endpoints.rs @@ -283,7 +283,7 @@ pub(crate) async fn handle( 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))] diff --git a/tauri/src/endpoints/asset.rs b/tauri/src/endpoints/asset.rs index e3bf215d9..a8b0252ce 100644 --- a/tauri/src/endpoints/asset.rs +++ b/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( dispatcher: &mut D, @@ -18,11 +19,7 @@ pub async fn load( 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( .to_string(); // how should that condition be handled now? - let asset_bytes = assets + let asset_bytes: Vec = 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( } 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( } else { dispatcher_.eval(asset_str); } - Ok("Asset loaded successfully".to_string()) + crate::Result::Ok("Asset loaded successfully".to_string()) } }, callback, diff --git a/tauri/src/endpoints/dialog.rs b/tauri/src/endpoints/dialog.rs index 070184e02..1ec00cf13 100644 --- a/tauri/src/endpoints/dialog.rs +++ b/tauri/src/endpoints/dialog.rs @@ -34,7 +34,8 @@ pub fn open( } 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( ) -> 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( 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, diff --git a/tauri/src/endpoints/file_system.rs b/tauri/src/endpoints/file_system.rs index 479a1c3f0..0ebff3fd0 100644 --- a/tauri/src/endpoints/file_system.rs +++ b/tauri/src/endpoints/file_system.rs @@ -28,7 +28,7 @@ pub async fn read_dir( } 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( ), 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( (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( (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( 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( ), 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( 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( 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( ) { 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( ) { 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, ) diff --git a/tauri/src/endpoints/http.rs b/tauri/src/endpoints/http.rs index 8e28c62e9..1cdb585db 100644 --- a/tauri/src/endpoints/http.rs +++ b/tauri/src/endpoints/http.rs @@ -8,5 +8,11 @@ pub async fn make_request( 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; } diff --git a/tauri/src/endpoints/notification.rs b/tauri/src/endpoints/notification.rs index 5cbbeb990..72be0f90c 100644 --- a/tauri/src/endpoints/notification.rs +++ b/tauri/src/endpoints/notification.rs @@ -24,7 +24,7 @@ pub async fn send( 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( 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( 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( 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, diff --git a/tauri/src/endpoints/path.rs b/tauri/src/endpoints/path.rs index eca59d01e..eea5543fb 100644 --- a/tauri/src/endpoints/path.rs +++ b/tauri/src/endpoints/path.rs @@ -12,7 +12,7 @@ pub async fn resolve_path( ) { 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, ) diff --git a/tauri/src/error.rs b/tauri/src/error.rs new file mode 100644 index 000000000..595e7f017 --- /dev/null +++ b/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 for Error { + fn from(error: serde_json::Error) -> Self { + if error.to_string().contains("unknown variant") { + Self::UnknownApi + } else { + Self::Json(error) + } + } +} diff --git a/tauri/src/lib.rs b/tauri/src/lib.rs index 3cde69fe1..af5ab4aea 100644 --- a/tauri/src/lib.rs +++ b/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 = std::result::Result; + pub(crate) mod async_runtime; /// A task to run on the main thread. pub type SyncTask = Box; -/// 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( ) { 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, ) diff --git a/tauri/src/plugin.rs b/tauri/src/plugin.rs index d18623926..e1ab188f8 100644 --- a/tauri/src/plugin.rs +++ b/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 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: Send + Sync { @@ -35,7 +14,7 @@ pub trait Plugin: 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: 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( store: &PluginStore, dispatcher: &mut D, arg: &str, -) -> Result { +) -> crate::Result { 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( return Ok(true); } Err(e) => match e { - Error::UnknownApi => {} + crate::Error::UnknownApi => {} _ => return Err(e), }, } diff --git a/tauri/src/settings.rs b/tauri/src/settings.rs index 8528a2d98..45ce18755 100644 --- a/tauri/src/settings.rs +++ b/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 { 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()) } diff --git a/tauri/src/webview/wry.rs b/tauri/src/webview/wry.rs index 79990c0bf..a1b4cdf83 100644 --- a/tauri/src/webview/wry.rs +++ b/tauri/src/webview/wry.rs @@ -152,8 +152,7 @@ impl ApplicationExt for WryApplication { } fn new() -> crate::Result { - 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(()) }