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