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,
53 pub ssl_mode: SslMode,
55 pub application_name: Option<String>,
57}
58
59#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
61pub enum SslMode {
62 Disable,
64 #[default]
66 Prefer,
67 Require,
69 VerifyCa,
71 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 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 pub fn builder(url: impl Into<String>) -> SqlxConfigBuilder {
116 SqlxConfigBuilder::new(url)
117 }
118
119 pub fn with_max_connections(mut self, max: u32) -> Self {
121 self.max_connections = max;
122 self
123 }
124
125 pub fn with_min_connections(mut self, min: u32) -> Self {
127 self.min_connections = min;
128 self
129 }
130
131 pub fn with_connect_timeout(mut self, timeout: Duration) -> Self {
133 self.connect_timeout = timeout;
134 self
135 }
136
137 pub fn with_idle_timeout(mut self, timeout: Duration) -> Self {
139 self.idle_timeout = Some(timeout);
140 self
141 }
142
143 pub fn with_max_lifetime(mut self, lifetime: Duration) -> Self {
145 self.max_lifetime = Some(lifetime);
146 self
147 }
148
149 pub fn with_statement_cache(mut self, capacity: usize) -> Self {
151 self.statement_cache_capacity = capacity;
152 self
153 }
154
155 pub fn with_ssl_mode(mut self, mode: SslMode) -> Self {
157 self.ssl_mode = mode;
158 self
159 }
160
161 pub fn with_application_name(mut self, name: impl Into<String>) -> Self {
163 self.application_name = Some(name.into());
164 self
165 }
166}
167
168pub struct SqlxConfigBuilder {
170 config: SqlxConfig,
171}
172
173impl SqlxConfigBuilder {
174 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 pub fn max_connections(mut self, max: u32) -> Self {
190 self.config.max_connections = max;
191 self
192 }
193
194 pub fn min_connections(mut self, min: u32) -> Self {
196 self.config.min_connections = min;
197 self
198 }
199
200 pub fn connect_timeout(mut self, timeout: Duration) -> Self {
202 self.config.connect_timeout = timeout;
203 self
204 }
205
206 pub fn idle_timeout(mut self, timeout: Duration) -> Self {
208 self.config.idle_timeout = Some(timeout);
209 self
210 }
211
212 pub fn no_idle_timeout(mut self) -> Self {
214 self.config.idle_timeout = None;
215 self
216 }
217
218 pub fn max_lifetime(mut self, lifetime: Duration) -> Self {
220 self.config.max_lifetime = Some(lifetime);
221 self
222 }
223
224 pub fn no_max_lifetime(mut self) -> Self {
226 self.config.max_lifetime = None;
227 self
228 }
229
230 pub fn statement_cache(mut self, capacity: usize) -> Self {
232 self.config.statement_cache_capacity = capacity;
233 self
234 }
235
236 pub fn ssl_mode(mut self, mode: SslMode) -> Self {
238 self.config.ssl_mode = mode;
239 self
240 }
241
242 pub fn require_ssl(mut self) -> Self {
244 self.config.ssl_mode = SslMode::Require;
245 self
246 }
247
248 pub fn application_name(mut self, name: impl Into<String>) -> Self {
250 self.config.application_name = Some(name.into());
251 self
252 }
253
254 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}