omni_dev/utils/
settings.rs1use std::collections::HashMap;
7use std::env;
8use std::fs;
9use std::path::{Path, PathBuf};
10
11use anyhow::{Context, Result};
12use serde::Deserialize;
13
14#[derive(Debug, Default, Deserialize)]
16pub struct Settings {
17 #[serde(default)]
19 pub env: HashMap<String, String>,
20}
21
22#[derive(Debug, Default)]
30pub struct SettingsEnv {
31 settings: Settings,
32}
33
34impl SettingsEnv {
35 pub fn load() -> Self {
38 Self {
39 settings: Settings::load().unwrap_or_default(),
40 }
41 }
42}
43
44impl crate::utils::env::EnvSource for SettingsEnv {
45 fn var(&self, key: &str) -> Option<String> {
46 env::var(key)
47 .ok()
48 .or_else(|| self.settings.env.get(key).cloned())
49 }
50}
51
52impl Settings {
53 pub fn load() -> Result<Self> {
55 let settings_path = Self::get_settings_path()?;
56 Self::load_from_path(&settings_path)
57 }
58
59 pub fn load_from_path<P: AsRef<Path>>(path: P) -> Result<Self> {
61 let path = path.as_ref();
62
63 if !path.exists() {
65 return Ok(Self {
66 env: HashMap::new(),
67 });
68 }
69
70 let content = fs::read_to_string(path)
72 .with_context(|| format!("Failed to read settings file: {}", path.display()))?;
73
74 serde_json::from_str::<Self>(&content)
75 .with_context(|| format!("Failed to parse settings file: {}", path.display()))
76 }
77
78 pub fn get_settings_path() -> Result<PathBuf> {
80 let home_dir = dirs::home_dir().context("Failed to determine home directory")?;
81
82 Ok(home_dir.join(".omni-dev").join("settings.json"))
83 }
84
85 pub fn get_env_var(&self, key: &str) -> Option<String> {
87 match env::var(key) {
89 Ok(value) => Some(value),
90 Err(_) => {
91 self.env.get(key).cloned()
93 }
94 }
95 }
96}
97
98pub fn get_env_var(key: &str) -> Result<String> {
100 match env::var(key) {
102 Ok(value) => Ok(value),
103 Err(_) => {
104 match Settings::load() {
106 Ok(settings) => settings
107 .env
108 .get(key)
109 .cloned()
110 .ok_or_else(|| anyhow::anyhow!("Environment variable not found: {key}")),
111 Err(err) => {
112 Err(anyhow::anyhow!("Environment variable not found: {key}").context(err))
114 }
115 }
116 }
117 }
118}
119
120pub fn get_env_vars(keys: &[&str]) -> Result<String> {
122 for key in keys {
123 if let Ok(value) = get_env_var(key) {
124 return Ok(value);
125 }
126 }
127
128 Err(anyhow::anyhow!(
129 "None of the environment variables found: {keys:?}"
130 ))
131}
132
133#[cfg(test)]
134#[allow(clippy::unwrap_used, clippy::expect_used)]
135mod tests {
136 use super::*;
137 use std::fs;
138 use tempfile::TempDir;
139
140 #[test]
141 fn settings_load_from_path() {
142 let temp_dir = {
144 std::fs::create_dir_all("tmp").ok();
145 TempDir::new_in("tmp").unwrap()
146 };
147 let settings_path = temp_dir.path().join("settings.json");
148
149 let settings_json = r#"{
151 "env": {
152 "TEST_VAR": "test_value",
153 "CLAUDE_API_KEY": "test_api_key"
154 }
155 }"#;
156 fs::write(&settings_path, settings_json).unwrap();
157
158 let settings = Settings::load_from_path(&settings_path).unwrap();
160
161 assert_eq!(settings.env.get("TEST_VAR").unwrap(), "test_value");
163 assert_eq!(settings.env.get("CLAUDE_API_KEY").unwrap(), "test_api_key");
164 }
165
166 #[test]
167 fn settings_get_env_var() {
168 let temp_dir = {
170 std::fs::create_dir_all("tmp").ok();
171 TempDir::new_in("tmp").unwrap()
172 };
173 let settings_path = temp_dir.path().join("settings.json");
174
175 let settings_json = r#"{
177 "env": {
178 "TEST_VAR": "test_value",
179 "CLAUDE_API_KEY": "test_api_key"
180 }
181 }"#;
182 fs::write(&settings_path, settings_json).unwrap();
183
184 let settings = Settings::load_from_path(&settings_path).unwrap();
186
187 env::set_var("TEST_VAR_ENV", "env_value");
189
190 env::set_var("TEST_VAR", "env_override");
192 assert_eq!(settings.get_env_var("TEST_VAR").unwrap(), "env_override");
193
194 env::remove_var("TEST_VAR"); assert_eq!(settings.get_env_var("TEST_VAR").unwrap(), "test_value");
197
198 assert_eq!(settings.get_env_var("TEST_VAR_ENV").unwrap(), "env_value");
200
201 env::remove_var("TEST_VAR_ENV");
203 }
204}