Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ fern = { version = "0.6.2", features = ["chrono", "colored", "date-based"] }
humantime = "2.1.0"
log = { version = "0.4.20", features = ["serde"] }
serde = { version = "1.0.193", features = ["derive"] }
serde-env = "0.2.0"
serde_json = "1.0.108"
serenity = { version = "0.12.0", default-features=false, features = ["builder", "cache", "collector", "client", "framework", "gateway", "http", "model", "standard_framework", "utils", "voice", "default_native_tls", "tokio_task_builder", "unstable_discord_api", "simd_json", "temp_cache", "chrono", "interactions_endpoint"] }
sqlx = { version = "0.8.0", features = ["runtime-tokio", "any", "postgres", "mysql", "sqlite", "tls-native-tls", "migrate", "macros", "uuid", "chrono", "json"] }
Expand Down
136 changes: 9 additions & 127 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -1,129 +1,11 @@
use core::fmt;
use serde::{Deserialize, Serialize};
use std::{
fmt::{Display, Formatter},
fs, io,
path::PathBuf,
};
use thiserror::Error;

#[derive(Debug, Error)]
pub enum ConfigPathError {
#[error("Unable to get OS config directory")]
UnknownBasePath,
}

#[derive(Debug, Error)]
pub enum ConfigInitError {
#[error("Unable to get config path: {0}")]
Path(#[from] ConfigPathError),
#[error("I/O error: {0}")]
IO(#[from] io::Error),
}

#[derive(Debug, Error)]
pub enum ConfigParseError {
#[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 {
String::from("Please provide a token")
}

#[derive(Debug, PartialEq, PartialOrd, Serialize, Deserialize, Clone)]
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 {
let content = match serde_json::to_string(self) {
Ok(content) => content,
Err(error) => {
return write!(f, "Unable to serialize config: {}", error);
}
};

write!(f, "{}", content)
}
}

#[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<PathBuf, ConfigPathError> {
let mut path = match dirs::config_dir() {
Some(path) => path,
None => return Err(ConfigPathError::UnknownBasePath),
};
pub mod config_handler;
pub mod environment_config;
pub mod file_config;

path.push(&self.app_name);
Ok(path)
}

pub fn create_config_dir_path(&self) -> Result<(), ConfigInitError> {
let path = self.get_config_dir_path()?;
fs::create_dir_all(path)?;
Ok(())
}

pub fn get_config_file_path(&self) -> Result<PathBuf, ConfigPathError> {
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 load_config(&self) -> Result<Config, ConfigParseError> {
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
pub use config_handler::{
ConfigHandler, ConfigInitError, ConfigParseError, ConfigPathError, ConfigSaveError,
EnvironmentConfigParseError, FileConfigParseError, Merge,
};

Ok(config)
}
}
pub use environment_config::EnvironmentConfig;
pub use file_config::FileConfig;
165 changes: 165 additions & 0 deletions src/config/config_handler.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
use std::{fs, io, marker::PhantomData, path::PathBuf};

use serde::{Deserialize, Serialize};
use thiserror::Error;

pub trait Merge<T> {
fn merge(&self, other: &T) -> Self;
}

#[derive(Debug, Error)]
pub enum ConfigPathError {
#[error("Unable to get OS-specific config directory")]
UnknownBasePath,
}

#[derive(Debug, Error)]
pub enum ConfigInitError {
#[error("Unable to get config path: {0}")]
Path(#[from] ConfigPathError),

#[error("I/O error: {0}")]
IO(#[from] io::Error),
}

#[derive(Debug, Error)]
pub enum ConfigSaveError {
#[error("Unable to get config path: {0}")]
Path(#[from] ConfigPathError),

#[error("Unable to init config: {0}")]
Init(#[from] ConfigInitError),

#[error("Unable to serialize config: {0}")]
Serde(#[from] serde_json::Error),

#[error("I/O error: {0}")]
IO(#[from] io::Error),
}

#[derive(Debug, Error)]
pub enum FileConfigParseError {
#[error("Unable to get config path: {0}")]
Path(#[from] ConfigPathError),

#[error("Unable to initialize config: {0}")]
Init(#[from] ConfigInitError),

#[error("Unable to save config: {0}")]
Save(#[from] ConfigSaveError),

#[error("I/O error: {0}")]
IO(#[from] io::Error),

#[error("Unable to serialize or deserialize config: {0}")]
Serde(#[from] serde_json::Error),
}

#[derive(Debug, Error)]
pub enum EnvironmentConfigParseError {
#[error("Unable to parse environment variables: {0}")]
Envy(#[from] serde_env::Error),
}

#[derive(Debug, Error)]
pub enum ConfigParseError {
#[error("Unable to parse config from file: {0}")]
File(#[from] FileConfigParseError),

#[error("Unable to parse config from environment: {0}")]
Env(#[from] EnvironmentConfigParseError),
}

#[derive(Debug)]
pub struct ConfigHandler<FILE, ENV>
where
FILE: Serialize + for<'de> Deserialize<'de> + Merge<ENV>,
ENV: Serialize + for<'de> Deserialize<'de>,
{
pub app_name: String,
_phantom_file: PhantomData<FILE>,
_phantom_env: PhantomData<ENV>,
}

impl<FILE, ENV> ConfigHandler<FILE, ENV>
where
FILE: Serialize + for<'de> Deserialize<'de> + Merge<ENV>,
ENV: Serialize + for<'de> Deserialize<'de>,
{
pub fn new(app_name: &str) -> Self {
ConfigHandler {
app_name: app_name.to_string(),
_phantom_file: PhantomData,
_phantom_env: PhantomData,
}
}

pub fn get_config_dir_path(&self) -> Result<PathBuf, ConfigPathError> {
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()?;
fs::create_dir_all(path)?;

Ok(())
}

pub fn get_config_file_path(&self) -> Result<PathBuf, ConfigPathError> {
let mut path = self.get_config_dir_path()?;
path.push("config.json");

Ok(path)
}

pub fn save_config(&self, config: &FILE) -> Result<(), ConfigSaveError> {
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 load_config_from_file(&self) -> Result<FILE, FileConfigParseError> {
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 = 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)
}

pub fn load_config_from_env(&self) -> Result<ENV, EnvironmentConfigParseError> {
let prefix = self.app_name.to_uppercase();
let config = serde_env::from_env_with_prefix(&prefix)?;

Ok(config)
}

pub fn merge_configs(prioritized_config: &ENV, secondary_config: FILE) -> FILE {
secondary_config.merge(prioritized_config)
}

pub fn load_config(&self) -> Result<FILE, ConfigParseError> {
let env_config = self.load_config_from_env()?;
let file_config = self.load_config_from_file()?;
let merged_config = ConfigHandler::merge_configs(&env_config, file_config);

Ok(merged_config)
}
}
21 changes: 21 additions & 0 deletions src/config/environment_config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
use std::fmt::{self, Display, Formatter};

use serde::{Deserialize, Serialize};

#[derive(Debug, Default, PartialEq, PartialOrd, Serialize, Deserialize, Clone)]
pub struct EnvironmentConfig {
pub discord_token: Option<String>,
}

impl Display for EnvironmentConfig {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let content = match serde_json::to_string(self) {
Ok(content) => content,
Err(error) => {
return write!(f, "Unable to serialize config: {}", error);
}
};

write!(f, "{}", content)
}
}
Loading
Loading