#![allow(clippy::expect_used)] use config::builder::DefaultState;
use config::{Config, ConfigBuilder, Environment};
use serde::{Deserialize, Serialize};
use std::path::Path;
use std::sync::LazyLock;
use std::sync::RwLock;
use super::error::Result;
use crate::utils::types::LogLevel;
static BUILDER: LazyLock<RwLock<ConfigBuilder<DefaultState>>> =
LazyLock::new(|| RwLock::new(Config::builder()));
#[derive(Debug, Serialize, Deserialize)]
pub struct Database {
pub url: String,
pub variable: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct AppConfig {
pub debug: bool,
pub log_level: LogLevel,
pub database: Database,
}
impl AppConfig {
pub fn init(default_config: Option<&str>) -> Result<()> {
let mut builder = Config::builder();
if let Some(config_contents) = default_config {
builder = builder.add_source(config::File::from_str(
config_contents,
config::FileFormat::Toml,
));
}
builder = builder.add_source(Environment::with_prefix("APP"));
{
let mut w = BUILDER.write()?;
*w = builder;
}
Ok(())
}
pub fn merge_config(config_file: Option<&Path>) -> Result<()> {
if let Some(config_file_path) = config_file {
{
let mut w = BUILDER.write().expect("RwLock should not be poisoned");
*w = w.clone().add_source(config::File::with_name(
config_file_path.to_str().unwrap_or(""),
));
}
}
Ok(())
}
pub fn set(key: &str, value: &str) -> Result<()> {
{
let mut w = BUILDER.write().expect("RwLock should not be poisoned");
*w = w.clone().set_override(key, value)?;
}
Ok(())
}
pub fn get<'de, T>(key: &'de str) -> Result<T>
where
T: serde::Deserialize<'de>,
{
Ok(BUILDER.read()?.clone().build()?.get::<T>(key)?)
}
pub fn fetch() -> Result<Self> {
let config_clone = BUILDER.read()?.clone().build()?;
let app_config: Self = config_clone.try_into()?;
Ok(app_config)
}
}
impl TryFrom<Config> for AppConfig {
type Error = crate::utils::error::Error;
fn try_from(config: Config) -> Result<Self> {
Ok(Self {
debug: config.get_bool("debug")?,
log_level: config.get::<LogLevel>("log_level")?,
database: config.get::<Database>("database")?,
})
}
}