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

This commit is contained in:
Lucas Fernandes Nogueira 2021-02-10 19:51:15 -03:00 committed by GitHub
parent df32e18be3
commit 156a0ad5cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 269 additions and 205 deletions

5
.changes/core-errors.md Normal file
View File

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

View File

@ -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"

View File

@ -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()

View File

@ -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);
}

View File

@ -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
tauri-api/src/error.rs Normal file
View File

@ -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)
}
}

View File

@ -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());
}
}
}

View File

@ -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())
}
}

View File

@ -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)]

View File

@ -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(())
}
}

View File

@ -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();

View File

@ -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()
}
}

View File

@ -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"

View File

@ -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),
}

View File

@ -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)
}
}

View File

@ -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" }

View File

@ -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));
}
}
}

View File

@ -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))]

View File

@ -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,

View File

@ -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,

View File

@ -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,
)

View File

@ -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;
}

View File

@ -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,

View File

@ -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
tauri/src/error.rs Normal file
View File

@ -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)
}
}
}

View File

@ -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,
)

View File

@ -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),
},
}

View File

@ -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())
}

View File

@ -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(())
}