use crate::core::config::GrrConfig;
use crate::core::error::{GrrError, Result};
use figment::{
Figment, Provider,
providers::Format,
providers::{Env, Toml},
};
use std::path::PathBuf;
use tracing::info;
pub struct ConfigLoader;
impl ConfigLoader {
pub async fn load() -> Result<GrrConfig> {
let config_path = Self::config_path()?;
info!("Loading config from: {:?}", config_path);
let config = tokio::task::spawn_blocking(move || {
Figment::new()
.merge(Toml::file(&config_path))
.merge(Self::env_provider())
.extract::<GrrConfig>()
.map_err(|e| e.to_string())
})
.await
.map_err(|e| GrrError::Config(e.to_string()))?
.map_err(|e| GrrError::Config(e.to_string()))?;
info!("Loaded config: client_id={}", config.oauth.client_id);
match config.oauth.client_secret.as_deref() {
Some(s) if !s.is_empty() => info!("client_secret: configured"),
_ => info!("client_secret: absent (PKCE-only)"),
}
Ok(config)
}
fn env_provider() -> impl Provider {
Env::raw().filter_map(|key| {
let key = key.as_str();
if let Some(stripped) = key.strip_prefix("GRR_") {
let key = stripped.replace("__", ".");
let key = key.replace('_', "-");
let key = key.to_ascii_lowercase();
Some(key.into())
} else {
None
}
})
}
fn config_path() -> Result<PathBuf> {
if let Ok(path) = std::env::var("GRR_CONFIG_PATH") {
return Ok(PathBuf::from(path));
}
let home = dirs::home_dir()
.ok_or_else(|| GrrError::Config("Could not find home directory".into()))?;
Ok(home.join(".grr").join("config.toml"))
}
}