1use json::{object, JsonValue};
2use serde::{Deserialize, Serialize};
3use std::collections::BTreeMap;
4use std::fs;
5use std::path::PathBuf;
6
7#[derive(Clone, Debug, Deserialize, Serialize)]
8pub struct Config {
9 pub default: String,
10 pub connections: BTreeMap<String, Connection>,
11}
12
13impl Default for Config {
14 fn default() -> Self {
15 Self::new()
16 }
17}
18
19impl Config {
20 pub fn new() -> Config {
21 let mut connections = BTreeMap::new();
22 connections.insert("sqlite".to_string(), Connection::new("sqlite"));
23 connections.insert("mysql".to_string(), Connection::new("mysql"));
24 connections.insert("pgsql".to_string(), Connection::new("pgsql"));
25 connections.insert("mssql".to_string(), Connection::new("mssql"));
26 Self {
27 default: "sqlite".to_string(),
28 connections,
29 }
30 }
31 pub fn from(data: JsonValue) -> Config {
32 let default = data["default"].to_string();
33 let mut connections = BTreeMap::new();
34 for (key, value) in data["connections"].entries() {
35 let connection = Connection::from(value.clone()).clone();
36 connections.insert(key.to_string(), connection.clone());
37 }
38
39 Self {
40 default,
41 connections,
42 }
43 }
44 pub fn create(config_file: PathBuf, pkg_name: bool) -> Config {
45 #[derive(Clone, Debug, Deserialize, Serialize)]
46 pub struct ConfigPkg {
47 pub br_db: Config,
48 }
49 impl ConfigPkg {
50 pub fn new() -> ConfigPkg {
51 let mut connections = BTreeMap::new();
52 connections.insert("sqlite".to_string(), Connection::new("sqlite"));
53 connections.insert("mysql".to_string(), Connection::new("mysql"));
54 connections.insert("pgsql".to_string(), Connection::new("pgsql"));
55 connections.insert("mssql".to_string(), Connection::new("mssql"));
56 Self {
57 br_db: Config {
58 default: "sqlite".to_string(),
59 connections,
60 },
61 }
62 }
63 }
64 match fs::read_to_string(config_file.clone()) {
65 Ok(e) => {
66 if pkg_name {
67 toml::from_str::<ConfigPkg>(&e)
68 .map(|c| c.br_db)
69 .unwrap_or_else(|_| Config::new())
70 } else {
71 toml::from_str::<Config>(&e).unwrap_or_else(|_| Config::new())
72 }
73 }
74 Err(_) => {
75 if pkg_name {
76 let data = ConfigPkg::new();
77 if let Some(parent) = config_file.parent() {
78 let _ = fs::create_dir_all(parent);
79 }
80 if let Ok(toml) = toml::to_string(&data) {
81 if let Some(path) = config_file.to_str() {
82 let _ = fs::write(path, toml);
83 }
84 }
85 data.br_db
86 } else {
87 let data = Config::new();
88 if let Some(parent) = config_file.parent() {
89 let _ = fs::create_dir_all(parent);
90 }
91 if let Ok(toml) = toml::to_string(&data) {
92 if let Some(path) = config_file.to_str() {
93 let _ = fs::write(path, toml);
94 }
95 }
96 data
97 }
98 }
99 }
100 }
101 pub fn set_connection(&mut self, name: &str, connection: JsonValue) {
103 let connection = Connection::from(connection);
104 self.connections.insert(name.to_string(), connection);
105 }
106 pub fn set_default(&mut self, name: &str) {
108 self.default = name.to_string();
109 }
110}
111#[derive(Clone, Debug, Deserialize, Serialize)]
113pub enum Mode {
114 Mysql,
115 Mssql,
116 Sqlite,
117 Pgsql,
118 None,
119}
120
121impl Mode {
122 pub fn str(&mut self) -> String {
123 match self {
124 Mode::Mysql => "mysql",
125 Mode::Sqlite => "sqlite",
126 Mode::Mssql => "mssql",
127 Mode::Pgsql => "pgsql",
128 Mode::None => "",
129 }
130 .to_string()
131 }
132 pub fn from(name: &str) -> Self {
133 match name.to_lowercase().as_str() {
134 "mysql" => Mode::Mysql,
135 "sqlite" => Mode::Sqlite,
136 "mssql" => Mode::Mssql,
137 "pgsql" => Mode::Pgsql,
138 _ => Mode::None,
139 }
140 }
141}
142
143#[derive(Clone, Debug, Deserialize, Serialize)]
145pub struct PoolConfig {
146 pub min_connections: u32,
147 pub max_connections: u32,
148 pub connect_timeout_secs: u64,
149 pub read_timeout_secs: u64,
150 pub write_timeout_secs: u64,
151 pub keepalive_ms: u64,
152}
153
154impl Default for PoolConfig {
155 fn default() -> Self {
156 Self {
157 min_connections: 0,
158 max_connections: 400,
159 connect_timeout_secs: 5,
160 read_timeout_secs: 15,
161 write_timeout_secs: 20,
162 keepalive_ms: 5000,
163 }
164 }
165}
166
167impl PoolConfig {
168 pub fn from(data: &JsonValue) -> Self {
169 Self {
170 min_connections: data["min_connections"].as_u32().unwrap_or(0),
171 max_connections: data["max_connections"].as_u32().unwrap_or(400),
172 connect_timeout_secs: data["connect_timeout_secs"].as_u64().unwrap_or(5),
173 read_timeout_secs: data["read_timeout_secs"].as_u64().unwrap_or(15),
174 write_timeout_secs: data["write_timeout_secs"].as_u64().unwrap_or(20),
175 keepalive_ms: data["keepalive_ms"].as_u64().unwrap_or(5000),
176 }
177 }
178}
179
180#[derive(Clone, Debug, Deserialize, Serialize)]
182pub struct Connection {
183 pub mode: Mode,
184 pub hostname: String,
185 pub hostport: String,
186 pub database: String,
187 pub username: String,
188 pub userpass: String,
189 pub params: Vec<String>,
190 pub charset: Charset,
191 pub prefix: String,
192 pub debug: bool,
193 #[serde(default)]
194 pub pool: PoolConfig,
195}
196
197impl Default for Connection {
198 fn default() -> Self {
199 Self::new("sqlite")
200 }
201}
202
203impl Connection {
204 pub fn new(mode: &str) -> Connection {
205 let mut that = Self {
206 mode: Mode::from(mode),
207 hostname: "".to_string(),
208 hostport: "".to_string(),
209 database: "".to_string(),
210 username: "".to_string(),
211 userpass: "".to_string(),
212 params: vec![],
213 charset: Charset::Utf8mb4,
214 prefix: "".to_string(),
215 debug: false,
216 pool: PoolConfig::default(),
217 };
218 match Mode::from(mode) {
219 Mode::Mysql => {
220 that.hostname = "127.0.0.1".to_string();
221 that.hostport = "3306".to_string();
222 that.database = "test".to_string();
223 that.username = "test".to_string();
224 that.userpass = "test".to_string();
225 }
226 Mode::Mssql => {}
227 Mode::Sqlite => {
228 that.database = "db/app.db".to_string();
229 }
230 Mode::Pgsql => {
231 that.hostname = "127.0.0.1".to_string();
232 that.hostport = "5432".to_string();
233 that.username = "test".to_string();
234 that.userpass = "test".to_string();
235 }
236 Mode::None => {}
237 }
238
239 that
240 }
241 pub fn json(&mut self) -> JsonValue {
242 object! {
243 mode: self.mode.str(),
244 hostname: self.hostname.clone(),
245 hostport: self.hostport.clone(),
246 database: self.database.clone(),
247 username: self.username.clone(),
248 userpass:self.userpass.clone(),
249 params: self.params.clone(),
250 charset: self.charset.str(),
251 prefix: self.prefix.clone(),
252 debug: self.debug
253 }
254 }
255 pub fn from(data: JsonValue) -> Connection {
256 Self {
257 mode: Mode::from(data["mode"].as_str().unwrap_or("none")),
258 hostname: data["hostname"].to_string(),
259 hostport: data["hostport"].to_string(),
260 database: data["database"].to_string(),
261 username: data["username"].to_string(),
262 userpass: data["userpass"].to_string(),
263 params: data["params"].members().map(|x| x.to_string()).collect(),
264 charset: Charset::from(data["charset"].as_str().unwrap_or("utf8mb4")),
265 prefix: data["prefix"].as_str().unwrap_or("").to_string(),
266 debug: data["debug"].to_string().parse::<bool>().unwrap_or(false),
267 pool: PoolConfig::from(&data["pool"]),
268 }
269 }
270 pub fn get_dsn(self) -> String {
271 match self.mode {
272 Mode::Mysql => {
273 format!(
274 "mysql://{}:{}@{}:{}/{}",
275 self.username, self.userpass, self.hostname, self.hostport, self.database
276 )
277 }
278 Mode::Sqlite => {
279 let db_path = self.database.as_str();
280 let path_buf = PathBuf::from(db_path);
281 if !path_buf.is_file() {
282 if let Some(file_name) = path_buf.file_name() {
283 if let Some(file_name_str) = file_name.to_str() {
284 let dir_path = db_path.trim_end_matches(file_name_str);
285 let _ = fs::create_dir_all(dir_path);
286 }
287 }
288 }
289 path_buf.to_str().unwrap_or(db_path).to_string()
290 }
291 Mode::Mssql => format!(
292 "sqlsrv://{}:{}@{}:{}/{}",
293 self.username, self.userpass, self.hostname, self.hostport, self.database
294 ),
295 Mode::Pgsql => format!(
296 "host={} user={} password={} dbname={}",
297 self.hostname, self.username, self.userpass, self.database
298 ),
299 Mode::None => "".to_string(),
300 }
301 }
302}
303
304#[derive(Clone, Debug, Deserialize, Serialize)]
305pub enum Charset {
306 Utf8mb4,
307 Utf8,
308 None,
309}
310
311impl Charset {
312 pub fn from(str: &str) -> Charset {
313 match str.to_lowercase().as_str() {
314 "utf8" => Charset::Utf8,
315 "utf8mb4" => Charset::Utf8mb4,
316 _ => Charset::None,
317 }
318 }
319 pub fn str(&self) -> String {
320 match self {
321 Charset::Utf8 => "utf8",
322 Charset::Utf8mb4 => "utf8mb4",
323 Charset::None => "",
324 }
325 .to_string()
326 }
327}
328
329#[cfg(test)]
330mod tests {
331 use super::*;
332 use json::object;
333
334 #[test]
335 fn config_new_has_four_connections() {
336 let cfg = Config::new();
337 assert_eq!(cfg.connections.len(), 4);
338 assert!(cfg.connections.contains_key("sqlite"));
339 assert!(cfg.connections.contains_key("mysql"));
340 assert!(cfg.connections.contains_key("pgsql"));
341 assert!(cfg.connections.contains_key("mssql"));
342 }
343
344 #[test]
345 fn config_new_default_is_sqlite() {
346 let cfg = Config::new();
347 assert_eq!(cfg.default, "sqlite");
348 }
349
350 #[test]
351 fn config_default_equals_new() {
352 let a = Config::new();
353 let b = Config::default();
354 assert_eq!(a.default, b.default);
355 assert_eq!(a.connections.len(), b.connections.len());
356 for key in a.connections.keys() {
357 assert!(b.connections.contains_key(key));
358 }
359 }
360
361 #[test]
362 fn config_from_valid_json() {
363 let data = object! {
364 default: "mysql",
365 connections: {
366 myconn: {
367 mode: "mysql",
368 hostname: "10.0.0.1",
369 hostport: "3307",
370 database: "mydb",
371 username: "admin",
372 userpass: "secret",
373 params: [],
374 charset: "utf8",
375 prefix: "app_",
376 debug: true
377 }
378 }
379 };
380 let cfg = Config::from(data);
381 assert_eq!(cfg.default, "mysql");
382 assert_eq!(cfg.connections.len(), 1);
383 let conn = cfg.connections.get("myconn").expect("myconn should exist");
384 assert_eq!(conn.hostname, "10.0.0.1");
385 assert_eq!(conn.hostport, "3307");
386 assert_eq!(conn.database, "mydb");
387 assert_eq!(conn.username, "admin");
388 assert_eq!(conn.userpass, "secret");
389 assert_eq!(conn.prefix, "app_");
390 assert!(conn.debug);
391 }
392
393 #[test]
394 fn config_from_empty_json() {
395 let data = object! {};
396 let cfg = Config::from(data);
397 assert_eq!(cfg.default, "null");
398 assert_eq!(cfg.connections.len(), 0);
399 }
400
401 #[test]
402 fn config_from_missing_connections() {
403 let data = object! { default: "pgsql" };
404 let cfg = Config::from(data);
405 assert_eq!(cfg.default, "pgsql");
406 assert_eq!(cfg.connections.len(), 0);
407 }
408
409 #[test]
410 fn config_create_nonexistent_file_no_pkg_name() {
411 let dir = std::env::temp_dir().join("br_db_test_create_no_pkg");
412 let _ = fs::remove_dir_all(&dir);
413 let file = dir.join("config.toml");
414
415 let cfg = Config::create(file.clone(), false);
416 assert_eq!(cfg.default, "sqlite");
417 assert_eq!(cfg.connections.len(), 4);
418 assert!(file.is_file());
419
420 let cfg2 = Config::create(file.clone(), false);
421 assert_eq!(cfg2.default, "sqlite");
422 assert_eq!(cfg2.connections.len(), 4);
423
424 let _ = fs::remove_dir_all(&dir);
425 }
426
427 #[test]
428 fn config_create_nonexistent_file_with_pkg_name() {
429 let dir = std::env::temp_dir().join("br_db_test_create_pkg");
430 let _ = fs::remove_dir_all(&dir);
431 let file = dir.join("config.toml");
432
433 let cfg = Config::create(file.clone(), true);
434 assert_eq!(cfg.default, "sqlite");
435 assert_eq!(cfg.connections.len(), 4);
436 assert!(file.is_file());
437
438 let cfg2 = Config::create(file.clone(), true);
439 assert_eq!(cfg2.default, "sqlite");
440 assert_eq!(cfg2.connections.len(), 4);
441
442 let _ = fs::remove_dir_all(&dir);
443 }
444
445 #[test]
446 fn config_create_existing_file_without_pkg_name() {
447 let dir = std::env::temp_dir().join("br_db_test_create_existing");
448 let _ = fs::remove_dir_all(&dir);
449 fs::create_dir_all(&dir).expect("create temp dir");
450 let file = dir.join("config.toml");
451
452 let content = r#"
453default = "mysql"
454
455[connections.only]
456mode = "Mysql"
457hostname = "1.2.3.4"
458hostport = "3306"
459database = "custom"
460username = "u"
461userpass = "p"
462params = []
463charset = "Utf8mb4"
464prefix = ""
465debug = false
466
467[connections.only.pool]
468min_connections = 0
469max_connections = 400
470connect_timeout_secs = 5
471read_timeout_secs = 15
472write_timeout_secs = 20
473keepalive_ms = 5000
474"#;
475 fs::write(&file, content).expect("write temp config");
476
477 let cfg = Config::create(file.clone(), false);
478 assert_eq!(cfg.default, "mysql");
479 assert_eq!(cfg.connections.len(), 1);
480 assert!(cfg.connections.contains_key("only"));
481
482 let _ = fs::remove_dir_all(&dir);
483 }
484
485 #[test]
486 fn config_set_connection_adds_new() {
487 let mut cfg = Config::new();
488 let conn_json = object! {
489 mode: "pgsql",
490 hostname: "db.example.com",
491 hostport: "5433",
492 database: "prod",
493 username: "admin",
494 userpass: "pw",
495 params: [],
496 charset: "utf8",
497 prefix: "v2_",
498 debug: false
499 };
500 cfg.set_connection("production", conn_json);
501 assert_eq!(cfg.connections.len(), 5);
502 let conn = cfg
503 .connections
504 .get("production")
505 .expect("production should exist");
506 assert_eq!(conn.hostname, "db.example.com");
507 assert_eq!(conn.hostport, "5433");
508 assert_eq!(conn.prefix, "v2_");
509 }
510
511 #[test]
512 fn config_set_connection_overwrites_existing() {
513 let mut cfg = Config::new();
514 let conn_json = object! {
515 mode: "mysql",
516 hostname: "new-host",
517 hostport: "3307",
518 database: "newdb",
519 username: "newuser",
520 userpass: "newpass",
521 params: [],
522 charset: "utf8mb4",
523 prefix: "",
524 debug: true
525 };
526 cfg.set_connection("mysql", conn_json);
527 assert_eq!(cfg.connections.len(), 4);
528 let conn = cfg.connections.get("mysql").expect("mysql should exist");
529 assert_eq!(conn.hostname, "new-host");
530 assert!(conn.debug);
531 }
532
533 #[test]
534 fn config_set_default_changes_default() {
535 let mut cfg = Config::new();
536 assert_eq!(cfg.default, "sqlite");
537 cfg.set_default("mysql");
538 assert_eq!(cfg.default, "mysql");
539 cfg.set_default("pgsql");
540 assert_eq!(cfg.default, "pgsql");
541 }
542
543 #[test]
544 fn mode_str_all_variants() {
545 assert_eq!(Mode::Mysql.str(), "mysql");
546 assert_eq!(Mode::Sqlite.str(), "sqlite");
547 assert_eq!(Mode::Mssql.str(), "mssql");
548 assert_eq!(Mode::Pgsql.str(), "pgsql");
549 assert_eq!(Mode::None.str(), "");
550 }
551
552 #[test]
553 fn mode_from_all_variants() {
554 assert!(matches!(Mode::from("mysql"), Mode::Mysql));
555 assert!(matches!(Mode::from("sqlite"), Mode::Sqlite));
556 assert!(matches!(Mode::from("mssql"), Mode::Mssql));
557 assert!(matches!(Mode::from("pgsql"), Mode::Pgsql));
558 }
559
560 #[test]
561 fn mode_from_case_insensitive() {
562 assert!(matches!(Mode::from("MYSQL"), Mode::Mysql));
563 assert!(matches!(Mode::from("Sqlite"), Mode::Sqlite));
564 assert!(matches!(Mode::from("PGSQL"), Mode::Pgsql));
565 }
566
567 #[test]
568 fn mode_from_unknown_returns_none() {
569 assert!(matches!(Mode::from("oracle"), Mode::None));
570 assert!(matches!(Mode::from(""), Mode::None));
571 assert!(matches!(Mode::from("redis"), Mode::None));
572 }
573
574 #[test]
575 fn pool_config_default_values() {
576 let pc = PoolConfig::default();
577 assert_eq!(pc.min_connections, 0);
578 assert_eq!(pc.max_connections, 400);
579 assert_eq!(pc.connect_timeout_secs, 5);
580 assert_eq!(pc.read_timeout_secs, 15);
581 assert_eq!(pc.write_timeout_secs, 20);
582 assert_eq!(pc.keepalive_ms, 5000);
583 }
584
585 #[test]
586 fn pool_config_from_full_data() {
587 let data = object! {
588 min_connections: 5,
589 max_connections: 100,
590 connect_timeout_secs: 10,
591 read_timeout_secs: 30,
592 write_timeout_secs: 60,
593 keepalive_ms: 10000
594 };
595 let pc = PoolConfig::from(&data);
596 assert_eq!(pc.min_connections, 5);
597 assert_eq!(pc.max_connections, 100);
598 assert_eq!(pc.connect_timeout_secs, 10);
599 assert_eq!(pc.read_timeout_secs, 30);
600 assert_eq!(pc.write_timeout_secs, 60);
601 assert_eq!(pc.keepalive_ms, 10000);
602 }
603
604 #[test]
605 fn pool_config_from_partial_data() {
606 let data = object! {
607 max_connections: 50
608 };
609 let pc = PoolConfig::from(&data);
610 assert_eq!(pc.min_connections, 0);
611 assert_eq!(pc.max_connections, 50);
612 assert_eq!(pc.connect_timeout_secs, 5);
613 assert_eq!(pc.read_timeout_secs, 15);
614 assert_eq!(pc.write_timeout_secs, 20);
615 assert_eq!(pc.keepalive_ms, 5000);
616 }
617
618 #[test]
619 fn pool_config_from_empty_data() {
620 let data = object! {};
621 let pc = PoolConfig::from(&data);
622 assert_eq!(pc.min_connections, 0);
623 assert_eq!(pc.max_connections, 400);
624 assert_eq!(pc.connect_timeout_secs, 5);
625 assert_eq!(pc.read_timeout_secs, 15);
626 assert_eq!(pc.write_timeout_secs, 20);
627 assert_eq!(pc.keepalive_ms, 5000);
628 }
629
630 #[test]
631 fn connection_new_mysql() {
632 let conn = Connection::new("mysql");
633 assert!(matches!(conn.mode, Mode::Mysql));
634 assert_eq!(conn.hostname, "127.0.0.1");
635 assert_eq!(conn.hostport, "3306");
636 assert_eq!(conn.database, "test");
637 assert_eq!(conn.username, "test");
638 assert_eq!(conn.userpass, "test");
639 assert!(!conn.debug);
640 }
641
642 #[test]
643 fn connection_new_sqlite() {
644 let conn = Connection::new("sqlite");
645 assert!(matches!(conn.mode, Mode::Sqlite));
646 assert_eq!(conn.database, "db/app.db");
647 assert_eq!(conn.hostname, "");
648 assert_eq!(conn.hostport, "");
649 }
650
651 #[test]
652 fn connection_new_pgsql() {
653 let conn = Connection::new("pgsql");
654 assert!(matches!(conn.mode, Mode::Pgsql));
655 assert_eq!(conn.hostname, "127.0.0.1");
656 assert_eq!(conn.hostport, "5432");
657 assert_eq!(conn.username, "test");
658 assert_eq!(conn.userpass, "test");
659 assert_eq!(conn.database, "");
660 }
661
662 #[test]
663 fn connection_new_mssql() {
664 let conn = Connection::new("mssql");
665 assert!(matches!(conn.mode, Mode::Mssql));
666 assert_eq!(conn.hostname, "");
667 assert_eq!(conn.hostport, "");
668 }
669
670 #[test]
671 fn connection_new_unknown() {
672 let conn = Connection::new("oracle");
673 assert!(matches!(conn.mode, Mode::None));
674 assert_eq!(conn.hostname, "");
675 assert_eq!(conn.database, "");
676 }
677
678 #[test]
679 fn connection_default_is_sqlite() {
680 let conn = Connection::default();
681 assert!(matches!(conn.mode, Mode::Sqlite));
682 assert_eq!(conn.database, "db/app.db");
683 }
684
685 #[test]
686 fn connection_from_full_json() {
687 let data = object! {
688 mode: "pgsql",
689 hostname: "pg.local",
690 hostport: "5433",
691 database: "appdb",
692 username: "pguser",
693 userpass: "pgpass",
694 params: ["sslmode=require"],
695 charset: "utf8",
696 prefix: "t_",
697 debug: true,
698 pool: {
699 min_connections: 2,
700 max_connections: 50,
701 connect_timeout_secs: 3,
702 read_timeout_secs: 10,
703 write_timeout_secs: 10,
704 keepalive_ms: 3000
705 }
706 };
707 let conn = Connection::from(data);
708 assert!(matches!(conn.mode, Mode::Pgsql));
709 assert_eq!(conn.hostname, "pg.local");
710 assert_eq!(conn.hostport, "5433");
711 assert_eq!(conn.database, "appdb");
712 assert_eq!(conn.username, "pguser");
713 assert_eq!(conn.userpass, "pgpass");
714 assert_eq!(conn.params, vec!["sslmode=require".to_string()]);
715 assert!(matches!(conn.charset, Charset::Utf8));
716 assert_eq!(conn.prefix, "t_");
717 assert!(conn.debug);
718 assert_eq!(conn.pool.min_connections, 2);
719 assert_eq!(conn.pool.max_connections, 50);
720 }
721
722 #[test]
723 fn connection_from_partial_json() {
724 let data = object! {
725 mode: "mysql",
726 hostname: "db.host"
727 };
728 let conn = Connection::from(data);
729 assert!(matches!(conn.mode, Mode::Mysql));
730 assert_eq!(conn.hostname, "db.host");
731 assert_eq!(conn.hostport, "null");
732 assert_eq!(conn.prefix, "");
733 assert!(!conn.debug);
734 }
735
736 #[test]
737 fn connection_json_roundtrip() {
738 let mut original = Connection::new("mysql");
739 original.prefix = "pre_".to_string();
740 original.debug = true;
741
742 let json_val = original.json();
743 let mut restored = Connection::from(json_val);
744
745 assert_eq!(original.hostname, restored.hostname);
746 assert_eq!(original.hostport, restored.hostport);
747 assert_eq!(original.database, restored.database);
748 assert_eq!(original.username, restored.username);
749 assert_eq!(original.userpass, restored.userpass);
750 assert_eq!(original.prefix, restored.prefix);
751 assert_eq!(original.debug, restored.debug);
752 assert_eq!(original.charset.str(), restored.charset.str());
753 assert_eq!(original.mode.str(), restored.mode.str());
754 }
755
756 #[test]
757 fn connection_get_dsn_mysql() {
758 let conn = Connection::new("mysql");
759 let dsn = conn.get_dsn();
760 assert_eq!(dsn, "mysql://test:test@127.0.0.1:3306/test");
761 }
762
763 #[test]
764 fn connection_get_dsn_sqlite() {
765 let mut conn = Connection::new("sqlite");
766 conn.database = "/tmp/br_db_test_dsn.db".to_string();
767 let dsn = conn.get_dsn();
768 assert_eq!(dsn, "/tmp/br_db_test_dsn.db");
769 }
770
771 #[test]
772 fn connection_get_dsn_mssql() {
773 let mut conn = Connection::new("mssql");
774 conn.hostname = "mssql.local".to_string();
775 conn.hostport = "1433".to_string();
776 conn.username = "sa".to_string();
777 conn.userpass = "pass".to_string();
778 conn.database = "master".to_string();
779 let dsn = conn.get_dsn();
780 assert_eq!(dsn, "sqlsrv://sa:pass@mssql.local:1433/master");
781 }
782
783 #[test]
784 fn connection_get_dsn_pgsql() {
785 let mut conn = Connection::new("pgsql");
786 conn.database = "mydb".to_string();
787 let dsn = conn.get_dsn();
788 assert_eq!(dsn, "host=127.0.0.1 user=test password=test dbname=mydb");
789 }
790
791 #[test]
792 fn connection_get_dsn_none() {
793 let conn = Connection::new("unknown");
794 let dsn = conn.get_dsn();
795 assert_eq!(dsn, "");
796 }
797
798 #[test]
799 fn charset_from_utf8() {
800 assert!(matches!(Charset::from("utf8"), Charset::Utf8));
801 }
802
803 #[test]
804 fn charset_from_utf8mb4() {
805 assert!(matches!(Charset::from("utf8mb4"), Charset::Utf8mb4));
806 }
807
808 #[test]
809 fn charset_from_case_insensitive() {
810 assert!(matches!(Charset::from("UTF8"), Charset::Utf8));
811 assert!(matches!(Charset::from("UTF8MB4"), Charset::Utf8mb4));
812 }
813
814 #[test]
815 fn charset_from_unknown() {
816 assert!(matches!(Charset::from("latin1"), Charset::None));
817 assert!(matches!(Charset::from(""), Charset::None));
818 }
819
820 #[test]
821 fn charset_str_all_variants() {
822 assert_eq!(Charset::Utf8.str(), "utf8");
823 assert_eq!(Charset::Utf8mb4.str(), "utf8mb4");
824 assert_eq!(Charset::None.str(), "");
825 }
826}