1use std::{
7 collections::HashMap,
8 path::{Path, PathBuf},
9 sync::Arc,
10};
11
12use anyhow::{Context, Result};
13use serde_json::Value;
14use tokio::sync::RwLock;
15
16use crate::{Services::Service, dev_log};
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
20pub enum ConfigurationScope {
21 Global,
23
24 Workspace,
26
27 Extension,
29}
30
31#[derive(Debug, Clone)]
33pub struct ConfigurationValue {
34 pub value:Value,
36
37 pub scope:ConfigurationScope,
39
40 pub modified_at:u64,
42}
43
44pub struct ConfigurationServiceImpl {
46 name:String,
48
49 config:Arc<RwLock<HashMap<String, ConfigurationValue>>>,
51
52 config_paths:Arc<RwLock<HashMap<ConfigurationScope, PathBuf>>>,
54
55 running:Arc<RwLock<bool>>,
57
58 watchers:Arc<RwLock<HashMap<String, Vec<ConfigurationWatcherCallback>>>>,
60}
61
62type ConfigurationWatcherCallback = Arc<RwLock<dyn Fn(String, Value) -> Result<()> + Send + Sync>>;
64
65impl ConfigurationServiceImpl {
66 pub fn new(config_path:Option<PathBuf>) -> Self {
68 let mut config_paths = HashMap::new();
69
70 if let Some(path) = config_path {
71 config_paths.insert(ConfigurationScope::Global, path);
72 }
73
74 Self {
75 name:"ConfigurationService".to_string(),
76
77 config:Arc::new(RwLock::new(HashMap::new())),
78
79 config_paths:Arc::new(RwLock::new(config_paths)),
80
81 running:Arc::new(RwLock::new(false)),
82
83 watchers:Arc::new(RwLock::new(HashMap::new())),
84 }
85 }
86
87 pub async fn get(&self, key:&str) -> Option<Value> {
89 dev_log!("config", "Getting configuration value: {}", key);
90
91 self.config.read().await.get(key).map(|v| v.value.clone())
92 }
93
94 pub async fn get_with_default(&self, key:&str, default:Value) -> Value { self.get(key).await.unwrap_or(default) }
96
97 pub async fn set(&self, key:String, value:Value, scope:ConfigurationScope) -> Result<()> {
99 dev_log!("config", "Setting configuration value: {} = {:?}", key, value);
100
101 let now = std::time::SystemTime::now()
102 .duration_since(std::time::UNIX_EPOCH)
103 .map(|d| d.as_secs())
104 .unwrap_or(0);
105
106 let config_value = ConfigurationValue { value:value.clone(), scope, modified_at:now };
107
108 self.config.write().await.insert(key.clone(), config_value);
109
110 self.notify_watchers(key, value).await;
112
113 Ok(())
114 }
115
116 pub async fn remove(&self, key:String) -> Result<bool> {
118 dev_log!("config", "Removing configuration value: {}", key);
119
120 let removed = self.config.write().await.remove(&key).is_some();
121
122 Ok(removed)
123 }
124
125 pub async fn get_all(&self) -> HashMap<String, Value> {
127 self.config
128 .read()
129 .await
130 .iter()
131 .map(|(k, v)| (k.clone(), v.value.clone()))
132 .collect()
133 }
134
135 pub async fn get_all_in_scope(&self, scope:ConfigurationScope) -> HashMap<String, Value> {
137 self.config
138 .read()
139 .await
140 .iter()
141 .filter(|(_, v)| v.scope == scope)
142 .map(|(k, v)| (k.clone(), v.value.clone()))
143 .collect()
144 }
145
146 pub async fn load_from_file(&self, path:&Path, scope:ConfigurationScope) -> Result<()> {
148 dev_log!("config", "Loading configuration from: {:?}", path);
149
150 let content = tokio::fs::read_to_string(path)
151 .await
152 .context("Failed to read configuration file")?;
153
154 let config:Value = serde_json::from_str(&content).context("Failed to parse configuration file")?;
155
156 self.load_from_value(config, scope).await?;
157
158 self.config_paths.write().await.insert(scope, path.to_path_buf());
160
161 dev_log!("config", "Configuration loaded successfully");
162
163 Ok(())
164 }
165
166 pub async fn load_from_value(&self, value:Value, scope:ConfigurationScope) -> Result<()> {
168 if let Value::Object(object) = value {
169 let mut config = self.config.write().await;
170
171 let now = std::time::SystemTime::now()
172 .duration_since(std::time::UNIX_EPOCH)
173 .map(|d| d.as_secs())
174 .unwrap_or(0);
175
176 for (key, val) in object {
177 config.insert(key, ConfigurationValue { value:val, scope, modified_at:now });
178 }
179 }
180
181 Ok(())
182 }
183
184 pub async fn save_to_file(&self, path:&Path, scope:ConfigurationScope) -> Result<()> {
186 dev_log!("config", "Saving configuration to: {:?}", path);
187
188 let config = self.get_all_in_scope(scope).await;
189
190 let config_value = Value::Object(config.into_iter().map(|(k, v)| (k, v)).collect());
191
192 let content = serde_json::to_string_pretty(&config_value).context("Failed to serialize configuration")?;
193
194 tokio::fs::write(path, content)
195 .await
196 .context("Failed to write configuration file")?;
197
198 dev_log!("config", "Configuration saved successfully");
199
200 Ok(())
201 }
202
203 pub async fn register_watcher<F>(&self, key:String, callback:F)
205 where
206 F: Fn(String, Value) -> Result<()> + Send + Sync + 'static, {
207 let key_clone = key.clone();
208
209 let mut watchers = self.watchers.write().await;
210
211 watchers
212 .entry(key)
213 .or_insert_with(Vec::new)
214 .push(Arc::new(RwLock::new(callback)));
215
216 dev_log!("config", "Registered configuration watcher for: {}", key_clone);
217 }
218
219 pub async fn unregister_watcher(&self, key:String) -> Result<bool> {
221 let mut watchers = self.watchers.write().await;
222
223 let removed = watchers.remove(&key).is_some();
224
225 Ok(removed)
226 }
227
228 async fn notify_watchers(&self, key:String, value:Value) {
230 let watchers = self.watchers.read().await;
231
232 if let Some(callbacks) = watchers.get(&key) {
233 for callback in callbacks {
234 if let Err(e) = callback.read().await(key.clone(), value.clone()) {
235 dev_log!("config", "warn: configuration watcher callback failed: {}", e);
236 }
237 }
238 }
239 }
240
241 pub async fn get_config_paths(&self) -> HashMap<ConfigurationScope, PathBuf> {
243 self.config_paths.read().await.clone()
244 }
245}
246
247impl Service for ConfigurationServiceImpl {
248 fn name(&self) -> &str { &self.name }
249
250 async fn start(&self) -> Result<()> {
251 dev_log!("config", "Starting configuration service");
252
253 *self.running.write().await = true;
254
255 dev_log!("config", "Configuration service started");
256
257 Ok(())
258 }
259
260 async fn stop(&self) -> Result<()> {
261 dev_log!("config", "Stopping configuration service");
262
263 *self.running.write().await = false;
264
265 dev_log!("config", "Configuration service stopped");
266
267 Ok(())
268 }
269
270 async fn is_running(&self) -> bool { *self.running.read().await }
271}
272
273#[cfg(test)]
274mod tests {
275
276 use super::*;
277
278 #[tokio::test]
279 async fn test_configuration_service_basic() {
280 let service = ConfigurationServiceImpl::new(None);
281
282 let _:anyhow::Result<()> = service.start().await;
283
284 let _:anyhow::Result<()> = service
286 .set(
287 "test.key".to_string(),
288 serde_json::json!("test-value"),
289 ConfigurationScope::Global,
290 )
291 .await;
292
293 let value = service.get("test.key").await;
294
295 assert_eq!(value, Some(serde_json::json!("test-value")));
296
297 let _:anyhow::Result<()> = service.stop().await;
298 }
299
300 #[tokio::test]
301 async fn test_get_with_default() {
302 let service = ConfigurationServiceImpl::new(None);
303
304 let default = serde_json::json!("default-value");
305
306 let value = service.get_with_default("nonexistent.key", default.clone()).await;
307
308 assert_eq!(value, default);
309 }
310
311 #[tokio::test]
312 async fn test_get_all_in_scope() {
313 let service = ConfigurationServiceImpl::new(None);
314
315 let _:anyhow::Result<()> = service
316 .set("key1".to_string(), serde_json::json!("value1"), ConfigurationScope::Global)
317 .await;
318
319 let _:anyhow::Result<()> = service
320 .set("key2".to_string(), serde_json::json!("value2"), ConfigurationScope::Workspace)
321 .await;
322
323 let global_values = service.get_all_in_scope(ConfigurationScope::Global).await;
324
325 assert_eq!(global_values.len(), 1);
326
327 assert_eq!(global_values.get("key1"), Some(&serde_json::json!("value1")));
328 }
329
330 #[test]
331 fn test_configuration_scope() {
332 let global = ConfigurationScope::Global;
333
334 let workspace = ConfigurationScope::Workspace;
335
336 let extension = ConfigurationScope::Extension;
337
338 assert_eq!(global, ConfigurationScope::Global);
339
340 assert_ne!(global, workspace);
341
342 assert_ne!(global, extension);
343 }
344}