Skip to content

Commit 1655827

Browse files
authored
Merge refactor/config into main (#81)
* refactor: replace envy with serde-env * refactor: config system * refactor: re-arrange methods
1 parent 02c573b commit 1655827

File tree

7 files changed

+258
-129
lines changed

7 files changed

+258
-129
lines changed

Cargo.lock

Lines changed: 17 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ fern = { version = "0.6.2", features = ["chrono", "colored", "date-based"] }
2828
humantime = "2.1.0"
2929
log = { version = "0.4.20", features = ["serde"] }
3030
serde = { version = "1.0.193", features = ["derive"] }
31+
serde-env = "0.2.0"
3132
serde_json = "1.0.108"
3233
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"] }
3334
sqlx = { version = "0.8.0", features = ["runtime-tokio", "any", "postgres", "mysql", "sqlite", "tls-native-tls", "migrate", "macros", "uuid", "chrono", "json"] }

src/config.rs

Lines changed: 9 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -1,129 +1,11 @@
1-
use core::fmt;
2-
use serde::{Deserialize, Serialize};
3-
use std::{
4-
fmt::{Display, Formatter},
5-
fs, io,
6-
path::PathBuf,
7-
};
8-
use thiserror::Error;
9-
10-
#[derive(Debug, Error)]
11-
pub enum ConfigPathError {
12-
#[error("Unable to get OS config directory")]
13-
UnknownBasePath,
14-
}
15-
16-
#[derive(Debug, Error)]
17-
pub enum ConfigInitError {
18-
#[error("Unable to get config path: {0}")]
19-
Path(#[from] ConfigPathError),
20-
#[error("I/O error: {0}")]
21-
IO(#[from] io::Error),
22-
}
23-
24-
#[derive(Debug, Error)]
25-
pub enum ConfigParseError {
26-
#[error("Unable to get config path: {0}")]
27-
Path(#[from] ConfigPathError),
28-
#[error("Unable to initialize config: {0}")]
29-
Init(#[from] ConfigInitError),
30-
#[error("Unable to serialize or deserialize config: {0}")]
31-
Serde(#[from] serde_json::Error),
32-
#[error("I/O error: {0}")]
33-
IO(#[from] io::Error),
34-
}
35-
36-
fn discord_token_default() -> String {
37-
String::from("Please provide a token")
38-
}
39-
40-
#[derive(Debug, PartialEq, PartialOrd, Serialize, Deserialize, Clone)]
41-
pub struct Config {
42-
#[serde(rename = "discordToken", default = "discord_token_default")]
43-
pub discord_token: String,
44-
}
45-
46-
impl Default for Config {
47-
fn default() -> Self {
48-
Config {
49-
discord_token: discord_token_default(),
50-
}
51-
}
52-
}
53-
54-
impl Display for Config {
55-
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
56-
let content = match serde_json::to_string(self) {
57-
Ok(content) => content,
58-
Err(error) => {
59-
return write!(f, "Unable to serialize config: {}", error);
60-
}
61-
};
62-
63-
write!(f, "{}", content)
64-
}
65-
}
66-
67-
#[derive(Debug)]
68-
pub struct ConfigHandler {
69-
pub app_name: String,
70-
}
71-
72-
impl ConfigHandler {
73-
pub fn new(app_name: &str) -> Self {
74-
ConfigHandler {
75-
app_name: app_name.to_string(),
76-
}
77-
}
78-
79-
pub fn get_config_dir_path(&self) -> Result<PathBuf, ConfigPathError> {
80-
let mut path = match dirs::config_dir() {
81-
Some(path) => path,
82-
None => return Err(ConfigPathError::UnknownBasePath),
83-
};
1+
pub mod config_handler;
2+
pub mod environment_config;
3+
pub mod file_config;
844

85-
path.push(&self.app_name);
86-
Ok(path)
87-
}
88-
89-
pub fn create_config_dir_path(&self) -> Result<(), ConfigInitError> {
90-
let path = self.get_config_dir_path()?;
91-
fs::create_dir_all(path)?;
92-
Ok(())
93-
}
94-
95-
pub fn get_config_file_path(&self) -> Result<PathBuf, ConfigPathError> {
96-
let mut path = self.get_config_dir_path()?;
97-
path.push("config.json");
98-
Ok(path)
99-
}
100-
101-
pub fn save_config(&self, config: &Config) -> Result<(), ConfigParseError> {
102-
let path = self.get_config_file_path()?;
103-
104-
if !path.exists() {
105-
self.create_config_dir_path()?;
106-
}
107-
108-
let config_json = serde_json::to_string_pretty(config)?;
109-
110-
fs::write(path, config_json)?;
111-
112-
Ok(())
113-
}
114-
115-
pub fn load_config(&self) -> Result<Config, ConfigParseError> {
116-
let path = self.get_config_file_path()?;
117-
if !path.exists() {
118-
self.create_config_dir_path()?;
119-
fs::write(&path, "{}")?;
120-
}
121-
122-
let config_json = fs::read_to_string(path)?;
123-
let config: Config = serde_json::from_str(&config_json)?;
124-
125-
self.save_config(&config)?; // In case the config file was missing some fields which serde used the defaults for
5+
pub use config_handler::{
6+
ConfigHandler, ConfigInitError, ConfigParseError, ConfigPathError, ConfigSaveError,
7+
EnvironmentConfigParseError, FileConfigParseError, Merge,
8+
};
1269

127-
Ok(config)
128-
}
129-
}
10+
pub use environment_config::EnvironmentConfig;
11+
pub use file_config::FileConfig;

src/config/config_handler.rs

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
use std::{fs, io, marker::PhantomData, path::PathBuf};
2+
3+
use serde::{Deserialize, Serialize};
4+
use thiserror::Error;
5+
6+
pub trait Merge<T> {
7+
fn merge(&self, other: &T) -> Self;
8+
}
9+
10+
#[derive(Debug, Error)]
11+
pub enum ConfigPathError {
12+
#[error("Unable to get OS-specific config directory")]
13+
UnknownBasePath,
14+
}
15+
16+
#[derive(Debug, Error)]
17+
pub enum ConfigInitError {
18+
#[error("Unable to get config path: {0}")]
19+
Path(#[from] ConfigPathError),
20+
21+
#[error("I/O error: {0}")]
22+
IO(#[from] io::Error),
23+
}
24+
25+
#[derive(Debug, Error)]
26+
pub enum ConfigSaveError {
27+
#[error("Unable to get config path: {0}")]
28+
Path(#[from] ConfigPathError),
29+
30+
#[error("Unable to init config: {0}")]
31+
Init(#[from] ConfigInitError),
32+
33+
#[error("Unable to serialize config: {0}")]
34+
Serde(#[from] serde_json::Error),
35+
36+
#[error("I/O error: {0}")]
37+
IO(#[from] io::Error),
38+
}
39+
40+
#[derive(Debug, Error)]
41+
pub enum FileConfigParseError {
42+
#[error("Unable to get config path: {0}")]
43+
Path(#[from] ConfigPathError),
44+
45+
#[error("Unable to initialize config: {0}")]
46+
Init(#[from] ConfigInitError),
47+
48+
#[error("Unable to save config: {0}")]
49+
Save(#[from] ConfigSaveError),
50+
51+
#[error("I/O error: {0}")]
52+
IO(#[from] io::Error),
53+
54+
#[error("Unable to serialize or deserialize config: {0}")]
55+
Serde(#[from] serde_json::Error),
56+
}
57+
58+
#[derive(Debug, Error)]
59+
pub enum EnvironmentConfigParseError {
60+
#[error("Unable to parse environment variables: {0}")]
61+
Envy(#[from] serde_env::Error),
62+
}
63+
64+
#[derive(Debug, Error)]
65+
pub enum ConfigParseError {
66+
#[error("Unable to parse config from file: {0}")]
67+
File(#[from] FileConfigParseError),
68+
69+
#[error("Unable to parse config from environment: {0}")]
70+
Env(#[from] EnvironmentConfigParseError),
71+
}
72+
73+
#[derive(Debug)]
74+
pub struct ConfigHandler<FILE, ENV>
75+
where
76+
FILE: Serialize + for<'de> Deserialize<'de> + Merge<ENV>,
77+
ENV: Serialize + for<'de> Deserialize<'de>,
78+
{
79+
pub app_name: String,
80+
_phantom_file: PhantomData<FILE>,
81+
_phantom_env: PhantomData<ENV>,
82+
}
83+
84+
impl<FILE, ENV> ConfigHandler<FILE, ENV>
85+
where
86+
FILE: Serialize + for<'de> Deserialize<'de> + Merge<ENV>,
87+
ENV: Serialize + for<'de> Deserialize<'de>,
88+
{
89+
pub fn new(app_name: &str) -> Self {
90+
ConfigHandler {
91+
app_name: app_name.to_string(),
92+
_phantom_file: PhantomData,
93+
_phantom_env: PhantomData,
94+
}
95+
}
96+
97+
pub fn get_config_dir_path(&self) -> Result<PathBuf, ConfigPathError> {
98+
let mut path = match dirs::config_dir() {
99+
Some(path) => path,
100+
None => return Err(ConfigPathError::UnknownBasePath),
101+
};
102+
path.push(&self.app_name);
103+
104+
Ok(path)
105+
}
106+
107+
pub fn create_config_dir_path(&self) -> Result<(), ConfigInitError> {
108+
let path = self.get_config_dir_path()?;
109+
fs::create_dir_all(path)?;
110+
111+
Ok(())
112+
}
113+
114+
pub fn get_config_file_path(&self) -> Result<PathBuf, ConfigPathError> {
115+
let mut path = self.get_config_dir_path()?;
116+
path.push("config.json");
117+
118+
Ok(path)
119+
}
120+
121+
pub fn save_config(&self, config: &FILE) -> Result<(), ConfigSaveError> {
122+
let path = self.get_config_file_path()?;
123+
if !path.exists() {
124+
self.create_config_dir_path()?;
125+
}
126+
127+
let config_json = serde_json::to_string_pretty(config)?;
128+
fs::write(path, config_json)?;
129+
130+
Ok(())
131+
}
132+
133+
pub fn load_config_from_file(&self) -> Result<FILE, FileConfigParseError> {
134+
let path = self.get_config_file_path()?;
135+
if !path.exists() {
136+
self.create_config_dir_path()?;
137+
fs::write(&path, "{}")?;
138+
}
139+
140+
let config_json = fs::read_to_string(path)?;
141+
let config = serde_json::from_str(&config_json)?;
142+
self.save_config(&config)?; // In case the config file was missing some fields which serde used the defaults for
143+
144+
Ok(config)
145+
}
146+
147+
pub fn load_config_from_env(&self) -> Result<ENV, EnvironmentConfigParseError> {
148+
let prefix = self.app_name.to_uppercase();
149+
let config = serde_env::from_env_with_prefix(&prefix)?;
150+
151+
Ok(config)
152+
}
153+
154+
pub fn merge_configs(prioritized_config: &ENV, secondary_config: FILE) -> FILE {
155+
secondary_config.merge(prioritized_config)
156+
}
157+
158+
pub fn load_config(&self) -> Result<FILE, ConfigParseError> {
159+
let env_config = self.load_config_from_env()?;
160+
let file_config = self.load_config_from_file()?;
161+
let merged_config = ConfigHandler::merge_configs(&env_config, file_config);
162+
163+
Ok(merged_config)
164+
}
165+
}

src/config/environment_config.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
use std::fmt::{self, Display, Formatter};
2+
3+
use serde::{Deserialize, Serialize};
4+
5+
#[derive(Debug, Default, PartialEq, PartialOrd, Serialize, Deserialize, Clone)]
6+
pub struct EnvironmentConfig {
7+
pub discord_token: Option<String>,
8+
}
9+
10+
impl Display for EnvironmentConfig {
11+
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
12+
let content = match serde_json::to_string(self) {
13+
Ok(content) => content,
14+
Err(error) => {
15+
return write!(f, "Unable to serialize config: {}", error);
16+
}
17+
};
18+
19+
write!(f, "{}", content)
20+
}
21+
}

0 commit comments

Comments
 (0)