feat(errors): Using SerializeDisplay for better error management with Result

This commit is contained in:
quexeky
2025-01-19 17:17:51 +11:00
parent c2f54c1dbc
commit 170fde5e23
15 changed files with 74 additions and 107 deletions

View File

@ -6,7 +6,7 @@ use std::{
use serde_json::Value;
use crate::{database::settings::Settings, error::user_error::UserValue, DB};
use crate::{database::settings::Settings, download_manager::{download_manager::DownloadManagerSignal, internal_error::InternalError}, DB};
use super::{db::DATA_ROOT_DIR, debug::SystemData};
@ -27,16 +27,16 @@ pub fn delete_download_dir(index: usize) {
}
#[tauri::command]
pub fn add_download_dir(new_dir: PathBuf) -> UserValue<(), Error> {
pub fn add_download_dir(new_dir: PathBuf) -> Result<(), InternalError<()>> {
// Check the new directory is all good
let new_dir_path = Path::new(&new_dir);
if new_dir_path.exists() {
let dir_contents = new_dir_path.read_dir()?;
if dir_contents.count() != 0 {
return UserValue::Err(Error::new(
return Err(Error::new(
ErrorKind::DirectoryNotEmpty,
"Selected directory cannot contain any existing files",
));
).into());
}
} else {
create_dir_all(new_dir_path)?;
@ -45,16 +45,16 @@ pub fn add_download_dir(new_dir: PathBuf) -> UserValue<(), Error> {
// Add it to the dictionary
let mut lock = DB.borrow_data_mut().unwrap();
if lock.applications.install_dirs.contains(&new_dir) {
return UserValue::Err(Error::new(
return Err(Error::new(
ErrorKind::AlreadyExists,
"Selected directory already exists in database",
));
).into());
}
lock.applications.install_dirs.push(new_dir);
drop(lock);
DB.save().unwrap();
UserValue::Ok(())
Ok(())
}
#[tauri::command]

View File

@ -0,0 +1,27 @@
use std::{fmt::Display, io, sync::mpsc::SendError};
use serde_with::SerializeDisplay;
#[derive(SerializeDisplay)]
pub enum InternalError<T> {
IOError(io::Error),
SignalError(SendError<T>)
}
impl<T> Display for InternalError<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
InternalError::IOError(error) => write!(f, "{}", error.to_string()),
InternalError::SignalError(send_error) => write!(f, "{}", send_error.to_string()),
}
}
}
impl<T> From<SendError<T>> for InternalError<T> {
fn from(value: SendError<T>) -> Self {
InternalError::SignalError(value)
}
}
impl<T> From<io::Error> for InternalError<T> {
fn from(value: io::Error) -> Self {
InternalError::IOError(value)
}
}

View File

@ -7,3 +7,4 @@ pub mod downloadable_metadata;
pub mod progress_object;
pub mod queue;
pub mod rolling_progress_updates;
pub mod internal_error;

View File

@ -3,10 +3,12 @@ use std::{
io,
};
use serde_with::SerializeDisplay;
use super::{remote_access_error::RemoteAccessError, setup_error::SetupError};
// TODO: Rename / separate from downloads
#[derive(Debug, Clone)]
#[derive(Debug, Clone, SerializeDisplay)]
pub enum ApplicationDownloadError {
Communication(RemoteAccessError),
Checksum,

View File

@ -1,5 +1,8 @@
use std::fmt::Display;
use serde_with::SerializeDisplay;
#[derive(SerializeDisplay)]
pub enum LibraryError {
MetaNotFound(String),
}

View File

@ -4,4 +4,3 @@ pub mod library_error;
pub mod process_error;
pub mod remote_access_error;
pub mod setup_error;
pub mod user_error;

View File

@ -1,5 +1,8 @@
use std::{fmt::Display, io::Error};
use serde_with::SerializeDisplay;
#[derive(SerializeDisplay)]
pub enum ProcessError {
SetupRequired,
NotInstalled,

View File

@ -5,11 +5,12 @@ use std::{
};
use http::StatusCode;
use serde_with::SerializeDisplay;
use url::ParseError;
use super::drop_server_error::DropServerError;
#[derive(Debug, Clone)]
#[derive(Debug, Clone, SerializeDisplay)]
pub enum RemoteAccessError {
FetchError(Arc<reqwest::Error>),
ParsingError(ParseError),

View File

@ -1,66 +0,0 @@
use std::{
fmt::Display,
ops::{FromResidual, Try},
};
use serde::Serialize;
#[derive(Debug)]
pub enum UserValue<T, D>
where
T: Serialize,
D: Display,
{
Ok(T),
Err(D),
}
impl<T: Serialize, D: Display> Serialize for UserValue<T, D> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self {
UserValue::Ok(data) => data.serialize(serializer),
UserValue::Err(err) => serializer.serialize_str(err.to_string().as_ref()),
}
}
}
impl<T: Serialize, D: Display> From<Result<T, D>> for UserValue<T, D> {
fn from(value: Result<T, D>) -> Self {
match value {
Ok(data) => UserValue::Ok(data),
Err(data) => UserValue::Err(data),
}
}
}
impl<T: Serialize, D: Display> Try for UserValue<T, D> {
type Output = T;
type Residual = D;
fn from_output(output: Self::Output) -> Self {
Self::Ok(output)
}
fn branch(self) -> std::ops::ControlFlow<Self::Residual, Self::Output> {
match self {
UserValue::Ok(data) => std::ops::ControlFlow::Continue(data),
UserValue::Err(e) => std::ops::ControlFlow::Break(e),
}
}
}
impl<T: Serialize, D: Display> FromResidual for UserValue<T, D> {
fn from_residual(residual: <Self as std::ops::Try>::Residual) -> Self {
UserValue::Err(residual)
}
}
impl<T: Serialize, D: Display, U> FromResidual<Result<U, D>> for UserValue<T, D> {
fn from_residual(residual: Result<U, D>) -> Self {
match residual {
Ok(_) => unreachable!(),
Err(e) => UserValue::Err(e),
}
}
}

View File

@ -4,7 +4,7 @@ use tauri::AppHandle;
use crate::{
error::{
library_error::LibraryError, remote_access_error::RemoteAccessError, user_error::UserValue,
library_error::LibraryError, remote_access_error::RemoteAccessError,
},
games::library::{get_current_meta, uninstall_game_logic},
AppState,
@ -19,16 +19,16 @@ use super::{
};
#[tauri::command]
pub fn fetch_library(app: AppHandle) -> UserValue<Vec<Game>, RemoteAccessError> {
fetch_library_logic(app).into()
pub fn fetch_library(app: AppHandle) -> Result<Vec<Game>, RemoteAccessError> {
fetch_library_logic(app)
}
#[tauri::command]
pub fn fetch_game(
game_id: String,
app: tauri::AppHandle,
) -> UserValue<FetchGameStruct, RemoteAccessError> {
fetch_game_logic(game_id, app).into()
) -> Result<FetchGameStruct, RemoteAccessError> {
fetch_game_logic(game_id, app)
}
#[tauri::command]
@ -37,21 +37,21 @@ pub fn fetch_game_status(id: String) -> GameStatusWithTransient {
}
#[tauri::command]
pub fn uninstall_game(game_id: String, app_handle: AppHandle) -> UserValue<(), LibraryError> {
pub fn uninstall_game(game_id: String, app_handle: AppHandle) -> Result<(), LibraryError> {
let meta = match get_current_meta(&game_id) {
Some(data) => data,
None => return UserValue::Err(LibraryError::MetaNotFound(game_id)),
None => return Err(LibraryError::MetaNotFound(game_id)),
};
println!("{:?}", meta);
uninstall_game_logic(meta, &app_handle);
UserValue::Ok(())
Ok(())
}
#[tauri::command]
pub fn fetch_game_verion_options(
game_id: String,
state: tauri::State<'_, Mutex<AppState>>,
) -> UserValue<Vec<GameVersionOption>, RemoteAccessError> {
fetch_game_verion_options_logic(game_id, state).into()
) -> Result<Vec<GameVersionOption>, RemoteAccessError> {
fetch_game_verion_options_logic(game_id, state)
}

View File

@ -1,8 +1,7 @@
use std::sync::{mpsc::SendError, Arc, Mutex};
use crate::{
download_manager::{download_manager::DownloadManagerSignal, downloadable::Downloadable},
error::user_error::UserValue,
download_manager::{download_manager::DownloadManagerSignal, downloadable::Downloadable, internal_error::InternalError},
AppState,
};
@ -14,7 +13,7 @@ pub fn download_game(
game_version: String,
install_dir: usize,
state: tauri::State<'_, Mutex<AppState>>,
) -> UserValue<(), SendError<DownloadManagerSignal>> {
) -> Result<(), InternalError<DownloadManagerSignal>> {
let sender = state.lock().unwrap().download_manager.get_sender();
let game_download_agent = Arc::new(Box::new(GameDownloadAgent::new(
game_id,
@ -22,10 +21,9 @@ pub fn download_game(
install_dir,
sender,
)) as Box<dyn Downloadable + Send + Sync>);
state
Ok(state
.lock()
.unwrap()
.download_manager
.queue_download(game_download_agent)
.into()
.queue_download(game_download_agent)?)
}

View File

@ -136,7 +136,7 @@ impl GameDownloadAgent {
let manifest_url = base_url
.join(
format!(
"/api/v1/client/metadata/manifest?id={}&version={}",
"/api/v1/client/game/manifest?id={}&version={}",
self.id,
encode(&self.version)
)

View File

@ -180,7 +180,7 @@ pub fn fetch_game_verion_options_logic(
) -> Result<Vec<GameVersionOption>, RemoteAccessError> {
let client = reqwest::blocking::Client::new();
let response = make_request(&client, &["/api/v1/client/metadata/versions"], &[("id", &game_id)], |r| {
let response = make_request(&client, &["/api/v1/client/game/versions"], &[("id", &game_id)], |r| {
r.header("Authorization", generate_authorization_header())
})?.send()?;

View File

@ -1,15 +1,15 @@
use std::sync::Mutex;
use crate::{
error::{process_error::ProcessError, user_error::UserValue},
AppState, DB,
error::process_error::ProcessError,
AppState,
};
#[tauri::command]
pub fn launch_game(
id: String,
state: tauri::State<'_, Mutex<AppState>>,
) -> UserValue<(), ProcessError> {
) -> Result<(), ProcessError> {
let state_lock = state.lock().unwrap();
let mut process_manager_lock = state_lock.process_manager.lock().unwrap();
@ -21,24 +21,23 @@ pub fn launch_game(
match process_manager_lock.launch_process(id) {
Ok(_) => {}
Err(e) => return UserValue::Err(e),
Err(e) => return Err(e),
};
drop(process_manager_lock);
drop(state_lock);
UserValue::Ok(())
Ok(())
}
#[tauri::command]
pub fn kill_game(
game_id: String,
state: tauri::State<'_, Mutex<AppState>>,
) -> UserValue<(), ProcessError> {
) -> Result<(), ProcessError> {
let state_lock = state.lock().unwrap();
let mut process_manager_lock = state_lock.process_manager.lock().unwrap();
process_manager_lock
.kill_game(game_id)
.map_err(ProcessError::IOError)
.into()
}

View File

@ -4,7 +4,7 @@ use tauri::{AppHandle, Emitter, Manager};
use url::Url;
use crate::{
error::{remote_access_error::RemoteAccessError, user_error::UserValue},
error::remote_access_error::RemoteAccessError,
AppState, AppStatus, DB,
};
@ -17,12 +17,12 @@ use super::{
pub fn use_remote(
url: String,
state: tauri::State<'_, Mutex<AppState<'_>>>,
) -> UserValue<(), RemoteAccessError> {
UserValue::Ok(use_remote_logic(url, state)?)
) -> Result<(), RemoteAccessError> {
Ok(use_remote_logic(url, state)?)
}
#[tauri::command]
pub fn gen_drop_url(path: String) -> UserValue<String, RemoteAccessError> {
pub fn gen_drop_url(path: String) -> Result<String, RemoteAccessError> {
let base_url = {
let handle = DB.borrow_data().unwrap();
@ -31,7 +31,7 @@ pub fn gen_drop_url(path: String) -> UserValue<String, RemoteAccessError> {
let url = base_url.join(&path).unwrap();
UserValue::Ok(url.to_string())
Ok(url.to_string())
}
#[tauri::command]
@ -67,7 +67,7 @@ pub fn retry_connect(state: tauri::State<'_, Mutex<AppState>>) {
}
#[tauri::command]
pub fn auth_initiate() -> UserValue<(), RemoteAccessError> {
pub fn auth_initiate() -> Result<(), RemoteAccessError> {
auth_initiate_logic().into()
}