mirror of
https://github.com/winapps-org/winapps.git
synced 2025-06-04 06:07:19 +02:00
feat: custom error handling, logging with tracing
This commit is contained in:
parent
6242117629
commit
2124e49db1
@ -4,3 +4,4 @@ members = [
|
||||
"winapps-cli",
|
||||
"winapps-gui",
|
||||
]
|
||||
resolver = "2"
|
@ -6,7 +6,11 @@ edition = "2021"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.75"
|
||||
derive-new = "0.5.9"
|
||||
home = "0.5.5"
|
||||
lazy_static = "1.4.0"
|
||||
serde = { version = "1.0.171", features = ["derive"] }
|
||||
toml = "0.7.6"
|
||||
thiserror = "1.0.49"
|
||||
toml = "0.8.2"
|
||||
tracing = "0.1.37"
|
||||
|
145
winapps/src/errors.rs
Normal file
145
winapps/src/errors.rs
Normal file
@ -0,0 +1,145 @@
|
||||
#![allow(clippy::crate_in_macro_def)]
|
||||
|
||||
use std::error::Error;
|
||||
use std::fmt::Debug;
|
||||
use std::process::exit;
|
||||
|
||||
/// This enum represents all possible errors that can occur in this crate.
|
||||
/// It is used as a return type for most functions should they return an error.
|
||||
/// There's 2 base variants: `Message` and `WithError`.
|
||||
/// `Message` is used for simple errors that don't have an underlying cause.
|
||||
/// `WithError` is used for errors that occur from another error.
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum WinappsError {
|
||||
#[error("{0}")]
|
||||
Message(String),
|
||||
#[error("{0}\n{1}")]
|
||||
WithError(#[source] anyhow::Error, String),
|
||||
}
|
||||
|
||||
impl WinappsError {
|
||||
/// This function prints the error to the console.
|
||||
/// It is used internally by the `unrecoverable` and `panic` functions.
|
||||
/// All lines are logged as seperate messages, and the source of the error is also logged if it exists.
|
||||
fn error(&self) {
|
||||
let messages: Vec<String> = self.to_string().split('\n').map(|s| s.into()).collect();
|
||||
messages.iter().for_each(|s| tracing::error!("{}", s));
|
||||
|
||||
if self.source().is_some() {
|
||||
tracing::error!("Caused by: {}", self.source().unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
/// This function prints the error to the console and exits the program with an exit code of 1.
|
||||
pub fn unrecoverable(&self) -> ! {
|
||||
self.error();
|
||||
|
||||
tracing::error!("Unrecoverable error, exiting...");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/// This function prints the error to the console and panics.
|
||||
pub fn panic(&self) -> ! {
|
||||
self.error();
|
||||
|
||||
panic!("Program crashed, see log above");
|
||||
}
|
||||
}
|
||||
|
||||
/// This macro is a shortcut for creating a `WinappsError` from a string.
|
||||
/// You can use normal `format!` syntax inside the macro.
|
||||
#[macro_export]
|
||||
macro_rules! error {
|
||||
($($fmt:tt)*) => {
|
||||
crate::errors::WinappsError::Message(format!($($fmt)*))
|
||||
};
|
||||
}
|
||||
|
||||
/// This macro is a shortcut for creating a `WinappsError` from a string.
|
||||
/// The first argument is the source error.
|
||||
/// You can use normal `format!` syntax inside the macro.
|
||||
#[macro_export]
|
||||
macro_rules! error_from {
|
||||
($err:expr, $($fmt:tt)*) => {
|
||||
crate::errors::WinappsError::WithError(anyhow::Error::new($err), format!($($fmt)*))
|
||||
};
|
||||
}
|
||||
|
||||
/// This trait serves as a generic way to convert a `Result` or `Option` into a `WinappsError`.
|
||||
pub trait IntoError<T> {
|
||||
fn into_error(self, msg: String) -> Result<T, WinappsError>;
|
||||
}
|
||||
|
||||
impl<T, U> IntoError<T> for Result<T, U>
|
||||
where
|
||||
T: Debug,
|
||||
U: Error + Send + Sync + 'static,
|
||||
{
|
||||
fn into_error(self, msg: String) -> Result<T, WinappsError> {
|
||||
if let Err(error) = self {
|
||||
return Err(WinappsError::WithError(anyhow::Error::new(error), msg));
|
||||
}
|
||||
|
||||
Ok(self.unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoError<T> for Option<T> {
|
||||
fn into_error(self, msg: String) -> Result<T, WinappsError> {
|
||||
if self.is_none() {
|
||||
return Err(WinappsError::Message(msg));
|
||||
}
|
||||
|
||||
Ok(self.unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
/// This function unwraps a `Result` or `Option` and returns the value if it exists.
|
||||
/// Should the value not exist, then the program will exit with an exit code of 1.
|
||||
/// Called internally by the `unwrap_or_exit!` macro, which you should probably use instead.
|
||||
pub fn unwrap_or_exit<T, U>(val: U, msg: String) -> T
|
||||
where
|
||||
T: Sized + Debug,
|
||||
U: IntoError<T>,
|
||||
{
|
||||
val.into_error(msg).unwrap_or_else(|e| e.unrecoverable())
|
||||
}
|
||||
|
||||
/// This function unwraps a `Result` or `Option` and returns the value if it exists.
|
||||
/// Should the value not exist, then the program will panic.
|
||||
/// Called internally by the `unwrap_or_panic!` macro, which you should probably use instead.
|
||||
pub fn unwrap_or_panic<T, U>(val: U, msg: String) -> T
|
||||
where
|
||||
T: Sized + Debug,
|
||||
U: IntoError<T>,
|
||||
{
|
||||
val.into_error(msg).unwrap_or_else(|e| e.panic())
|
||||
}
|
||||
|
||||
/// This macro unwraps a `Result` or `Option` and returns the value if it exists.
|
||||
/// Should the value not exist, then the program will exit with exit code 1.
|
||||
/// Optionally, a message can be passed to the function which uses standard `format!` syntax.
|
||||
/// The result type has to implement `Debug` and `Sized`, and the error type has to implement `Error`, `Send`, `Sync` has to be `'static`.
|
||||
#[macro_export]
|
||||
macro_rules! unwrap_or_exit {
|
||||
($expr:expr) => {{
|
||||
crate::errors::unwrap_or_exit($expr, "Expected a value, got None / Error".into())
|
||||
}};
|
||||
($expr:expr, $($fmt:tt)*) => {{
|
||||
crate::errors::unwrap_or_exit($expr, format!($($fmt)*))
|
||||
}};
|
||||
}
|
||||
|
||||
/// This macro unwraps a `Result` or `Option` and returns the value if it exists.
|
||||
/// Should the value not exist, then the program will panic.
|
||||
/// Optionally, a message can be passed to the function which uses standard `format!` syntax.
|
||||
/// The result type has to implement `Debug` and `Sized`, and the error type has to implement `Error`, `Send`, `Sync` has to be `'static`.
|
||||
#[macro_export]
|
||||
macro_rules! unwrap_or_panic {
|
||||
($expr:expr) => {{
|
||||
crate::errors::unwrap_or_panic($expr, "Expected a value, got None / Error".into())
|
||||
}};
|
||||
($expr:expr, $($fmt:tt)*) => {{
|
||||
crate::errors::unwrap_or_panic($expr, format!($($fmt)*))
|
||||
}};
|
||||
}
|
@ -1,5 +1,8 @@
|
||||
pub mod errors;
|
||||
pub mod freerdp;
|
||||
pub mod quickemu;
|
||||
|
||||
use crate::errors::WinappsError;
|
||||
use derive_new::new;
|
||||
use home::home_dir;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@ -10,8 +13,7 @@ use std::{
|
||||
fs::{self, File},
|
||||
path::Path,
|
||||
};
|
||||
|
||||
pub mod freerdp;
|
||||
use tracing::{info, warn};
|
||||
|
||||
pub trait RemoteClient {
|
||||
fn check_depends(&self, config: Config);
|
||||
@ -59,22 +61,24 @@ pub fn get_config_file(path: Option<&str>) -> PathBuf {
|
||||
let default = match env::var("XDG_CONFIG_HOME") {
|
||||
Ok(dir) => PathBuf::from(dir).join("winapps"),
|
||||
Err(_) => {
|
||||
println!("Couldn't read XDG_CONFIG_HOME, falling back to ~/.config");
|
||||
home_dir()
|
||||
.expect("Could not find the home path!")
|
||||
.join(".config/winapps")
|
||||
warn!("Couldn't read XDG_CONFIG_HOME, falling back to ~/.config");
|
||||
unwrap_or_panic!(home_dir(), "Couldn't find the home directory").join(".config/winapps")
|
||||
}
|
||||
};
|
||||
|
||||
let path = Path::new(path.unwrap_or(default.to_str().unwrap()));
|
||||
let path = Path::new(path.unwrap_or(unwrap_or_panic!(
|
||||
default.to_str(),
|
||||
"Couldn't convert path {:?} to string",
|
||||
default
|
||||
)));
|
||||
|
||||
if !path.exists() {
|
||||
println!("{:?} does not exist! Creating...", path);
|
||||
info!("{:?} does not exist! Creating...", path);
|
||||
fs::create_dir_all(path).expect("Failed to create directory");
|
||||
}
|
||||
|
||||
if !path.is_dir() {
|
||||
panic!("Config directory {:?} is not a directory!", path);
|
||||
error!("Config directory {:?} is not a directory", path).panic();
|
||||
}
|
||||
|
||||
path.join("config.toml")
|
||||
@ -85,54 +89,76 @@ pub fn load_config(path: Option<&str>) -> Config {
|
||||
let config_path = get_config_file(path);
|
||||
|
||||
if !config_path.exists() {
|
||||
save_config(&config, path).expect("Failed to write default configuration");
|
||||
unwrap_or_panic!(
|
||||
save_config(&config, path),
|
||||
"Failed to write default configuration"
|
||||
);
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
let config_file = fs::read_to_string(config_path).expect("Failed to read configuration file");
|
||||
let config: Config =
|
||||
toml::from_str(config_file.as_str()).expect("Failed to parse the configuration");
|
||||
let config_file = unwrap_or_panic!(
|
||||
fs::read_to_string(config_path),
|
||||
"Failed to read configuration file"
|
||||
);
|
||||
|
||||
let config: Config = unwrap_or_panic!(
|
||||
toml::from_str(config_file.as_str()),
|
||||
"Failed to parse configuration file",
|
||||
);
|
||||
|
||||
config
|
||||
}
|
||||
|
||||
pub fn save_config(config: &Config, path: Option<&str>) -> std::io::Result<()> {
|
||||
pub fn save_config(config: &Config, path: Option<&str>) -> Result<(), WinappsError> {
|
||||
let config_path = get_config_file(path);
|
||||
let serialized_config = toml::to_string(&config).expect("Failed to serialize configuration");
|
||||
let serialized_config = unwrap_or_panic!(
|
||||
toml::to_string(&config),
|
||||
"Failed to serialize configuration"
|
||||
);
|
||||
|
||||
let mut config_file = match config_path.exists() {
|
||||
true => File::open(&config_path).expect("Failed to open configuration file"),
|
||||
false => File::create(&config_path).expect("Failed to create configuration file"),
|
||||
true => unwrap_or_panic!(
|
||||
File::open(&config_path),
|
||||
"Failed to open configuration file"
|
||||
),
|
||||
false => unwrap_or_panic!(
|
||||
File::create(&config_path),
|
||||
"Failed to create configuration file"
|
||||
),
|
||||
};
|
||||
|
||||
write!(config_file, "{}", serialized_config)
|
||||
if let Err(e) = write!(config_file, "{}", serialized_config) {
|
||||
return Err(error_from!(e, "Failed to write configuration file"));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_data_dir() -> PathBuf {
|
||||
let data_dir = match env::var("XDG_DATA_HOME") {
|
||||
let path = match env::var("XDG_DATA_HOME") {
|
||||
Ok(dir) => PathBuf::from(dir).join("winapps"),
|
||||
Err(_) => {
|
||||
println!("Couldn't read XDG_DATA_HOME, falling back to ~/.local/share");
|
||||
home_dir()
|
||||
.expect("Could not find the home path!")
|
||||
warn!("Couldn't read XDG_DATA_HOME, falling back to ~/.local/share");
|
||||
unwrap_or_panic!(home_dir(), "Couldn't find the home directory")
|
||||
.join(".local/share/winapps")
|
||||
}
|
||||
};
|
||||
|
||||
if !data_dir.exists() {
|
||||
let dir = data_dir.clone();
|
||||
println!(
|
||||
if !path.exists() {
|
||||
let dir = path.clone();
|
||||
info!(
|
||||
"Data directory {:?} does not exist! Creating...",
|
||||
dir.to_str()
|
||||
);
|
||||
fs::create_dir_all(dir).expect("Failed to create directory");
|
||||
}
|
||||
|
||||
if !data_dir.is_dir() {
|
||||
panic!("Data directory {:?} is not a directory!", data_dir);
|
||||
if !path.is_dir() {
|
||||
error!("Data directory {:?} is not a directory", path).panic();
|
||||
}
|
||||
|
||||
data_dir
|
||||
path
|
||||
}
|
||||
|
||||
pub fn add(left: usize, right: usize) -> usize {
|
||||
|
Loading…
x
Reference in New Issue
Block a user