From 8faef894d59e00e2f7e2178baf32d5c632c02c74 Mon Sep 17 00:00:00 2001 From: Torben Schweren Date: Fri, 29 Dec 2023 18:31:08 +0100 Subject: [PATCH 1/2] Implement config - Add dirs crate - Implement ConfigHandler - Implement custom error types - Implement config struct - Load config on startup --- Cargo.toml | 1 + src/config.rs | 188 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 14 +++- 3 files changed, 202 insertions(+), 1 deletion(-) create mode 100644 src/config.rs diff --git a/Cargo.toml b/Cargo.toml index d39ef20..0100df8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ repository = "https://github.com/Kitt3120/lum" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +dirs = "5.0.1" serde = { version = "1.0.193", features = ["derive"] } serde_json = "1.0.108" sqlx = { version = "0.7.3", features = ["runtime-tokio", "any", "postgres", "mysql", "sqlite", "tls-native-tls", "migrate", "macros", "uuid", "chrono", "json"] } diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..7de079f --- /dev/null +++ b/src/config.rs @@ -0,0 +1,188 @@ +use core::fmt; +use std::{ + error::Error, + fmt::{Display, Formatter}, + fs, io, + path::PathBuf, +}; + +use serde::{Deserialize, Serialize}; + +//TODO: Use thiserror + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +pub enum ConfigPathError { + UnknownBasePath, +} + +impl Error for ConfigPathError {} + +impl Display for ConfigPathError { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + ConfigPathError::UnknownBasePath => write!(f, "Unable to get OS config directory"), + } + } +} + +#[derive(Debug)] +pub enum ConfigInitError { + PathError(ConfigPathError), + IOError(io::Error), +} + +impl Error for ConfigInitError {} + +impl Display for ConfigInitError { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + ConfigInitError::PathError(e) => write!(f, "Path error: {}", e), + ConfigInitError::IOError(e) => write!(f, "IO error: {}", e), + } + } +} + +impl From for ConfigInitError { + fn from(e: ConfigPathError) -> Self { + ConfigInitError::PathError(e) + } +} + +impl From for ConfigInitError { + fn from(e: std::io::Error) -> Self { + ConfigInitError::IOError(e) + } +} + +#[derive(Debug)] +pub enum ConfigParseError { + PathError(ConfigPathError), + InitError(ConfigInitError), + SerdeError(serde_json::Error), + IoError(io::Error), +} + +impl Error for ConfigParseError {} + +impl Display for ConfigParseError { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + ConfigParseError::PathError(e) => write!(f, "Path error: {}", e), + ConfigParseError::InitError(e) => write!(f, "Init error: {}", e), + ConfigParseError::SerdeError(e) => write!(f, "De-/Serialization error: {}", e), + ConfigParseError::IoError(e) => write!(f, "IO error: {}", e), + } + } +} + +impl From for ConfigParseError { + fn from(e: ConfigPathError) -> Self { + ConfigParseError::PathError(e) + } +} + +impl From for ConfigParseError { + fn from(e: ConfigInitError) -> Self { + ConfigParseError::InitError(e) + } +} + +impl From for ConfigParseError { + fn from(e: serde_json::Error) -> Self { + ConfigParseError::SerdeError(e) + } +} + +impl From for ConfigParseError { + fn from(e: io::Error) -> Self { + ConfigParseError::IoError(e) + } +} + +fn discord_token_default() -> String { + String::from("Please provide a token") +} + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +pub struct Config { + #[serde(rename = "discordToken", default = "discord_token_default")] + pub discord_token: String, +} + +impl Default for Config { + fn default() -> Self { + Config { + discord_token: discord_token_default(), + } + } +} + +impl Display for Config { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "discord_token: {}", self.discord_token) + } +} + +#[derive(Debug)] +pub struct ConfigHandler { + pub app_name: String, +} + +impl ConfigHandler { + pub fn new(app_name: &str) -> Self { + ConfigHandler { + app_name: app_name.to_string(), + } + } + + pub fn get_config_dir_path(&self) -> Result { + let mut path = match dirs::config_dir() { + Some(path) => path, + None => return Err(ConfigPathError::UnknownBasePath), + }; + + path.push(&self.app_name); + Ok(path) + } + + pub fn create_config_dir_path(&self) -> Result<(), ConfigInitError> { + let path = self.get_config_dir_path()?; + std::fs::create_dir_all(path)?; + Ok(()) + } + + pub fn get_config_file_path(&self) -> Result { + let mut path = self.get_config_dir_path()?; + path.push("config.json"); + Ok(path) + } + + pub fn save_config(&self, config: &Config) -> Result<(), ConfigParseError> { + let path = self.get_config_file_path()?; + + if !path.exists() { + self.create_config_dir_path()?; + } + + let config_json = serde_json::to_string_pretty(config)?; + + fs::write(path, config_json)?; + + Ok(()) + } + + pub fn get_config(&self) -> Result { + let path = self.get_config_file_path()?; + if !path.exists() { + self.create_config_dir_path()?; + fs::write(&path, "{}")?; + } + + let config_json = fs::read_to_string(path)?; + let config: Config = serde_json::from_str(&config_json)?; + + self.save_config(&config)?; // In case the config file was missing some fields which serde used the defaults for + + Ok(config) + } +} diff --git a/src/main.rs b/src/main.rs index e7a11a9..992058b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,15 @@ +mod config; + +pub const BOT_NAME: &str = "Lum"; + fn main() { - println!("Hello, world!"); + let config_handler = config::ConfigHandler::new(BOT_NAME.to_lowercase().as_str()); + let config = match config_handler.get_config() { + Ok(config) => config, + Err(err) => { + panic!("Error reading config file: {}", err); + } + }; + + println!("Config: {}", config); } From ea684813990c99393463db9cbdadcca230276750 Mon Sep 17 00:00:00 2001 From: Torben Schweren Date: Fri, 29 Dec 2023 21:19:05 +0100 Subject: [PATCH 2/2] Use thiserror - Add thiserror crate - Refactor existing error types to use thiserror --- Cargo.toml | 1 + src/config.rs | 99 +++++++++------------------------------------------ 2 files changed, 18 insertions(+), 82 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0100df8..ee0b2a2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,4 +16,5 @@ dirs = "5.0.1" serde = { version = "1.0.193", features = ["derive"] } serde_json = "1.0.108" sqlx = { version = "0.7.3", features = ["runtime-tokio", "any", "postgres", "mysql", "sqlite", "tls-native-tls", "migrate", "macros", "uuid", "chrono", "json"] } +thiserror = "1.0.52" tokio = { version = "1.35.1", features = ["full"] } diff --git a/src/config.rs b/src/config.rs index 7de079f..7a17995 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,102 +1,37 @@ use core::fmt; use std::{ - error::Error, fmt::{Display, Formatter}, fs, io, path::PathBuf, }; use serde::{Deserialize, Serialize}; +use thiserror::Error; -//TODO: Use thiserror - -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Error)] pub enum ConfigPathError { + #[error("Unable to get OS config directory")] UnknownBasePath, } -impl Error for ConfigPathError {} - -impl Display for ConfigPathError { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - ConfigPathError::UnknownBasePath => write!(f, "Unable to get OS config directory"), - } - } -} - -#[derive(Debug)] +#[derive(Debug, Error)] pub enum ConfigInitError { - PathError(ConfigPathError), - IOError(io::Error), -} - -impl Error for ConfigInitError {} - -impl Display for ConfigInitError { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - ConfigInitError::PathError(e) => write!(f, "Path error: {}", e), - ConfigInitError::IOError(e) => write!(f, "IO error: {}", e), - } - } -} - -impl From for ConfigInitError { - fn from(e: ConfigPathError) -> Self { - ConfigInitError::PathError(e) - } -} - -impl From for ConfigInitError { - fn from(e: std::io::Error) -> Self { - ConfigInitError::IOError(e) - } + #[error("Unable to get config path: {0}")] + Path(#[from] ConfigPathError), + #[error("I/O error: {0}")] + IO(#[from] io::Error), } -#[derive(Debug)] +#[derive(Debug, Error)] pub enum ConfigParseError { - PathError(ConfigPathError), - InitError(ConfigInitError), - SerdeError(serde_json::Error), - IoError(io::Error), -} - -impl Error for ConfigParseError {} - -impl Display for ConfigParseError { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - ConfigParseError::PathError(e) => write!(f, "Path error: {}", e), - ConfigParseError::InitError(e) => write!(f, "Init error: {}", e), - ConfigParseError::SerdeError(e) => write!(f, "De-/Serialization error: {}", e), - ConfigParseError::IoError(e) => write!(f, "IO error: {}", e), - } - } -} - -impl From for ConfigParseError { - fn from(e: ConfigPathError) -> Self { - ConfigParseError::PathError(e) - } -} - -impl From for ConfigParseError { - fn from(e: ConfigInitError) -> Self { - ConfigParseError::InitError(e) - } -} - -impl From for ConfigParseError { - fn from(e: serde_json::Error) -> Self { - ConfigParseError::SerdeError(e) - } -} - -impl From for ConfigParseError { - fn from(e: io::Error) -> Self { - ConfigParseError::IoError(e) - } + #[error("Unable to get config path: {0}")] + Path(#[from] ConfigPathError), + #[error("Unable to initialize config: {0}")] + Init(#[from] ConfigInitError), + #[error("Unable to serialize or deserialize config: {0}")] + Serde(#[from] serde_json::Error), + #[error("I/O error: {0}")] + IO(#[from] io::Error), } fn discord_token_default() -> String {