gglib_core/services/
settings_service.rs1use crate::ports::{CoreError, SettingsRepository};
4use crate::settings::{Settings, SettingsUpdate, validate_settings};
5use std::sync::Arc;
6
7pub struct SettingsService {
9 repo: Arc<dyn SettingsRepository>,
10}
11
12impl SettingsService {
13 pub fn new(repo: Arc<dyn SettingsRepository>) -> Self {
15 Self { repo }
16 }
17
18 pub fn repo(&self) -> Arc<dyn SettingsRepository> {
20 Arc::clone(&self.repo)
21 }
22
23 pub async fn get(&self) -> Result<Settings, CoreError> {
25 self.repo.load().await.map_err(CoreError::from)
26 }
27
28 pub async fn update(&self, update: SettingsUpdate) -> Result<Settings, CoreError> {
36 self.repo
37 .modify(&|settings: &mut Settings| {
38 settings.merge(&update);
39 validate_settings(settings)
40 })
41 .await
42 }
43
44 pub async fn save(&self, settings: &Settings) -> Result<(), CoreError> {
46 validate_settings(settings)?;
47 self.repo.save(settings).await.map_err(CoreError::from)
48 }
49}
50
51#[cfg(test)]
52mod tests {
53 use super::*;
54 use crate::ports::RepositoryError;
55 use async_trait::async_trait;
56 use std::sync::Mutex;
57
58 struct MockSettingsRepo {
59 settings: Mutex<Settings>,
60 }
61
62 impl MockSettingsRepo {
63 fn new() -> Self {
64 Self {
65 settings: Mutex::new(Settings::with_defaults()),
66 }
67 }
68 }
69
70 #[async_trait]
71 impl SettingsRepository for MockSettingsRepo {
72 async fn load(&self) -> Result<Settings, RepositoryError> {
73 Ok(self.settings.lock().unwrap().clone())
74 }
75
76 async fn save(&self, settings: &Settings) -> Result<(), RepositoryError> {
77 *self.settings.lock().unwrap() = settings.clone();
78 Ok(())
79 }
80 }
81
82 #[tokio::test]
83 async fn test_get_default_settings() {
84 let repo = Arc::new(MockSettingsRepo::new());
85 let service = SettingsService::new(repo);
86
87 let settings = service.get().await.unwrap();
88 assert_eq!(settings.default_context_size, None);
90 }
91
92 #[tokio::test]
93 async fn test_update_settings() {
94 let repo = Arc::new(MockSettingsRepo::new());
95 let service = SettingsService::new(repo);
96
97 let update = SettingsUpdate {
98 default_context_size: Some(Some(8192)),
99 ..Default::default()
100 };
101
102 let updated = service.update(update).await.unwrap();
103 assert_eq!(updated.default_context_size, Some(8192));
104
105 let fetched = service.get().await.unwrap();
107 assert_eq!(fetched.default_context_size, Some(8192));
108 }
109
110 #[tokio::test]
113 async fn an_update_that_fails_validation_stores_nothing() {
114 let repo = Arc::new(MockSettingsRepo::new());
115 let service = SettingsService::new(repo);
116
117 let refused = service
118 .update(SettingsUpdate {
119 proxy_port: Some(Some(9191)),
120 default_context_size: Some(Some(1)),
121 ..Default::default()
122 })
123 .await;
124
125 assert!(
126 matches!(refused, Err(CoreError::Settings(_))),
127 "{refused:?}"
128 );
129 assert_eq!(
130 service.get().await.unwrap().proxy_port,
131 Settings::with_defaults().proxy_port,
132 "the valid half of a refused update was not stored"
133 );
134 }
135}