use std::collections::BTreeMap;
use std::path::PathBuf;
use crate::config::errors::ConfigError;
use crate::config::partial::{OutMode, PartialConfig};
use crate::store::Secret;
#[derive(Clone, Debug, Default, PartialEq)]
pub struct EnvSnapshot(pub BTreeMap<String, String>);
impl EnvSnapshot {
pub fn get(&self, key: &str) -> Option<&str> {
self.0.get(key).map(String::as_str)
}
}
pub fn partial_from_env(env: &EnvSnapshot) -> Result<PartialConfig, ConfigError> {
let output = match env.get("BRAZEN_OUTPUT") {
Some(v) => Some(OutMode::parse(v).ok_or_else(|| ConfigError::BadValue {
key: "BRAZEN_OUTPUT".into(),
detail: format!("unknown output mode `{v}`"),
})?),
None => None,
};
Ok(PartialConfig {
provider: env.get("BRAZEN_PROVIDER").map(str::to_owned),
model: env.get("BRAZEN_MODEL").map(str::to_owned),
base_url: env.get("BRAZEN_BASE_URL").map(str::to_owned),
api_key: env.get("BRAZEN_API_KEY").map(Secret::new),
output,
thinking: parse_scalar("BRAZEN_THINKING", env)?,
max_tokens: parse_scalar("BRAZEN_MAX_TOKENS", env)?,
temperature: parse_scalar("BRAZEN_TEMPERATURE", env)?,
top_p: parse_scalar("BRAZEN_TOP_P", env)?,
reasoning: parse_scalar("BRAZEN_REASONING", env)?,
stream: parse_scalar("BRAZEN_STREAM", env)?,
timeout: parse_scalar("BRAZEN_TIMEOUT", env)?,
..Default::default()
})
}
fn parse_scalar<T: std::str::FromStr>(
key: &str,
env: &EnvSnapshot,
) -> Result<Option<T>, ConfigError> {
match env.get(key) {
Some(value) => value.parse().map(Some).map_err(|_| ConfigError::BadValue {
key: key.to_owned(),
detail: format!("could not parse `{value}`"),
}),
None => Ok(None),
}
}
pub fn config_path(explicit: Option<PathBuf>, env: &EnvSnapshot) -> PathBuf {
explicit
.or_else(|| env.get("BRAZEN_CONFIG").map(PathBuf::from))
.unwrap_or_else(|| xdg_config_home(env).join("brazen/config.toml"))
}
fn xdg_config_home(env: &EnvSnapshot) -> PathBuf {
if let Some(xdg) = env.get("XDG_CONFIG_HOME") {
return PathBuf::from(xdg);
}
match env.get("HOME") {
Some(home) => PathBuf::from(home).join(".config"),
None => PathBuf::from(".config"),
}
}