Skip to main content

mockforge_vbr/
config.rs

1//! VBR engine configuration
2//!
3//! This module defines the configuration structure for the Virtual Backend Reality engine,
4//! including storage backend selection, entity definitions, session configuration, and
5//! time-based data evolution settings.
6
7use serde::{Deserialize, Serialize};
8use std::path::PathBuf;
9
10/// Storage backend type for the virtual database
11#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
12#[serde(tag = "backend", rename_all = "lowercase")]
13pub enum StorageBackend {
14    /// SQLite database backend (persistent, production-like)
15    Sqlite {
16        /// Path to the SQLite database file
17        path: PathBuf,
18    },
19    /// JSON file backend (human-readable, easy to inspect)
20    Json {
21        /// Path to the JSON file
22        path: PathBuf,
23    },
24    /// In-memory backend (fast, no persistence)
25    Memory,
26}
27
28impl Default for StorageBackend {
29    fn default() -> Self {
30        StorageBackend::Sqlite {
31            path: PathBuf::from("./data/vbr.db"),
32        }
33    }
34}
35
36/// Session configuration
37#[derive(Debug, Clone, Serialize, Deserialize)]
38pub struct SessionConfig {
39    /// Whether sessions are enabled
40    #[serde(default = "default_true")]
41    pub enabled: bool,
42
43    /// Session timeout in seconds
44    #[serde(default = "default_session_timeout")]
45    pub timeout: u64,
46
47    /// Whether to use session-scoped data (per-session virtual DB)
48    #[serde(default)]
49    pub scoped_data: bool,
50}
51
52fn default_true() -> bool {
53    true
54}
55
56fn default_session_timeout() -> u64 {
57    3600 // 1 hour
58}
59
60impl Default for SessionConfig {
61    fn default() -> Self {
62        Self {
63            enabled: true,
64            timeout: default_session_timeout(),
65            scoped_data: false,
66        }
67    }
68}
69
70/// Data aging configuration
71#[derive(Debug, Clone, Serialize, Deserialize)]
72pub struct AgingConfig {
73    /// Whether data aging is enabled
74    #[serde(default)]
75    pub enabled: bool,
76
77    /// Cleanup interval in seconds
78    #[serde(default = "default_cleanup_interval")]
79    pub cleanup_interval: u64,
80
81    /// Whether to automatically update timestamp fields
82    #[serde(default = "default_true")]
83    pub auto_update_timestamps: bool,
84}
85
86fn default_cleanup_interval() -> u64 {
87    3600 // 1 hour
88}
89
90impl Default for AgingConfig {
91    fn default() -> Self {
92        Self {
93            enabled: false,
94            cleanup_interval: default_cleanup_interval(),
95            auto_update_timestamps: true,
96        }
97    }
98}
99
100/// Authentication configuration
101#[derive(Debug, Clone, Serialize, Deserialize)]
102pub struct AuthConfig {
103    /// Whether authentication is enabled
104    #[serde(default)]
105    pub enabled: bool,
106
107    /// JWT secret for token generation
108    #[serde(default)]
109    pub jwt_secret: Option<String>,
110
111    /// Token expiration in seconds
112    #[serde(default = "default_token_expiration")]
113    pub token_expiration: u64,
114}
115
116fn default_token_expiration() -> u64 {
117    86400 // 24 hours
118}
119
120impl Default for AuthConfig {
121    fn default() -> Self {
122        Self {
123            enabled: false,
124            jwt_secret: None,
125            token_expiration: default_token_expiration(),
126        }
127    }
128}
129
130/// VBR engine configuration
131#[derive(Debug, Clone, Serialize, Deserialize)]
132pub struct VbrConfig {
133    /// Storage backend configuration
134    #[serde(default)]
135    pub storage: StorageBackend,
136
137    /// Session configuration
138    #[serde(default)]
139    pub sessions: SessionConfig,
140
141    /// Data aging configuration
142    #[serde(default)]
143    pub aging: AgingConfig,
144
145    /// Authentication configuration
146    #[serde(default)]
147    pub auth: AuthConfig,
148
149    /// API base path prefix
150    #[serde(default = "default_api_prefix")]
151    pub api_prefix: String,
152}
153
154fn default_api_prefix() -> String {
155    "/api".to_string()
156}
157
158impl Default for VbrConfig {
159    fn default() -> Self {
160        Self {
161            storage: StorageBackend::default(),
162            sessions: SessionConfig::default(),
163            aging: AgingConfig::default(),
164            auth: AuthConfig::default(),
165            api_prefix: default_api_prefix(),
166        }
167    }
168}
169
170impl VbrConfig {
171    /// Create a new VBR configuration with default settings
172    pub fn new() -> Self {
173        Self::default()
174    }
175
176    /// Set the storage backend
177    pub fn with_storage_backend(mut self, backend: StorageBackend) -> Self {
178        self.storage = backend;
179        self
180    }
181
182    /// Set session configuration
183    pub fn with_sessions(mut self, config: SessionConfig) -> Self {
184        self.sessions = config;
185        self
186    }
187
188    /// Set aging configuration
189    pub fn with_aging(mut self, config: AgingConfig) -> Self {
190        self.aging = config;
191        self
192    }
193
194    /// Set authentication configuration
195    pub fn with_auth(mut self, config: AuthConfig) -> Self {
196        self.auth = config;
197        self
198    }
199
200    /// Set the API prefix
201    pub fn with_api_prefix(mut self, prefix: String) -> Self {
202        self.api_prefix = prefix;
203        self
204    }
205}
206
207#[cfg(test)]
208mod tests {
209    use super::*;
210
211    // StorageBackend tests
212    #[test]
213    fn test_storage_backend_default() {
214        let backend = StorageBackend::default();
215        assert!(matches!(backend, StorageBackend::Sqlite { .. }));
216    }
217
218    #[test]
219    fn test_storage_backend_sqlite() {
220        let backend = StorageBackend::Sqlite {
221            path: PathBuf::from("/tmp/test.db"),
222        };
223        if let StorageBackend::Sqlite { path } = backend {
224            assert_eq!(path, PathBuf::from("/tmp/test.db"));
225        } else {
226            panic!("Expected Sqlite variant");
227        }
228    }
229
230    #[test]
231    fn test_storage_backend_json() {
232        let backend = StorageBackend::Json {
233            path: PathBuf::from("/tmp/data.json"),
234        };
235        if let StorageBackend::Json { path } = backend {
236            assert_eq!(path, PathBuf::from("/tmp/data.json"));
237        } else {
238            panic!("Expected Json variant");
239        }
240    }
241
242    #[test]
243    fn test_storage_backend_memory() {
244        let backend = StorageBackend::Memory;
245        assert!(matches!(backend, StorageBackend::Memory));
246    }
247
248    #[test]
249    fn test_storage_backend_serialize_sqlite() {
250        let backend = StorageBackend::Sqlite {
251            path: PathBuf::from("./data/vbr.db"),
252        };
253        let json = serde_json::to_string(&backend).unwrap();
254        assert!(json.contains("\"backend\":\"sqlite\""));
255        assert!(json.contains("vbr.db"));
256    }
257
258    #[test]
259    fn test_storage_backend_serialize_memory() {
260        let backend = StorageBackend::Memory;
261        let json = serde_json::to_string(&backend).unwrap();
262        assert!(json.contains("\"backend\":\"memory\""));
263    }
264
265    #[test]
266    fn test_storage_backend_clone() {
267        let backend = StorageBackend::Memory;
268        let cloned = backend.clone();
269        assert!(matches!(cloned, StorageBackend::Memory));
270    }
271
272    #[test]
273    fn test_storage_backend_debug() {
274        let backend = StorageBackend::Memory;
275        let debug = format!("{:?}", backend);
276        assert!(debug.contains("Memory"));
277    }
278
279    #[test]
280    fn test_storage_backend_eq() {
281        let b1 = StorageBackend::Memory;
282        let b2 = StorageBackend::Memory;
283        assert_eq!(b1, b2);
284
285        let b3 = StorageBackend::Sqlite {
286            path: PathBuf::from("a.db"),
287        };
288        let b4 = StorageBackend::Sqlite {
289            path: PathBuf::from("b.db"),
290        };
291        assert_ne!(b3, b4);
292    }
293
294    // SessionConfig tests
295    #[test]
296    fn test_session_config_default() {
297        let config = SessionConfig::default();
298        assert!(config.enabled);
299        assert_eq!(config.timeout, 3600);
300        assert!(!config.scoped_data);
301    }
302
303    #[test]
304    fn test_session_config_clone() {
305        let config = SessionConfig {
306            enabled: false,
307            timeout: 7200,
308            scoped_data: true,
309        };
310        let cloned = config.clone();
311        assert_eq!(config.enabled, cloned.enabled);
312        assert_eq!(config.timeout, cloned.timeout);
313        assert_eq!(config.scoped_data, cloned.scoped_data);
314    }
315
316    #[test]
317    fn test_session_config_serialize() {
318        let config = SessionConfig::default();
319        let json = serde_json::to_string(&config).unwrap();
320        assert!(json.contains("\"enabled\":true"));
321        assert!(json.contains("\"timeout\":3600"));
322    }
323
324    #[test]
325    fn test_session_config_deserialize() {
326        let json = r#"{"enabled": false, "timeout": 7200, "scoped_data": true}"#;
327        let config: SessionConfig = serde_json::from_str(json).unwrap();
328        assert!(!config.enabled);
329        assert_eq!(config.timeout, 7200);
330        assert!(config.scoped_data);
331    }
332
333    #[test]
334    fn test_session_config_debug() {
335        let config = SessionConfig::default();
336        let debug = format!("{:?}", config);
337        assert!(debug.contains("SessionConfig"));
338    }
339
340    // AgingConfig tests
341    #[test]
342    fn test_aging_config_default() {
343        let config = AgingConfig::default();
344        assert!(!config.enabled);
345        assert_eq!(config.cleanup_interval, 3600);
346        assert!(config.auto_update_timestamps);
347    }
348
349    #[test]
350    fn test_aging_config_clone() {
351        let config = AgingConfig {
352            enabled: true,
353            cleanup_interval: 1800,
354            auto_update_timestamps: false,
355        };
356        let cloned = config.clone();
357        assert_eq!(config.enabled, cloned.enabled);
358        assert_eq!(config.cleanup_interval, cloned.cleanup_interval);
359    }
360
361    #[test]
362    fn test_aging_config_serialize() {
363        let config = AgingConfig::default();
364        let json = serde_json::to_string(&config).unwrap();
365        assert!(json.contains("\"enabled\":false"));
366        assert!(json.contains("\"cleanup_interval\":3600"));
367    }
368
369    #[test]
370    fn test_aging_config_debug() {
371        let config = AgingConfig::default();
372        let debug = format!("{:?}", config);
373        assert!(debug.contains("AgingConfig"));
374    }
375
376    // AuthConfig tests
377    #[test]
378    fn test_auth_config_default() {
379        let config = AuthConfig::default();
380        assert!(!config.enabled);
381        assert!(config.jwt_secret.is_none());
382        assert_eq!(config.token_expiration, 86400);
383    }
384
385    #[test]
386    fn test_auth_config_with_secret() {
387        let config = AuthConfig {
388            enabled: true,
389            jwt_secret: Some("my-secret-key".to_string()),
390            token_expiration: 3600,
391        };
392        assert!(config.enabled);
393        assert_eq!(config.jwt_secret, Some("my-secret-key".to_string()));
394        assert_eq!(config.token_expiration, 3600);
395    }
396
397    #[test]
398    fn test_auth_config_clone() {
399        let config = AuthConfig {
400            enabled: true,
401            jwt_secret: Some("secret".to_string()),
402            token_expiration: 7200,
403        };
404        let cloned = config.clone();
405        assert_eq!(config.enabled, cloned.enabled);
406        assert_eq!(config.jwt_secret, cloned.jwt_secret);
407    }
408
409    #[test]
410    fn test_auth_config_serialize() {
411        let config = AuthConfig::default();
412        let json = serde_json::to_string(&config).unwrap();
413        assert!(json.contains("\"enabled\":false"));
414        assert!(json.contains("\"token_expiration\":86400"));
415    }
416
417    #[test]
418    fn test_auth_config_debug() {
419        let config = AuthConfig::default();
420        let debug = format!("{:?}", config);
421        assert!(debug.contains("AuthConfig"));
422    }
423
424    // VbrConfig tests
425    #[test]
426    fn test_vbr_config_default() {
427        let config = VbrConfig::default();
428        assert!(config.sessions.enabled);
429        assert_eq!(config.sessions.timeout, 3600);
430        assert!(!config.aging.enabled);
431        assert!(!config.auth.enabled);
432        assert_eq!(config.api_prefix, "/api");
433    }
434
435    #[test]
436    fn test_vbr_config_new() {
437        let config = VbrConfig::new();
438        assert_eq!(config.api_prefix, "/api");
439    }
440
441    #[test]
442    fn test_vbr_config_builder() {
443        let config = VbrConfig::new()
444            .with_api_prefix("/v1/api".to_string())
445            .with_storage_backend(StorageBackend::Memory);
446
447        assert_eq!(config.api_prefix, "/v1/api");
448        assert!(matches!(config.storage, StorageBackend::Memory));
449    }
450
451    #[test]
452    fn test_vbr_config_with_sessions() {
453        let session_config = SessionConfig {
454            enabled: false,
455            timeout: 1800,
456            scoped_data: true,
457        };
458
459        let config = VbrConfig::new().with_sessions(session_config);
460        assert!(!config.sessions.enabled);
461        assert_eq!(config.sessions.timeout, 1800);
462        assert!(config.sessions.scoped_data);
463    }
464
465    #[test]
466    fn test_vbr_config_with_aging() {
467        let aging_config = AgingConfig {
468            enabled: true,
469            cleanup_interval: 600,
470            auto_update_timestamps: false,
471        };
472
473        let config = VbrConfig::new().with_aging(aging_config);
474        assert!(config.aging.enabled);
475        assert_eq!(config.aging.cleanup_interval, 600);
476        assert!(!config.aging.auto_update_timestamps);
477    }
478
479    #[test]
480    fn test_vbr_config_with_auth() {
481        let auth_config = AuthConfig {
482            enabled: true,
483            jwt_secret: Some("test-secret".to_string()),
484            token_expiration: 3600,
485        };
486
487        let config = VbrConfig::new().with_auth(auth_config);
488        assert!(config.auth.enabled);
489        assert_eq!(config.auth.jwt_secret, Some("test-secret".to_string()));
490    }
491
492    #[test]
493    fn test_vbr_config_full_builder_chain() {
494        let config = VbrConfig::new()
495            .with_storage_backend(StorageBackend::Json {
496                path: PathBuf::from("/tmp/vbr.json"),
497            })
498            .with_sessions(SessionConfig {
499                enabled: true,
500                timeout: 7200,
501                scoped_data: true,
502            })
503            .with_aging(AgingConfig {
504                enabled: true,
505                cleanup_interval: 300,
506                auto_update_timestamps: true,
507            })
508            .with_auth(AuthConfig {
509                enabled: true,
510                jwt_secret: Some("secret".to_string()),
511                token_expiration: 86400,
512            })
513            .with_api_prefix("/v2/api".to_string());
514
515        assert!(matches!(config.storage, StorageBackend::Json { .. }));
516        assert!(config.sessions.scoped_data);
517        assert!(config.aging.enabled);
518        assert!(config.auth.enabled);
519        assert_eq!(config.api_prefix, "/v2/api");
520    }
521
522    #[test]
523    fn test_vbr_config_clone() {
524        let config = VbrConfig::new()
525            .with_api_prefix("/test".to_string())
526            .with_storage_backend(StorageBackend::Memory);
527
528        let cloned = config.clone();
529        assert_eq!(config.api_prefix, cloned.api_prefix);
530    }
531
532    #[test]
533    fn test_vbr_config_serialize() {
534        let config = VbrConfig::default();
535        let json = serde_json::to_string(&config).unwrap();
536        assert!(json.contains("\"api_prefix\":\"/api\""));
537    }
538
539    #[test]
540    fn test_vbr_config_deserialize() {
541        let json = r#"{
542            "storage": {"backend": "memory"},
543            "sessions": {"enabled": true, "timeout": 3600, "scoped_data": false},
544            "aging": {"enabled": false, "cleanup_interval": 3600, "auto_update_timestamps": true},
545            "auth": {"enabled": false, "jwt_secret": null, "token_expiration": 86400},
546            "api_prefix": "/custom"
547        }"#;
548
549        let config: VbrConfig = serde_json::from_str(json).unwrap();
550        assert!(matches!(config.storage, StorageBackend::Memory));
551        assert_eq!(config.api_prefix, "/custom");
552    }
553
554    #[test]
555    fn test_vbr_config_debug() {
556        let config = VbrConfig::default();
557        let debug = format!("{:?}", config);
558        assert!(debug.contains("VbrConfig"));
559    }
560}