Skip to main content

kipuka/config/
db.rs

1//! Database configuration.
2//!
3//! Kipuka supports three database backends via sqlx:
4//!
5//! - **SQLite** — default, zero-dependency option for single-node deployments.
6//! - **PostgreSQL** — recommended for HA and multi-node setups.
7//! - **MariaDB** — alternative for environments where MySQL-compatible
8//!   databases are the standard.
9
10use serde::Deserialize;
11
12/// `[database]` section — connection pool configuration.
13///
14/// ```toml
15/// [database]
16/// url = "sqlite:///var/lib/kipuka/kipuka.db"
17/// max_connections = 10
18/// min_connections = 2
19/// ```
20#[derive(Debug, Clone, Deserialize)]
21#[serde(deny_unknown_fields)]
22pub struct DbConfig {
23    /// Database connection URL.
24    ///
25    /// Examples:
26    /// - `"sqlite:///var/lib/kipuka/kipuka.db"` — file-backed SQLite
27    /// - `"sqlite::memory:"` — in-memory SQLite (testing only)
28    /// - `"postgres://user:pass@host/kipuka"` — PostgreSQL
29    /// - `"mariadb://user:pass@host/kipuka"` — MariaDB
30    ///
31    /// Supports `"env:VAR_NAME"` to read the URL from an environment variable.
32    pub url: String,
33
34    /// Maximum number of connections in the pool.
35    ///
36    /// Default: 10 for PostgreSQL/MariaDB, 1 for SQLite (WAL mode
37    /// serializes writes regardless of pool size).
38    pub max_connections: Option<u32>,
39
40    /// Minimum number of idle connections maintained in the pool.
41    ///
42    /// Default: none (sqlx default applies).
43    pub min_connections: Option<u32>,
44
45    /// Connection acquisition timeout in seconds.
46    ///
47    /// Default: 30.
48    #[serde(default = "default_connect_timeout_secs")]
49    pub connect_timeout_secs: u64,
50
51    /// Maximum connection lifetime in seconds before recycling.
52    ///
53    /// Default: 3600 (1 hour).
54    #[serde(default = "default_max_lifetime_secs")]
55    pub max_lifetime_secs: u64,
56
57    /// Enable SQLite WAL (Write-Ahead Logging) mode.
58    ///
59    /// WAL provides better concurrent read performance by allowing
60    /// readers and writers to proceed simultaneously.  Enabled by
61    /// default for SQLite; ignored for other backends.
62    #[serde(default = "bool_true")]
63    pub sqlite_wal: bool,
64
65    /// Run pending migrations at startup.
66    ///
67    /// Default: `true`.
68    #[serde(default = "bool_true")]
69    pub run_migrations: bool,
70}
71
72fn default_connect_timeout_secs() -> u64 {
73    30
74}
75
76fn default_max_lifetime_secs() -> u64 {
77    3600
78}
79
80fn bool_true() -> bool {
81    true
82}
83
84impl Default for DbConfig {
85    fn default() -> Self {
86        Self {
87            url: "sqlite:///var/lib/kipuka/kipuka.db".to_string(),
88            max_connections: None,
89            min_connections: None,
90            connect_timeout_secs: default_connect_timeout_secs(),
91            max_lifetime_secs: default_max_lifetime_secs(),
92            sqlite_wal: true,
93            run_migrations: true,
94        }
95    }
96}
97
98impl DbConfig {
99    /// Resolve the database URL, expanding `"env:VAR_NAME"` references.
100    pub fn resolve_url(&self) -> std::result::Result<String, String> {
101        if let Some(var_name) = self.url.strip_prefix("env:") {
102            std::env::var(var_name).map_err(|_| {
103                format!("[database].url references env var {var_name:?} which is not set")
104            })
105        } else {
106            Ok(self.url.clone())
107        }
108    }
109}