Skip to main content

br_db/
config.rs

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    /// 设置新地连接配置
102    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    /// 设置当前运行配置
107    pub fn set_default(&mut self, name: &str) {
108        self.default = name.to_string();
109    }
110}
111/// 数据库模式
112#[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/// 数据库连接池配置
144#[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/// 数据库连接
181#[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}