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;
15use tracing::{debug, info, instrument, warn};
16
17use crate::Services::Service;
18
19/// Configuration scope
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
21pub enum ConfigurationScope {
22	/// Global configuration
23	Global,
24	/// Workspace configuration
25	Workspace,
26	/// Extension-specific configuration
27	Extension,
28}
29
30/// Configuration value
31#[derive(Debug, Clone)]
32pub struct ConfigurationValue {
33	/// Value
34	pub value:Value,
35	/// Scope
36	pub scope:ConfigurationScope,
37	/// Timestamp of last modification
38	pub modified_at:u64,
39}
40
41/// Configuration service
42pub struct ConfigurationServiceImpl {
43	/// Service name
44	name:String,
45	/// Configuration data
46	config:Arc<RwLock<HashMap<String, ConfigurationValue>>>,
47	/// Configuration paths
48	config_paths:Arc<RwLock<HashMap<ConfigurationScope, PathBuf>>>,
49	/// Running flag
50	running:Arc<RwLock<bool>>,
51	/// Watchers
52	watchers:Arc<RwLock<HashMap<String, Vec<ConfigurationWatcherCallback>>>>,
53}
54
55/// Configuration watcher callback
56type ConfigurationWatcherCallback = Arc<RwLock<dyn Fn(String, Value) -> Result<()> + Send + Sync>>;
57
58impl ConfigurationServiceImpl {
59	/// Create a new configuration service
60	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	/// Get a configuration value
77	#[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	/// Get a configuration value with a default
84	pub async fn get_with_default(&self, key:&str, default:Value) -> Value { self.get(key).await.unwrap_or(default) }
85
86	/// Set a configuration value
87	#[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		// Notify watchers
101		self.notify_watchers(key, value).await;
102
103		Ok(())
104	}
105
106	/// Remove a configuration value
107	#[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	/// Get all configuration values
116	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	/// Get all configuration values in a scope
126	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	/// Load configuration from a file
137	#[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		// Store path for future reference
150		self.config_paths.write().await.insert(scope, path.to_path_buf());
151
152		info!("Configuration loaded successfully");
153
154		Ok(())
155	}
156
157	/// Load configuration from a value
158	#[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	/// Save configuration to a file
176	#[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	/// Register a configuration watcher
195	#[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	/// Unregister a configuration watcher
209	#[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	/// Notify watchers of configuration changes
217	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	/// Get configuration paths
230	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		// Test setting and getting
269		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}