prax_sqlx/
config.rs

1//! SQLx configuration for database connections.
2
3use crate::error::{SqlxError, SqlxResult};
4use std::time::Duration;
5
6/// Database backend type.
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum DatabaseBackend {
9    /// PostgreSQL database
10    Postgres,
11    /// MySQL database
12    MySql,
13    /// SQLite database
14    Sqlite,
15}
16
17impl DatabaseBackend {
18    /// Parse backend from URL scheme.
19    pub fn from_url(url: &str) -> SqlxResult<Self> {
20        if url.starts_with("postgres://") || url.starts_with("postgresql://") {
21            Ok(Self::Postgres)
22        } else if url.starts_with("mysql://") || url.starts_with("mariadb://") {
23            Ok(Self::MySql)
24        } else if url.starts_with("sqlite://") || url.starts_with("file:") {
25            Ok(Self::Sqlite)
26        } else {
27            Err(SqlxError::config(
28                "Unknown database URL scheme. Expected postgres://, mysql://, or sqlite://",
29            ))
30        }
31    }
32}
33
34/// SQLx pool configuration.
35#[derive(Debug, Clone)]
36pub struct SqlxConfig {
37    /// Database connection URL
38    pub url: String,
39    /// Database backend type (auto-detected from URL)
40    pub backend: DatabaseBackend,
41    /// Maximum number of connections in the pool
42    pub max_connections: u32,
43    /// Minimum number of connections to keep idle
44    pub min_connections: u32,
45    /// Connection timeout
46    pub connect_timeout: Duration,
47    /// Idle connection timeout
48    pub idle_timeout: Option<Duration>,
49    /// Maximum lifetime of a connection
50    pub max_lifetime: Option<Duration>,
51    /// Enable statement caching
52    pub statement_cache_capacity: usize,
53    /// Enable SSL/TLS
54    pub ssl_mode: SslMode,
55    /// Application name (for PostgreSQL)
56    pub application_name: Option<String>,
57}
58
59/// SSL mode for connections.
60#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
61pub enum SslMode {
62    /// Disable SSL
63    Disable,
64    /// Prefer SSL but allow non-SSL
65    #[default]
66    Prefer,
67    /// Require SSL
68    Require,
69    /// Require SSL and verify server certificate
70    VerifyCa,
71    /// Require SSL and verify server certificate and hostname
72    VerifyFull,
73}
74
75impl Default for SqlxConfig {
76    fn default() -> Self {
77        Self {
78            url: String::new(),
79            backend: DatabaseBackend::Postgres,
80            max_connections: 10,
81            min_connections: 1,
82            connect_timeout: Duration::from_secs(30),
83            idle_timeout: Some(Duration::from_secs(600)),
84            max_lifetime: Some(Duration::from_secs(1800)),
85            statement_cache_capacity: 100,
86            ssl_mode: SslMode::default(),
87            application_name: None,
88        }
89    }
90}
91
92impl SqlxConfig {
93    /// Create a new configuration from a database URL.
94    ///
95    /// # Example
96    ///
97    /// ```rust
98    /// use prax_sqlx::SqlxConfig;
99    ///
100    /// let config = SqlxConfig::from_url("postgres://user:pass@localhost/mydb").unwrap();
101    /// assert_eq!(config.max_connections, 10);
102    /// ```
103    pub fn from_url(url: impl Into<String>) -> SqlxResult<Self> {
104        let url = url.into();
105        let backend = DatabaseBackend::from_url(&url)?;
106
107        Ok(Self {
108            url,
109            backend,
110            ..Default::default()
111        })
112    }
113
114    /// Create a builder for more detailed configuration.
115    pub fn builder(url: impl Into<String>) -> SqlxConfigBuilder {
116        SqlxConfigBuilder::new(url)
117    }
118
119    /// Set max connections.
120    pub fn with_max_connections(mut self, max: u32) -> Self {
121        self.max_connections = max;
122        self
123    }
124
125    /// Set min connections.
126    pub fn with_min_connections(mut self, min: u32) -> Self {
127        self.min_connections = min;
128        self
129    }
130
131    /// Set connection timeout.
132    pub fn with_connect_timeout(mut self, timeout: Duration) -> Self {
133        self.connect_timeout = timeout;
134        self
135    }
136
137    /// Set idle timeout.
138    pub fn with_idle_timeout(mut self, timeout: Duration) -> Self {
139        self.idle_timeout = Some(timeout);
140        self
141    }
142
143    /// Set max lifetime.
144    pub fn with_max_lifetime(mut self, lifetime: Duration) -> Self {
145        self.max_lifetime = Some(lifetime);
146        self
147    }
148
149    /// Set statement cache capacity.
150    pub fn with_statement_cache(mut self, capacity: usize) -> Self {
151        self.statement_cache_capacity = capacity;
152        self
153    }
154
155    /// Set SSL mode.
156    pub fn with_ssl_mode(mut self, mode: SslMode) -> Self {
157        self.ssl_mode = mode;
158        self
159    }
160
161    /// Set application name.
162    pub fn with_application_name(mut self, name: impl Into<String>) -> Self {
163        self.application_name = Some(name.into());
164        self
165    }
166}
167
168/// Builder for SQLx configuration.
169pub struct SqlxConfigBuilder {
170    config: SqlxConfig,
171}
172
173impl SqlxConfigBuilder {
174    /// Create a new builder with a database URL.
175    pub fn new(url: impl Into<String>) -> Self {
176        let url = url.into();
177        let backend = DatabaseBackend::from_url(&url).unwrap_or(DatabaseBackend::Postgres);
178
179        Self {
180            config: SqlxConfig {
181                url,
182                backend,
183                ..Default::default()
184            },
185        }
186    }
187
188    /// Set max connections.
189    pub fn max_connections(mut self, max: u32) -> Self {
190        self.config.max_connections = max;
191        self
192    }
193
194    /// Set min connections.
195    pub fn min_connections(mut self, min: u32) -> Self {
196        self.config.min_connections = min;
197        self
198    }
199
200    /// Set connection timeout.
201    pub fn connect_timeout(mut self, timeout: Duration) -> Self {
202        self.config.connect_timeout = timeout;
203        self
204    }
205
206    /// Set idle timeout.
207    pub fn idle_timeout(mut self, timeout: Duration) -> Self {
208        self.config.idle_timeout = Some(timeout);
209        self
210    }
211
212    /// Disable idle timeout.
213    pub fn no_idle_timeout(mut self) -> Self {
214        self.config.idle_timeout = None;
215        self
216    }
217
218    /// Set max lifetime.
219    pub fn max_lifetime(mut self, lifetime: Duration) -> Self {
220        self.config.max_lifetime = Some(lifetime);
221        self
222    }
223
224    /// Disable max lifetime.
225    pub fn no_max_lifetime(mut self) -> Self {
226        self.config.max_lifetime = None;
227        self
228    }
229
230    /// Set statement cache capacity.
231    pub fn statement_cache(mut self, capacity: usize) -> Self {
232        self.config.statement_cache_capacity = capacity;
233        self
234    }
235
236    /// Set SSL mode.
237    pub fn ssl_mode(mut self, mode: SslMode) -> Self {
238        self.config.ssl_mode = mode;
239        self
240    }
241
242    /// Require SSL.
243    pub fn require_ssl(mut self) -> Self {
244        self.config.ssl_mode = SslMode::Require;
245        self
246    }
247
248    /// Set application name.
249    pub fn application_name(mut self, name: impl Into<String>) -> Self {
250        self.config.application_name = Some(name.into());
251        self
252    }
253
254    /// Build the configuration.
255    pub fn build(self) -> SqlxConfig {
256        self.config
257    }
258}
259
260#[cfg(test)]
261mod tests {
262    use super::*;
263
264    #[test]
265    fn test_backend_detection() {
266        assert_eq!(
267            DatabaseBackend::from_url("postgres://localhost/db").unwrap(),
268            DatabaseBackend::Postgres
269        );
270        assert_eq!(
271            DatabaseBackend::from_url("postgresql://localhost/db").unwrap(),
272            DatabaseBackend::Postgres
273        );
274        assert_eq!(
275            DatabaseBackend::from_url("mysql://localhost/db").unwrap(),
276            DatabaseBackend::MySql
277        );
278        assert_eq!(
279            DatabaseBackend::from_url("sqlite://./test.db").unwrap(),
280            DatabaseBackend::Sqlite
281        );
282        assert_eq!(
283            DatabaseBackend::from_url("file:./test.db").unwrap(),
284            DatabaseBackend::Sqlite
285        );
286
287        assert!(DatabaseBackend::from_url("unknown://localhost").is_err());
288    }
289
290    #[test]
291    fn test_config_from_url() {
292        let config = SqlxConfig::from_url("postgres://user:pass@localhost:5432/mydb").unwrap();
293        assert_eq!(config.backend, DatabaseBackend::Postgres);
294        assert_eq!(config.max_connections, 10);
295    }
296
297    #[test]
298    fn test_config_builder() {
299        let config = SqlxConfig::builder("postgres://localhost/db")
300            .max_connections(20)
301            .min_connections(5)
302            .connect_timeout(Duration::from_secs(10))
303            .require_ssl()
304            .application_name("prax-app")
305            .build();
306
307        assert_eq!(config.max_connections, 20);
308        assert_eq!(config.min_connections, 5);
309        assert_eq!(config.connect_timeout, Duration::from_secs(10));
310        assert_eq!(config.ssl_mode, SslMode::Require);
311        assert_eq!(config.application_name, Some("prax-app".to_string()));
312    }
313
314    #[test]
315    fn test_config_with_methods() {
316        let config = SqlxConfig::from_url("mysql://localhost/db")
317            .unwrap()
318            .with_max_connections(50)
319            .with_statement_cache(200);
320
321        assert_eq!(config.max_connections, 50);
322        assert_eq!(config.statement_cache_capacity, 200);
323    }
324}