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}