Skip to main content

Grove/Services/
ConfigurationService.rs

1//! Configuration Service Module
2//!
3//! Provides configuration management for Grove.
4//! Handles reading, writing, and watching configuration changes.
5
6use 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/// Configuration scope
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
20pub enum ConfigurationScope {
21	/// Global configuration
22	Global,
23
24	/// Workspace configuration
25	Workspace,
26
27	/// Extension-specific configuration
28	Extension,
29}
30
31/// Configuration value
32#[derive(Debug, Clone)]
33pub struct ConfigurationValue {
34	/// Value
35	pub value:Value,
36
37	/// Scope
38	pub scope:ConfigurationScope,
39
40	/// Timestamp of last modification
41	pub modified_at:u64,
42}
43
44/// Configuration service
45pub struct ConfigurationServiceImpl {
46	/// Service name
47	name:String,
48
49	/// Configuration data
50	config:Arc<RwLock<HashMap<String, ConfigurationValue>>>,
51
52	/// Configuration paths
53	config_paths:Arc<RwLock<HashMap<ConfigurationScope, PathBuf>>>,
54
55	/// Running flag
56	running:Arc<RwLock<bool>>,
57
58	/// Watchers
59	watchers:Arc<RwLock<HashMap<String, Vec<ConfigurationWatcherCallback>>>>,
60}
61
62/// Configuration watcher callback
63type ConfigurationWatcherCallback = Arc<RwLock<dyn Fn(String, Value) -> Result<()> + Send + Sync>>;
64
65impl ConfigurationServiceImpl {
66	/// Create a new configuration service
67	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	/// Get a configuration value
88	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	/// Get a configuration value with a default
95	pub async fn get_with_default(&self, key:&str, default:Value) -> Value { self.get(key).await.unwrap_or(default) }
96
97	/// Set a configuration value
98	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		// Notify watchers
111		self.notify_watchers(key, value).await;
112
113		Ok(())
114	}
115
116	/// Remove a configuration value
117	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	/// Get all configuration values
126	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	/// Get all configuration values in a scope
136	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	/// Load configuration from a file
147	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		// Store path for future reference
159		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	/// Load configuration from a value
167	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	/// Save configuration to a file
185	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	/// Register a configuration watcher
204	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	/// Unregister a configuration watcher
220	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	/// Notify watchers of configuration changes
229	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	/// Get configuration paths
242	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		// Test setting and getting
285		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}