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