gglib_core/services/
settings_service.rs

1//! Settings service - orchestrates settings operations.
2
3use crate::ports::{CoreError, SettingsRepository};
4use crate::settings::{Settings, SettingsUpdate, validate_settings};
5use std::sync::Arc;
6
7/// Service for settings operations.
8pub struct SettingsService {
9    repo: Arc<dyn SettingsRepository>,
10}
11
12impl SettingsService {
13    /// Create a new settings service.
14    pub fn new(repo: Arc<dyn SettingsRepository>) -> Self {
15        Self { repo }
16    }
17
18    /// Get current settings.
19    pub async fn get(&self) -> Result<Settings, CoreError> {
20        self.repo.load().await.map_err(CoreError::from)
21    }
22
23    /// Update settings with partial changes.
24    pub async fn update(&self, update: SettingsUpdate) -> Result<Settings, CoreError> {
25        let mut current = self.repo.load().await.map_err(CoreError::from)?;
26        current.merge(&update);
27        validate_settings(&current)?;
28        self.repo.save(&current).await.map_err(CoreError::from)?;
29        Ok(current)
30    }
31
32    /// Save complete settings (validates first).
33    pub async fn save(&self, settings: &Settings) -> Result<(), CoreError> {
34        validate_settings(settings)?;
35        self.repo.save(settings).await.map_err(CoreError::from)
36    }
37}
38
39#[cfg(test)]
40mod tests {
41    use super::*;
42    use crate::ports::RepositoryError;
43    use async_trait::async_trait;
44    use std::sync::Mutex;
45
46    struct MockSettingsRepo {
47        settings: Mutex<Settings>,
48    }
49
50    impl MockSettingsRepo {
51        fn new() -> Self {
52            Self {
53                settings: Mutex::new(Settings::with_defaults()),
54            }
55        }
56    }
57
58    #[async_trait]
59    impl SettingsRepository for MockSettingsRepo {
60        async fn load(&self) -> Result<Settings, RepositoryError> {
61            Ok(self.settings.lock().unwrap().clone())
62        }
63
64        async fn save(&self, settings: &Settings) -> Result<(), RepositoryError> {
65            *self.settings.lock().unwrap() = settings.clone();
66            Ok(())
67        }
68    }
69
70    #[tokio::test]
71    async fn test_get_default_settings() {
72        let repo = Arc::new(MockSettingsRepo::new());
73        let service = SettingsService::new(repo);
74
75        let settings = service.get().await.unwrap();
76        assert_eq!(settings.default_context_size, Some(4096));
77    }
78
79    #[tokio::test]
80    async fn test_update_settings() {
81        let repo = Arc::new(MockSettingsRepo::new());
82        let service = SettingsService::new(repo);
83
84        let update = SettingsUpdate {
85            default_context_size: Some(Some(8192)),
86            ..Default::default()
87        };
88
89        let updated = service.update(update).await.unwrap();
90        assert_eq!(updated.default_context_size, Some(8192));
91
92        // Verify persisted
93        let fetched = service.get().await.unwrap();
94        assert_eq!(fetched.default_context_size, Some(8192));
95    }
96}