forge_core/config/
database.rs

1use serde::{Deserialize, Serialize};
2
3/// Database configuration.
4#[derive(Debug, Clone, Serialize, Deserialize)]
5pub struct DatabaseConfig {
6    /// Primary database connection URL.
7    pub url: String,
8
9    /// Connection pool size.
10    #[serde(default = "default_pool_size")]
11    pub pool_size: u32,
12
13    /// Pool checkout timeout in seconds.
14    #[serde(default = "default_pool_timeout")]
15    pub pool_timeout_secs: u64,
16
17    /// Statement timeout in seconds.
18    #[serde(default = "default_statement_timeout")]
19    pub statement_timeout_secs: u64,
20
21    /// Read replica URLs for scaling reads.
22    #[serde(default)]
23    pub replica_urls: Vec<String>,
24
25    /// Whether to route read queries to replicas.
26    #[serde(default)]
27    pub read_from_replica: bool,
28
29    /// Connection pool isolation configuration.
30    #[serde(default)]
31    pub pools: PoolsConfig,
32}
33
34impl Default for DatabaseConfig {
35    fn default() -> Self {
36        Self {
37            url: String::new(),
38            pool_size: default_pool_size(),
39            pool_timeout_secs: default_pool_timeout(),
40            statement_timeout_secs: default_statement_timeout(),
41            replica_urls: Vec::new(),
42            read_from_replica: false,
43            pools: PoolsConfig::default(),
44        }
45    }
46}
47
48fn default_pool_size() -> u32 {
49    50
50}
51
52fn default_pool_timeout() -> u64 {
53    30
54}
55
56fn default_statement_timeout() -> u64 {
57    30
58}
59
60/// Pool isolation configuration for different workloads.
61#[derive(Debug, Clone, Serialize, Deserialize, Default)]
62pub struct PoolsConfig {
63    /// Default pool for queries/mutations.
64    #[serde(default)]
65    pub default: Option<PoolConfig>,
66
67    /// Pool for background jobs.
68    #[serde(default)]
69    pub jobs: Option<PoolConfig>,
70
71    /// Pool for observability writes.
72    #[serde(default)]
73    pub observability: Option<PoolConfig>,
74
75    /// Pool for long-running analytics.
76    #[serde(default)]
77    pub analytics: Option<PoolConfig>,
78}
79
80/// Individual pool configuration.
81#[derive(Debug, Clone, Serialize, Deserialize)]
82pub struct PoolConfig {
83    /// Pool size.
84    pub size: u32,
85
86    /// Checkout timeout in seconds.
87    #[serde(default = "default_pool_timeout")]
88    pub timeout_secs: u64,
89
90    /// Statement timeout in seconds (optional override).
91    pub statement_timeout_secs: Option<u64>,
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97
98    #[test]
99    fn test_default_database_config() {
100        let config = DatabaseConfig::default();
101        assert_eq!(config.pool_size, 50);
102        assert_eq!(config.pool_timeout_secs, 30);
103    }
104
105    #[test]
106    fn test_parse_database_config() {
107        let toml = r#"
108            url = "postgres://localhost/test"
109            pool_size = 100
110            replica_urls = ["postgres://replica1/test", "postgres://replica2/test"]
111            read_from_replica = true
112        "#;
113
114        let config: DatabaseConfig = toml::from_str(toml).unwrap();
115        assert_eq!(config.pool_size, 100);
116        assert_eq!(config.replica_urls.len(), 2);
117        assert!(config.read_from_replica);
118    }
119}