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 crate::dev_log;
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	pub async fn get(&self, key:&str) -> Option<Value> {
78		dev_log!("config", "Getting configuration value: {}", key);
79		self.config.read().await.get(key).map(|v| v.value.clone())
80	}
81
82	/// Get a configuration value with a default
83	pub async fn get_with_default(&self, key:&str, default:Value) -> Value { self.get(key).await.unwrap_or(default) }
84
85	/// Set a configuration value
86	pub async fn set(&self, key:String, value:Value, scope:ConfigurationScope) -> Result<()> {
87		dev_log!("config", "Setting configuration value: {} = {:?}", key, value);
88
89		let now = std::time::SystemTime::now()
90			.duration_since(std::time::UNIX_EPOCH)
91			.map(|d| d.as_secs())
92			.unwrap_or(0);
93
94		let config_value = ConfigurationValue { value:value.clone(), scope, modified_at:now };
95
96		self.config.write().await.insert(key.clone(), config_value);
97
98		// Notify watchers
99		self.notify_watchers(key, value).await;
100
101		Ok(())
102	}
103
104	/// Remove a configuration value
105	pub async fn remove(&self, key:String) -> Result<bool> {
106		dev_log!("config", "Removing configuration value: {}", key);
107
108		let removed = self.config.write().await.remove(&key).is_some();
109		Ok(removed)
110	}
111
112	/// Get all configuration values
113	pub async fn get_all(&self) -> HashMap<String, Value> {
114		self.config
115			.read()
116			.await
117			.iter()
118			.map(|(k, v)| (k.clone(), v.value.clone()))
119			.collect()
120	}
121
122	/// Get all configuration values in a scope
123	pub async fn get_all_in_scope(&self, scope:ConfigurationScope) -> HashMap<String, Value> {
124		self.config
125			.read()
126			.await
127			.iter()
128			.filter(|(_, v)| v.scope == scope)
129			.map(|(k, v)| (k.clone(), v.value.clone()))
130			.collect()
131	}
132
133	/// Load configuration from a file
134	pub async fn load_from_file(&self, path:&Path, scope:ConfigurationScope) -> Result<()> {
135		dev_log!("config", "Loading configuration from: {:?}", path);
136
137		let content = tokio::fs::read_to_string(path)
138			.await
139			.context("Failed to read configuration file")?;
140
141		let config:Value = serde_json::from_str(&content).context("Failed to parse configuration file")?;
142
143		self.load_from_value(config, scope).await?;
144
145		// Store path for future reference
146		self.config_paths.write().await.insert(scope, path.to_path_buf());
147
148		dev_log!("config", "Configuration loaded successfully");
149
150		Ok(())
151	}
152
153	/// Load configuration from a value
154	pub async fn load_from_value(&self, value:Value, scope:ConfigurationScope) -> Result<()> {
155		if let Value::Object(object) = value {
156			let mut config = self.config.write().await;
157			let now = std::time::SystemTime::now()
158				.duration_since(std::time::UNIX_EPOCH)
159				.map(|d| d.as_secs())
160				.unwrap_or(0);
161
162			for (key, val) in object {
163				config.insert(key, ConfigurationValue { value:val, scope, modified_at:now });
164			}
165		}
166
167		Ok(())
168	}
169
170	/// Save configuration to a file
171	pub async fn save_to_file(&self, path:&Path, scope:ConfigurationScope) -> Result<()> {
172		dev_log!("config", "Saving configuration to: {:?}", path);
173
174		let config = self.get_all_in_scope(scope).await;
175		let config_value = Value::Object(config.into_iter().map(|(k, v)| (k, v)).collect());
176
177		let content = serde_json::to_string_pretty(&config_value).context("Failed to serialize configuration")?;
178
179		tokio::fs::write(path, content)
180			.await
181			.context("Failed to write configuration file")?;
182
183		dev_log!("config", "Configuration saved successfully");
184
185		Ok(())
186	}
187
188	/// Register a configuration watcher
189	pub async fn register_watcher<F>(&self, key:String, callback:F)
190	where
191		F: Fn(String, Value) -> Result<()> + Send + Sync + 'static, {
192		let key_clone = key.clone();
193		let mut watchers = self.watchers.write().await;
194		watchers
195			.entry(key)
196			.or_insert_with(Vec::new)
197			.push(Arc::new(RwLock::new(callback)));
198		dev_log!("config", "Registered configuration watcher for: {}", key_clone);
199	}
200
201	/// Unregister a configuration watcher
202	pub async fn unregister_watcher(&self, key:String) -> Result<bool> {
203		let mut watchers = self.watchers.write().await;
204		let removed = watchers.remove(&key).is_some();
205		Ok(removed)
206	}
207
208	/// Notify watchers of configuration changes
209	async fn notify_watchers(&self, key:String, value:Value) {
210		let watchers = self.watchers.read().await;
211
212		if let Some(callbacks) = watchers.get(&key) {
213			for callback in callbacks {
214				if let Err(e) = callback.read().await(key.clone(), value.clone()) {
215					dev_log!("config", "warn: configuration watcher callback failed: {}", e);
216				}
217			}
218		}
219	}
220
221	/// Get configuration paths
222	pub async fn get_config_paths(&self) -> HashMap<ConfigurationScope, PathBuf> {
223		self.config_paths.read().await.clone()
224	}
225}
226
227impl Service for ConfigurationServiceImpl {
228	fn name(&self) -> &str { &self.name }
229
230	async fn start(&self) -> Result<()> {
231		dev_log!("config", "Starting configuration service");
232
233		*self.running.write().await = true;
234
235		dev_log!("config", "Configuration service started");
236		Ok(())
237	}
238
239	async fn stop(&self) -> Result<()> {
240		dev_log!("config", "Stopping configuration service");
241
242		*self.running.write().await = false;
243
244		dev_log!("config", "Configuration service stopped");
245		Ok(())
246	}
247
248	async fn is_running(&self) -> bool { *self.running.read().await }
249}
250
251#[cfg(test)]
252mod tests {
253	use super::*;
254
255	#[tokio::test]
256	async fn test_configuration_service_basic() {
257		let service = ConfigurationServiceImpl::new(None);
258		let _:anyhow::Result<()> = service.start().await;
259
260		// Test setting and getting
261		let _:anyhow::Result<()> = service
262			.set(
263				"test.key".to_string(),
264				serde_json::json!("test-value"),
265				ConfigurationScope::Global,
266			)
267			.await;
268
269		let value = service.get("test.key").await;
270		assert_eq!(value, Some(serde_json::json!("test-value")));
271
272		let _:anyhow::Result<()> = service.stop().await;
273	}
274
275	#[tokio::test]
276	async fn test_get_with_default() {
277		let service = ConfigurationServiceImpl::new(None);
278
279		let default = serde_json::json!("default-value");
280		let value = service.get_with_default("nonexistent.key", default.clone()).await;
281		assert_eq!(value, default);
282	}
283
284	#[tokio::test]
285	async fn test_get_all_in_scope() {
286		let service = ConfigurationServiceImpl::new(None);
287
288		let _:anyhow::Result<()> = service
289			.set("key1".to_string(), serde_json::json!("value1"), ConfigurationScope::Global)
290			.await;
291
292		let _:anyhow::Result<()> = service
293			.set("key2".to_string(), serde_json::json!("value2"), ConfigurationScope::Workspace)
294			.await;
295
296		let global_values = service.get_all_in_scope(ConfigurationScope::Global).await;
297		assert_eq!(global_values.len(), 1);
298		assert_eq!(global_values.get("key1"), Some(&serde_json::json!("value1")));
299	}
300
301	#[test]
302	fn test_configuration_scope() {
303		let global = ConfigurationScope::Global;
304		let workspace = ConfigurationScope::Workspace;
305		let extension = ConfigurationScope::Extension;
306
307		assert_eq!(global, ConfigurationScope::Global);
308		assert_ne!(global, workspace);
309		assert_ne!(global, extension);
310	}
311}