1use serde::Deserialize;
22use std::path::PathBuf;
23
24use super::expand_config_path;
25
26#[derive(Debug, Clone)]
30pub struct OrgConfig {
31 pub name: Option<String>,
35
36 pub backend: OrgBackendConfig,
38
39 pub enabled: bool,
41}
42
43impl Default for OrgConfig {
44 fn default() -> Self {
45 Self {
46 name: None,
47 backend: OrgBackendConfig::None,
48 enabled: false,
49 }
50 }
51}
52
53impl OrgConfig {
54 #[must_use]
61 pub fn from_config_file(file: &ConfigFileOrg, org_scope_enabled: bool) -> Self {
62 let backend = OrgBackendConfig::from_config_file(file);
63 let has_backend = !matches!(backend, OrgBackendConfig::None);
64
65 Self {
66 name: file.name.clone(),
67 backend,
68 enabled: org_scope_enabled && has_backend,
69 }
70 }
71
72 #[must_use]
74 pub const fn is_available(&self) -> bool {
75 self.enabled && !matches!(self.backend, OrgBackendConfig::None)
76 }
77
78 #[must_use]
80 pub fn name_or_default(&self) -> &str {
81 self.name.as_deref().unwrap_or("default")
82 }
83}
84
85#[derive(Debug, Clone, Default)]
91pub enum OrgBackendConfig {
92 SqliteShared {
97 path: PathBuf,
102 },
103
104 Postgresql {
109 connection_url: String,
114
115 max_connections: u32,
119
120 timeout_secs: u64,
124 },
125
126 #[default]
128 None,
129}
130
131impl OrgBackendConfig {
132 #[must_use]
134 pub fn from_config_file(file: &ConfigFileOrg) -> Self {
135 match file.backend.as_deref() {
136 Some("sqlite" | "sqlite3") => file.sqlite_path.as_ref().map_or_else(
137 || {
138 tracing::warn!("org.backend=sqlite but org.sqlite_path not set");
139 Self::None
140 },
141 |path| Self::SqliteShared {
142 path: PathBuf::from(expand_config_path(path)),
143 },
144 ),
145 Some("postgresql" | "postgres" | "pg") => file.postgres_url.as_ref().map_or_else(
146 || {
147 tracing::warn!("org.backend=postgresql but org.postgres_url not set");
148 Self::None
149 },
150 |url| Self::Postgresql {
151 connection_url: expand_config_path(url),
152 max_connections: file.postgres_max_connections.unwrap_or(10),
153 timeout_secs: file.postgres_timeout_secs.unwrap_or(30),
154 },
155 ),
156 Some("none") | None => Self::None,
157 Some(unknown) => {
158 tracing::warn!(
159 backend = unknown,
160 "Unknown org backend type, disabling org scope"
161 );
162 Self::None
163 },
164 }
165 }
166
167 #[must_use]
169 pub const fn is_configured(&self) -> bool {
170 !matches!(self, Self::None)
171 }
172
173 #[must_use]
175 pub const fn backend_type(&self) -> &'static str {
176 match self {
177 Self::SqliteShared { .. } => "sqlite",
178 Self::Postgresql { .. } => "postgresql",
179 Self::None => "none",
180 }
181 }
182}
183
184#[derive(Debug, Clone, Deserialize, Default)]
188pub struct ConfigFileOrg {
189 pub name: Option<String>,
193
194 pub backend: Option<String>,
198
199 pub sqlite_path: Option<String>,
203
204 pub postgres_url: Option<String>,
209
210 pub postgres_max_connections: Option<u32>,
212
213 pub postgres_timeout_secs: Option<u64>,
215}
216
217#[cfg(test)]
218mod tests {
219 use super::*;
220
221 #[test]
222 fn test_org_config_default() {
223 let config = OrgConfig::default();
224 assert!(config.name.is_none());
225 assert!(!config.enabled);
226 assert!(!config.is_available());
227 }
228
229 #[test]
230 fn test_org_backend_sqlite() {
231 let file = ConfigFileOrg {
232 name: Some("acme-corp".to_string()),
233 backend: Some("sqlite".to_string()),
234 sqlite_path: Some("/shared/org/index.db".to_string()),
235 ..Default::default()
236 };
237
238 let backend = OrgBackendConfig::from_config_file(&file);
239 assert!(matches!(backend, OrgBackendConfig::SqliteShared { .. }));
240 assert!(backend.is_configured());
241 assert_eq!(backend.backend_type(), "sqlite");
242 }
243
244 #[test]
245 fn test_org_backend_postgresql() {
246 let file = ConfigFileOrg {
247 name: Some("acme-corp".to_string()),
248 backend: Some("postgresql".to_string()),
249 postgres_url: Some("postgresql://user:pass@localhost:5432/subcog".to_string()),
250 postgres_max_connections: Some(20),
251 postgres_timeout_secs: Some(60),
252 ..Default::default()
253 };
254
255 let backend = OrgBackendConfig::from_config_file(&file);
256 assert!(matches!(backend, OrgBackendConfig::Postgresql { .. }));
257 assert!(backend.is_configured());
258 assert_eq!(backend.backend_type(), "postgresql");
259
260 if let OrgBackendConfig::Postgresql {
261 max_connections,
262 timeout_secs,
263 ..
264 } = backend
265 {
266 assert_eq!(max_connections, 20);
267 assert_eq!(timeout_secs, 60);
268 }
269 }
270
271 #[test]
272 fn test_org_backend_none() {
273 let file = ConfigFileOrg::default();
274 let backend = OrgBackendConfig::from_config_file(&file);
275 assert!(matches!(backend, OrgBackendConfig::None));
276 assert!(!backend.is_configured());
277 }
278
279 #[test]
280 fn test_org_config_from_file_enabled() {
281 let file = ConfigFileOrg {
282 name: Some("test-org".to_string()),
283 backend: Some("sqlite".to_string()),
284 sqlite_path: Some("/tmp/org.db".to_string()),
285 ..Default::default()
286 };
287
288 let config = OrgConfig::from_config_file(&file, true);
290 assert_eq!(config.name.as_deref(), Some("test-org"));
291 assert!(config.enabled);
292 assert!(config.is_available());
293 }
294
295 #[test]
296 fn test_org_config_from_file_disabled() {
297 let file = ConfigFileOrg {
298 name: Some("test-org".to_string()),
299 backend: Some("sqlite".to_string()),
300 sqlite_path: Some("/tmp/org.db".to_string()),
301 ..Default::default()
302 };
303
304 let config = OrgConfig::from_config_file(&file, false);
306 assert!(!config.enabled);
307 assert!(!config.is_available());
308 }
309
310 #[test]
311 fn test_org_config_sqlite_missing_path() {
312 let file = ConfigFileOrg {
313 name: Some("test-org".to_string()),
314 backend: Some("sqlite".to_string()),
315 sqlite_path: None, ..Default::default()
317 };
318
319 let backend = OrgBackendConfig::from_config_file(&file);
320 assert!(matches!(backend, OrgBackendConfig::None));
321 }
322
323 #[test]
324 fn test_name_or_default() {
325 let config = OrgConfig::default();
326 assert_eq!(config.name_or_default(), "default");
327
328 let config = OrgConfig {
329 name: Some("my-org".to_string()),
330 ..Default::default()
331 };
332 assert_eq!(config.name_or_default(), "my-org");
333 }
334}