use std::collections::HashMap;
use std::env;
use std::fs;
use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
use serde::Deserialize;
#[derive(Debug, Deserialize)]
pub struct Settings {
#[serde(default)]
pub env: HashMap<String, String>,
}
impl Settings {
pub fn load() -> Result<Self> {
let settings_path = Self::get_settings_path()?;
Self::load_from_path(&settings_path)
}
pub fn load_from_path<P: AsRef<Path>>(path: P) -> Result<Self> {
let path = path.as_ref();
if !path.exists() {
return Ok(Self {
env: HashMap::new(),
});
}
let content = fs::read_to_string(path)
.with_context(|| format!("Failed to read settings file: {}", path.display()))?;
serde_json::from_str::<Self>(&content)
.with_context(|| format!("Failed to parse settings file: {}", path.display()))
}
pub fn get_settings_path() -> Result<PathBuf> {
let home_dir = dirs::home_dir().context("Failed to determine home directory")?;
Ok(home_dir.join(".omni-dev").join("settings.json"))
}
pub fn get_env_var(&self, key: &str) -> Option<String> {
match env::var(key) {
Ok(value) => Some(value),
Err(_) => {
self.env.get(key).cloned()
}
}
}
}
pub fn get_env_var(key: &str) -> Result<String> {
match env::var(key) {
Ok(value) => Ok(value),
Err(_) => {
match Settings::load() {
Ok(settings) => settings
.env
.get(key)
.cloned()
.ok_or_else(|| anyhow::anyhow!("Environment variable not found: {key}")),
Err(err) => {
Err(anyhow::anyhow!("Environment variable not found: {key}").context(err))
}
}
}
}
}
pub fn get_env_vars(keys: &[&str]) -> Result<String> {
for key in keys {
if let Ok(value) = get_env_var(key) {
return Ok(value);
}
}
Err(anyhow::anyhow!(
"None of the environment variables found: {keys:?}"
))
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
use std::fs;
use tempfile::TempDir;
#[test]
fn settings_load_from_path() {
let temp_dir = {
std::fs::create_dir_all("tmp").ok();
TempDir::new_in("tmp").unwrap()
};
let settings_path = temp_dir.path().join("settings.json");
let settings_json = r#"{
"env": {
"TEST_VAR": "test_value",
"CLAUDE_API_KEY": "test_api_key"
}
}"#;
fs::write(&settings_path, settings_json).unwrap();
let settings = Settings::load_from_path(&settings_path).unwrap();
assert_eq!(settings.env.get("TEST_VAR").unwrap(), "test_value");
assert_eq!(settings.env.get("CLAUDE_API_KEY").unwrap(), "test_api_key");
}
#[test]
fn settings_get_env_var() {
let temp_dir = {
std::fs::create_dir_all("tmp").ok();
TempDir::new_in("tmp").unwrap()
};
let settings_path = temp_dir.path().join("settings.json");
let settings_json = r#"{
"env": {
"TEST_VAR": "test_value",
"CLAUDE_API_KEY": "test_api_key"
}
}"#;
fs::write(&settings_path, settings_json).unwrap();
let settings = Settings::load_from_path(&settings_path).unwrap();
env::set_var("TEST_VAR_ENV", "env_value");
env::set_var("TEST_VAR", "env_override");
assert_eq!(settings.get_env_var("TEST_VAR").unwrap(), "env_override");
env::remove_var("TEST_VAR"); assert_eq!(settings.get_env_var("TEST_VAR").unwrap(), "test_value");
assert_eq!(settings.get_env_var("TEST_VAR_ENV").unwrap(), "env_value");
env::remove_var("TEST_VAR_ENV");
}
}