1use crate::error::{SqlxError, SqlxResult};
4use std::time::Duration;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum DatabaseBackend {
9 Postgres,
11 MySql,
13 Sqlite,
15}
16
17impl DatabaseBackend {
18 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#[derive(Debug, Clone)]
36pub struct SqlxConfig {
37 pub url: String,
39 pub backend: DatabaseBackend,
41 pub max_connections: u32,
43 pub min_connections: u32,
45 pub connect_timeout: Duration,
47 pub idle_timeout: Option<Duration>,
49 pub max_lifetime: Option<Duration>,
51 pub statement_cache_capacity: usize,
54 pub ssl_mode: SslMode,
57 pub application_name: Option<String>,
60}
61
62#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
64pub enum SslMode {
65 Disable,
67 #[default]
69 Prefer,
70 Require,
72 VerifyCa,
74 VerifyFull,
76}
77
78impl Default for SqlxConfig {
79 fn default() -> Self {
80 Self {
81 url: String::new(),
82 backend: DatabaseBackend::Postgres,
83 max_connections: 10,
84 min_connections: 1,
85 connect_timeout: Duration::from_secs(30),
86 idle_timeout: Some(Duration::from_secs(600)),
87 max_lifetime: Some(Duration::from_secs(1800)),
88 statement_cache_capacity: 100,
89 ssl_mode: SslMode::default(),
90 application_name: None,
91 }
92 }
93}
94
95impl SqlxConfig {
96 pub fn from_url(url: impl Into<String>) -> SqlxResult<Self> {
107 let url = url.into();
108 let backend = DatabaseBackend::from_url(&url)?;
109
110 Ok(Self {
111 url,
112 backend,
113 ..Default::default()
114 })
115 }
116
117 pub fn builder(url: impl Into<String>) -> SqlxConfigBuilder {
119 SqlxConfigBuilder::new(url)
120 }
121
122 pub fn with_max_connections(mut self, max: u32) -> Self {
124 self.max_connections = max;
125 self
126 }
127
128 pub fn with_min_connections(mut self, min: u32) -> Self {
130 self.min_connections = min;
131 self
132 }
133
134 pub fn with_connect_timeout(mut self, timeout: Duration) -> Self {
136 self.connect_timeout = timeout;
137 self
138 }
139
140 pub fn with_idle_timeout(mut self, timeout: Duration) -> Self {
142 self.idle_timeout = Some(timeout);
143 self
144 }
145
146 pub fn with_max_lifetime(mut self, lifetime: Duration) -> Self {
148 self.max_lifetime = Some(lifetime);
149 self
150 }
151
152 pub fn with_statement_cache(mut self, capacity: usize) -> Self {
154 self.statement_cache_capacity = capacity;
155 self
156 }
157
158 pub fn with_ssl_mode(mut self, mode: SslMode) -> Self {
160 self.ssl_mode = mode;
161 self
162 }
163
164 pub fn with_application_name(mut self, name: impl Into<String>) -> Self {
166 self.application_name = Some(name.into());
167 self
168 }
169}
170
171pub struct SqlxConfigBuilder {
173 config: SqlxConfig,
174}
175
176impl SqlxConfigBuilder {
177 pub fn new(url: impl Into<String>) -> Self {
179 let url = url.into();
180 let backend = DatabaseBackend::from_url(&url).unwrap_or(DatabaseBackend::Postgres);
181
182 Self {
183 config: SqlxConfig {
184 url,
185 backend,
186 ..Default::default()
187 },
188 }
189 }
190
191 pub fn max_connections(mut self, max: u32) -> Self {
193 self.config.max_connections = max;
194 self
195 }
196
197 pub fn min_connections(mut self, min: u32) -> Self {
199 self.config.min_connections = min;
200 self
201 }
202
203 pub fn connect_timeout(mut self, timeout: Duration) -> Self {
205 self.config.connect_timeout = timeout;
206 self
207 }
208
209 pub fn idle_timeout(mut self, timeout: Duration) -> Self {
211 self.config.idle_timeout = Some(timeout);
212 self
213 }
214
215 pub fn no_idle_timeout(mut self) -> Self {
217 self.config.idle_timeout = None;
218 self
219 }
220
221 pub fn max_lifetime(mut self, lifetime: Duration) -> Self {
223 self.config.max_lifetime = Some(lifetime);
224 self
225 }
226
227 pub fn no_max_lifetime(mut self) -> Self {
229 self.config.max_lifetime = None;
230 self
231 }
232
233 pub fn statement_cache(mut self, capacity: usize) -> Self {
235 self.config.statement_cache_capacity = capacity;
236 self
237 }
238
239 pub fn ssl_mode(mut self, mode: SslMode) -> Self {
241 self.config.ssl_mode = mode;
242 self
243 }
244
245 pub fn require_ssl(mut self) -> Self {
247 self.config.ssl_mode = SslMode::Require;
248 self
249 }
250
251 pub fn application_name(mut self, name: impl Into<String>) -> Self {
253 self.config.application_name = Some(name.into());
254 self
255 }
256
257 pub fn build(self) -> SqlxConfig {
259 self.config
260 }
261}
262
263#[cfg(test)]
264mod tests {
265 use super::*;
266
267 #[test]
268 fn test_backend_detection() {
269 assert_eq!(
270 DatabaseBackend::from_url("postgres://localhost/db").unwrap(),
271 DatabaseBackend::Postgres
272 );
273 assert_eq!(
274 DatabaseBackend::from_url("postgresql://localhost/db").unwrap(),
275 DatabaseBackend::Postgres
276 );
277 assert_eq!(
278 DatabaseBackend::from_url("mysql://localhost/db").unwrap(),
279 DatabaseBackend::MySql
280 );
281 assert_eq!(
282 DatabaseBackend::from_url("sqlite://./test.db").unwrap(),
283 DatabaseBackend::Sqlite
284 );
285 assert_eq!(
286 DatabaseBackend::from_url("file:./test.db").unwrap(),
287 DatabaseBackend::Sqlite
288 );
289
290 assert!(DatabaseBackend::from_url("unknown://localhost").is_err());
291 }
292
293 #[test]
294 fn test_config_from_url() {
295 let config = SqlxConfig::from_url("postgres://user:pass@localhost:5432/mydb").unwrap();
296 assert_eq!(config.backend, DatabaseBackend::Postgres);
297 assert_eq!(config.max_connections, 10);
298 }
299
300 #[test]
301 fn test_config_builder() {
302 let config = SqlxConfig::builder("postgres://localhost/db")
303 .max_connections(20)
304 .min_connections(5)
305 .connect_timeout(Duration::from_secs(10))
306 .require_ssl()
307 .application_name("prax-app")
308 .build();
309
310 assert_eq!(config.max_connections, 20);
311 assert_eq!(config.min_connections, 5);
312 assert_eq!(config.connect_timeout, Duration::from_secs(10));
313 assert_eq!(config.ssl_mode, SslMode::Require);
314 assert_eq!(config.application_name, Some("prax-app".to_string()));
315 }
316
317 #[test]
318 fn test_config_with_methods() {
319 let config = SqlxConfig::from_url("mysql://localhost/db")
320 .unwrap()
321 .with_max_connections(50)
322 .with_statement_cache(200);
323
324 assert_eq!(config.max_connections, 50);
325 assert_eq!(config.statement_cache_capacity, 200);
326 }
327}