From e6619215395afbe107ba6594e595c758fcb3daf5 Mon Sep 17 00:00:00 2001 From: Pauline Date: Sat, 23 Mar 2024 04:15:11 +0100 Subject: [PATCH] error handling part 1 --- src-tauri/Cargo.toml | 1 + src-tauri/src/auth/microsoft_auth.rs | 2 +- src-tauri/src/constants.rs | 6 ++- src-tauri/src/game/client.rs | 70 ++++++++++++++++------------ src-tauri/src/game/java.rs | 0 src-tauri/src/lib.rs | 41 ++++++++++++++++ 6 files changed, 87 insertions(+), 33 deletions(-) create mode 100644 src-tauri/src/game/java.rs diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index bdcc89b7..d976302f 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -27,6 +27,7 @@ zip = { workspace = true } uuid = { workspace = true } async-trait = { workspace = true } prisma-client-rust = { workspace = true } +thiserror = { workspace = true } [features] # This feature is used for production builds or when a dev server is not specified, DO NOT REMOVE!! diff --git a/src-tauri/src/auth/microsoft_auth.rs b/src-tauri/src/auth/microsoft_auth.rs index c0ecac04..0a6cad9b 100644 --- a/src-tauri/src/auth/microsoft_auth.rs +++ b/src-tauri/src/auth/microsoft_auth.rs @@ -10,7 +10,7 @@ use super::{Account, AuthenticationMethod}; pub(super) struct MicrosoftAuthenticationMethod; impl AuthenticationMethod for MicrosoftAuthenticationMethod { - async fn auth(handle: &AppHandle, stage: F) -> Result> + async fn auth(handle: &AppHandle, stage: F) -> > where F: Fn(String, u8, bool) -> () { stage("Authenticating with Microsoft".into(), 0, false); diff --git a/src-tauri/src/constants.rs b/src-tauri/src/constants.rs index 344bfcc9..bad69356 100644 --- a/src-tauri/src/constants.rs +++ b/src-tauri/src/constants.rs @@ -1,5 +1,9 @@ +// Basic Metadata pub const NAME: &str = "OneLauncher"; pub const VERSION: &str = "0.1.0"; +// Authentication and HTTP (TODO: env?) pub const USER_AGENT: &str = "OneLauncher/1.0.0 (polyfrost.org)"; -pub const CLIENT_ID: &str = "9419b7ee-1448-4d1b-b52a-550d8f36ab56"; \ No newline at end of file +pub const CLIENT_ID: &str = "9419b7ee-1448-4d1b-b52a-550d8f36ab56"; + +// Java Downloading diff --git a/src-tauri/src/game/client.rs b/src-tauri/src/game/client.rs index ff274a2c..d240ea7c 100644 --- a/src-tauri/src/game/client.rs +++ b/src-tauri/src/game/client.rs @@ -1,12 +1,13 @@ -use std::{collections::HashMap, env, error::Error, fs, path::PathBuf}; +use std::{collections::HashMap, env, fs, path::PathBuf}; use async_trait::async_trait; use serde::{Deserialize, Serialize}; use serde_json::Value; use tauri::{AppHandle, Manager}; +use thiserror::Error; use uuid::Uuid; -use crate::utils::{file, http}; +use crate::{utils::{file, http}, PolyError, PolyResult}; use super::{clients::vanilla::{vanilla_manifest, VanillaClient, VanillaManifest}, JavaVersion}; @@ -61,8 +62,8 @@ pub fn get_game_client(handle: AppHandle, details: GameClientDetails) -> Box Self where Self: Sized; - async fn get_all_version_urls() -> Result, Box> where Self: Sized; - async fn get_minecraft_manifest(version: String) -> Result> where Self: Sized; + async fn get_all_version_urls() -> PolyResult> where Self: Sized; + async fn get_minecraft_manifest(version: String) -> PolyResult where Self: Sized; // async fn get_details_from_version(version: String) -> Result> where Self: Sized; @@ -72,13 +73,14 @@ pub trait GameClient: Send + Sync { &self.get_details().client_type } - async fn launch(&self) -> Result<(), Box>; - async fn setup(&self) -> Result<(), Box>; + async fn launch(&self) -> PolyResult<()>; + async fn setup(&self) -> PolyResult<()>; - async fn download_java(&self) -> Result { + async fn download_java(&self) -> PolyResult { let config_dir = match self.get_handle().path().app_config_dir() { Ok(dir) => dir, - Err(_) => return Err(JavaDownloadError::UnsupportedOS) + // SURELY THERES A BETTER WAY TO DO THIS IDK LOLL + Err(err) => return Err(PolyError::JavaError(JavaDownloadError::UnsupportedOS(err.to_string()))) }; let java_dir = config_dir.join("java"); @@ -86,7 +88,7 @@ pub trait GameClient: Send + Sync { if !java_dir.exists() { match fs::create_dir_all(&java_dir) { Ok(_) => (), - Err(_) => return Err(JavaDownloadError::PermissionDenied) + Err(err) => return Err(PolyError::JavaError(JavaDownloadError::PermissionDenied(err.to_string()))) }; } @@ -96,7 +98,7 @@ pub trait GameClient: Send + Sync { "windows" => "zip", "macos" => "tar.gz", "linux" => "tar.gz", - _ => return Err(JavaDownloadError::UnsupportedOS) + _ => return Err(PolyError::JavaError(JavaDownloadError::UnsupportedOS(os.to_string()))) }; let archive_name = format!("zulu-{}.{}", java_version.to_string(), archive_type); @@ -115,29 +117,35 @@ pub trait GameClient: Send + Sync { if let Ok(mut files) = fs::read_dir(&dest) { let file = match files.nth(0) { Some(file) => file.unwrap().path(), - None => return Err(JavaDownloadError::NoJavaVersionFound) + None => return Err(PolyError::JavaError(JavaDownloadError::NoJavaVersionFound("unable to get the java executable file".to_string()))) }; return Ok(file.join("bin").join("java")); } - Err(JavaDownloadError::NoJavaVersionFound) + Err(PolyError::JavaError(JavaDownloadError::NoJavaVersionFound("unable to download the java executable file".to_string()))) } } #[async_trait] pub trait ModdedGameClient: GameClient { - async fn install_mods(&self) -> Result<(), Box>; + async fn install_mods(&self) -> PolyResult<()>; } -#[derive(Debug)] +#[derive(Debug, Error)] pub enum JavaDownloadError { - NoJavaVersionFound, - PermissionDenied, - UnsupportedOS, - UnsupportedArch, - ExtractError, - DownloadError, + #[error("failed to find a valid java version: {0}")] + NoJavaVersionFound(String), + #[error("failed creating a java directory (try running at an elevated permission): {0}")] + PermissionDenied(String), + #[error("unable to match your operating system ({0}) with a valid java version")] + UnsupportedOS(String), + #[error("unable to match your architecture ({0}) with a valid java version")] + UnsupportedArch(String), + #[error("failed to extract the java executable: {0}")] + ExtractError(String), + #[error("failed to download the java executable")] + DownloadError(String), } fn get_arch() -> String { @@ -151,7 +159,7 @@ fn get_arch() -> String { }.to_string() } -async fn get_java_versions(java_version: &JavaVersion, os: &str, archive_type: &str) -> Result { +async fn get_java_versions(java_version: &JavaVersion, os: &str, archive_type: &str) -> PolyResult { let url = format!(" https://api.azul.com/metadata/v1/zulu/packages/\ ?java_version={}\ @@ -168,38 +176,38 @@ async fn get_java_versions(java_version: &JavaVersion, os: &str, archive_type: & let response = match http::create_client().get(&url).send().await { Ok(response) => match response.json::().await { Ok(json) => json, - Err(_) => return Err(JavaDownloadError::NoJavaVersionFound) + Err(err) => return Err(PolyError::JavaError(JavaDownloadError::NoJavaVersionFound(err.to_string()))) }, - Err(_) => return Err(JavaDownloadError::NoJavaVersionFound) + Err(err) => return Err(PolyError::JavaError(JavaDownloadError::NoJavaVersionFound(err.to_string()))) }; if !response.is_array() || response.as_array().unwrap().is_empty() { - return Err(JavaDownloadError::NoJavaVersionFound); + return Err(PolyError::JavaError(JavaDownloadError::NoJavaVersionFound("didn't get the expected java api result".to_string()))); } Ok(response) } -async fn download(java_version: &JavaVersion, os: &str, archive_type: &str, archive: &PathBuf) -> Result<(), JavaDownloadError> { +async fn download(java_version: &JavaVersion, os: &str, archive_type: &str, archive: &PathBuf) -> PolyResult<()> { let response = get_java_versions(java_version, os, archive_type).await?; let latest = response.as_array().unwrap().first().unwrap(); let download_url = latest.get("download_url").unwrap().as_str().unwrap(); if let Err(err) = http::download_file(download_url, archive.as_path()).await { - eprintln!("{}", err); - return Err(JavaDownloadError::DownloadError); + // eprintln!("{}", err); + return Err(PolyError::JavaError(JavaDownloadError::DownloadError(err.to_string()))); }; Ok(()) } -fn extract(archive: &PathBuf, dest: &PathBuf) -> Result<(), JavaDownloadError> { +fn extract(archive: &PathBuf, dest: &PathBuf) -> PolyResult<()> { if let Err(err) = file::extract_archive(archive.as_path(), dest.as_path()) { - eprintln!("{}", err); + // eprintln!("{}", err); let _ = fs::remove_file(dest.as_path()); - return Err(JavaDownloadError::ExtractError); + return Err(PolyError::JavaError(JavaDownloadError::ExtractError(err.to_string()))); } let _ = fs::remove_file(archive.as_path()); Ok(()) -} \ No newline at end of file +} diff --git a/src-tauri/src/game/java.rs b/src-tauri/src/game/java.rs new file mode 100644 index 00000000..e69de29b diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index ba79c6ca..a5c1250e 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,4 +1,45 @@ +use thiserror::Error; + pub mod auth; pub mod game; pub mod utils; pub mod constants; + +/// A standardized [`Error`] type that is used across the launcher +#[derive(Debug, Error)] +pub enum PolyError { + #[error("a tauri runtime error occured (this should not happen): {0}")] + /// Wrapper around [`tauri::Error`] to handle expected Tauri errors. + TauriError(#[from] tauri::Error), + #[error("failed to manage a tokio semaphore: {0}")] + /// Wrapper around [`tokio::sync::AcquireError`] to handle sempahore errors. + TokioError(#[from] tokio::sync::AcquireError), + #[error("failed to compress a file with flate: {0}")] + /// Wrapper around [`flate2::CompressError`] to handle flate compression errors. + FlateError(#[from] flate2::CompressError), + // im actually the funniest person alive dont @ me + #[error("failed to decompress a file with flate: {0}")] + /// Wrapper around [`flate2::DecompressError`] to handle flate decompression errors. + DeflateError(#[from] flate2::DecompressError), + #[error("failed to parse uuids: {0}")] + /// Wrapper around [`uuid::Error`] to handle UUID parsing errors. + UUIDError(#[from] uuid::Error), + #[error("failed to manage serialization of json: {0}")] + /// Wrapper around [`serde_json::Error`] to handle Serde JSON parsing errors. + SerdeError(#[from] serde_json::Error), + #[error("failed to manage writing and reading of files: {0}")] + /// Wrapper around [`std::io::Error`] only to be used if a more defined error isn't available (e.g. when using `zip`) + IOError(#[from] std::io::Error), + #[error("failed to query the database: {0}")] + /// Wrapper around [`prisma_client_rust::QueryError`] to handle DB query errors. + DBQueryError(#[from] prisma_client_rust::QueryError), + #[error("failed to establish a connection to the database: {0}")] + /// Wrapper around [`prisma_client_rust::NewClientError`] to handle errors occuring while programmatically connecting to Prisma. + DBConnectionError(#[from] prisma_client_rust::NewClientError), + #[error("failed to download java: {0}")] + /// Wrapper around [`game::client::JavaDownloadError`] to handle Java executable downloading. + JavaError(#[from] game::client::JavaDownloadError), +} + +/// Alias for a [`Result`] with the error type [`PolyError`]. +pub type PolyResult = std::result::Result;