use config::{Config, ConfigError, File};
use serde::Deserialize;
use std::path::PathBuf;
#[derive(Debug, Deserialize, PartialEq, Clone)]
pub struct LoggingConfig {
pub log_level: String,
}
impl Default for LoggingConfig {
fn default() -> Self {
Self {
log_level: "INFO".to_string(),
}
}
}
#[derive(Debug, Deserialize, PartialEq, Clone)]
pub struct CacheConfig {
pub capacity: u64,
pub tti_seconds: u64,
}
impl Default for CacheConfig {
fn default() -> Self {
Self {
capacity: 30,
tti_seconds: 900,
}
}
}
#[derive(Debug, Deserialize, PartialEq, Clone)]
pub struct AuthConfig {
pub client_id: Option<String>,
pub client_secret: Option<String>,
pub tenant_id: Option<String>,
}
impl Default for AuthConfig {
fn default() -> Self {
Self {
client_id: None,
client_secret: None,
tenant_id: None,
}
}
}
#[derive(Debug, Deserialize, PartialEq, Clone)]
pub struct AppConfig {
pub logging: LoggingConfig,
pub cache: CacheConfig,
}
impl Default for AppConfig {
fn default() -> Self {
Self {
logging: LoggingConfig::default(),
cache: CacheConfig::default(),
}
}
}
pub struct AppConfigBuilder {
logging: LoggingConfig,
cache: CacheConfig,
}
impl AppConfigBuilder {
pub fn new() -> Self {
Self {
logging: LoggingConfig::default(),
cache: CacheConfig::default(),
}
}
pub fn from(config: AppConfig) -> Self {
Self {
logging: config.logging,
cache: config.cache,
}
}
pub fn with_log_level(mut self, log_level: String) -> Self {
self.logging.log_level = log_level;
self
}
pub fn with_cache_capacity(mut self, cache_capacity: u64) -> Self {
self.cache.capacity = cache_capacity;
self
}
pub fn with_cache_tti_seconds(mut self, tti_seconds: u64) -> Self {
self.cache.tti_seconds = tti_seconds;
self
}
pub fn build(self) -> AppConfig {
AppConfig {
logging: self.logging,
cache: self.cache,
}
}
}
pub fn load_config(
app_config: Option<AppConfig>,
config_path: Option<PathBuf>,
) -> Result<AppConfig, ConfigError> {
let mut settings_config_builder = Config::builder();
if let Some(config_path) = config_path {
settings_config_builder = settings_config_builder.add_source(File::from(config_path));
}
let settings = settings_config_builder.build()?;
let mut app_config_builder = if let Some(app_config) = app_config {
AppConfigBuilder::from(app_config)
} else {
AppConfigBuilder::new()
};
if let Some(log_level) = settings.get::<String>("logging.log_level").ok() {
app_config_builder = app_config_builder.with_log_level(log_level);
}
if let Some(cache_capacity) = settings.get::<u64>("cache.capacity").ok() {
app_config_builder = app_config_builder.with_cache_capacity(cache_capacity);
}
if let Some(cache_tti_seconds) = settings.get::<u64>("cache.tti_seconds").ok() {
app_config_builder = app_config_builder.with_cache_tti_seconds(cache_tti_seconds);
}
Ok(app_config_builder.build())
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
#[test]
fn test_load_config_from_toml() {
let config_toml = r#"
[logging]
log_level = "DEBUG"
[cache]
tti_seconds = 1000
"#;
let config_path = PathBuf::from("test_config.toml");
fs::write(&config_path, config_toml).unwrap();
let result = load_config(None, Some(config_path.clone()));
let expected_config = AppConfigBuilder::new()
.with_log_level("DEBUG".to_string())
.with_cache_capacity(30)
.with_cache_tti_seconds(1000)
.build();
assert_eq!(result.unwrap(), expected_config);
fs::remove_file(config_path).unwrap();
}
#[test]
fn test_load_config_with_custom_cache() {
let app_config = AppConfigBuilder::new()
.with_cache_capacity(50)
.with_cache_tti_seconds(1200)
.build();
let result = load_config(Some(app_config.clone()), None);
assert_eq!(result.unwrap(), app_config);
}
#[test]
fn test_load_config_with_custom_auth() {
let app_config = AppConfigBuilder::new()
.build();
let result = load_config(Some(app_config.clone()), None);
assert_eq!(result.unwrap(), app_config);
}
}