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