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