1use std::borrow::Cow;
68use std::collections::BTreeMap;
69use std::fmt::Display;
70use std::num::ParseIntError;
71use std::str::FromStr;
72use std::string::FromUtf8Error;
73
74use itertools::Itertools;
75#[cfg(feature = "pest")]
76use pest::Parser;
77#[cfg(feature = "pest")]
78use pest_derive::Parser;
79use regex::Regex;
80use thiserror::Error;
81use urlencoding::encode;
82
83impl Dsn {
84 pub fn from_regex(input: &str) -> Result<Self, DsnError> {
85 lazy_static::lazy_static! {
86 static ref RE: Regex = Regex::new(r"(?x)
87 (?P<driver>[\w.-]+)(\+(?P<protocol>[^@/?\#]+))?: # abc
88 (
89 # url-like dsn
90 //((?P<username>[\w\s\-_%.]+)?(:(?P<password>[^@/?\#]+))?@)? # for authorization
91 (((?P<protocol2>[\w\s.-]+)\()?
92 (?P<addr>[\w\-_%.:]*(:\d{0,5})?(,[\w\-:_.]*(:\d{0,5})?)*)? # for addresses
93 \)?)?
94 (/(?P<subject>[\w\s%$@.,/-]+)?)? # for subject
95 | # or
96 # path-like dsn
97 (?P<path>([\\/.~]$|/\s*\w+[\w\s %$@*:.\\\-/ _\(\)\[\]{}()【】{}]*|[\.~\w\s]?[\w\s %$@*:.\\\-/ _\(\)\[\]{}()【】{}]+))
98 ) # abc
99 (\?(?P<params>(?s:.)*))?").unwrap();
100 }
101
102 let cap = RE
103 .captures(input)
104 .ok_or_else(|| DsnError::InvalidConnection(input.to_string()))?;
105
106 let driver = cap.name("driver").unwrap().as_str().to_string();
107 let protocol = cap.name("protocol").map(|m| m.as_str().to_string());
108 let protocol2 = cap.name("protocol2").map(|m| m.as_str().to_string());
109 let protocol = match (protocol, protocol2) {
110 (Some(_), Some(_)) => Err(DsnError::InvalidProtocol("".to_string()))?,
111 (Some(p), None) | (None, Some(p)) => Some(p),
112 _ => None,
113 };
114 let username = cap
115 .name("username")
116 .map(|m| {
117 let s = m.as_str();
118 urlencoding::decode(s)
119 .map(|s| s.to_string())
120 .map_err(|err| DsnError::InvalidPassword(s.to_string(), err))
121 })
122 .transpose()?;
123 let password = cap
124 .name("password")
125 .map(|m| {
126 let s = m.as_str();
127 urlencoding::decode(s)
128 .map(|s| s.to_string())
129 .map_err(|err| DsnError::InvalidPassword(s.to_string(), err))
130 })
131 .transpose()?;
132 let subject = cap.name("subject").map(|m| m.as_str().to_string());
133 let path = cap.name("path").map(|m| m.as_str().to_string());
134 let addresses = if let Some(addr) = cap.name("addr") {
135 let addr = addr.as_str();
136 if addr.is_empty() {
137 vec![]
138 } else {
139 let mut addrs = Vec::new();
140
141 for s in addr.split(',') {
142 if s.is_empty() {
143 continue;
144 }
145 if let Some((host, port)) = s.split_once(':') {
146 let port = if port.is_empty() {
147 0
148 } else {
149 port.parse::<u16>().map_err(|err| {
150 DsnError::InvalidAddresses(s.to_string(), err.to_string())
151 })?
152 };
153 addrs.push(Address::new(host, port));
154 } else if s.contains('%') {
155 addrs.push(Address::from_path(urlencoding::decode(s).unwrap()));
156 } else {
157 addrs.push(Address::from_host(s));
158 }
159 }
160 addrs
161 }
162 } else {
163 vec![]
164 };
165 let mut params = BTreeMap::new();
166 if let Some(p) = cap.name("params") {
167 for p in p.as_str().split_terminator('&') {
168 if p.contains('=') {
169 if let Some((k, v)) = p.split_once('=') {
170 let k = urlencoding::decode(k)?;
171 let v = urlencoding::decode(v)?;
172 params.insert(k.to_string(), v.to_string());
173 }
174 } else {
175 let p = urlencoding::decode(p)?;
176 params.insert(p.to_string(), "".to_string());
177 }
178 }
179 }
180 Ok(Dsn {
181 driver,
182 protocol,
183 username,
184 password,
185 addresses,
186 path,
187 subject,
188 params,
189 })
190 }
191
192 #[cfg(feature = "pest")]
193 pub fn from_pest(input: &str) -> Result<Self, DsnError> {
194 let dsn = DsnParser::parse(Rule::dsn, input)?.next().unwrap();
195
196 let mut to = Dsn::default();
197 for pair in dsn.into_inner() {
198 match pair.as_rule() {
199 Rule::scheme => {
200 for inner in pair.into_inner() {
201 match inner.as_rule() {
202 Rule::driver => to.driver = inner.as_str().to_string(),
203 Rule::protocol => to.protocol = Some(inner.as_str().to_string()),
204 _ => unreachable!(),
205 }
206 }
207 }
208 Rule::SCHEME_IDENT => (),
209 Rule::username_with_password => {
210 for inner in pair.into_inner() {
211 match inner.as_rule() {
212 Rule::username => to.username = Some(inner.as_str().to_string()),
213 Rule::password => to.password = Some(inner.as_str().to_string()),
214 _ => unreachable!(),
215 }
216 }
217 }
218 Rule::protocol_with_addresses => {
219 for inner in pair.into_inner() {
220 match inner.as_rule() {
221 Rule::addresses => {
222 for inner in inner.into_inner() {
223 match inner.as_rule() {
224 Rule::address => {
225 let mut addr = Address::default();
226 for inner in inner.into_inner() {
227 match inner.as_rule() {
228 Rule::host => {
229 addr.host = Some(inner.as_str().to_string())
230 }
231 Rule::port => {
232 addr.port = Some(inner.as_str().parse()?)
233 }
234 Rule::path => {
235 addr.path = Some(
236 urlencoding::decode(inner.as_str())
237 .expect("UTF-8")
238 .to_string(),
239 )
240 }
241 _ => unreachable!(),
242 }
243 }
244 to.addresses.push(addr);
245 }
246 _ => unreachable!(),
247 }
248 }
249 }
250 Rule::protocol => to.protocol = Some(inner.as_str().to_string()),
251 _ => unreachable!(),
252 }
253 }
254 }
255 Rule::database => {
256 to.subject = Some(pair.as_str().to_string());
257 }
258 Rule::fragment => {
259 to.path = Some(pair.as_str().to_string());
260 }
261 Rule::path_like => {
262 to.path = Some(pair.as_str().to_string());
263 }
264 Rule::param => {
265 let (mut name, mut value) = ("".to_string(), "".to_string());
266 for inner in pair.into_inner() {
267 match inner.as_rule() {
268 Rule::name => name = inner.as_str().to_string(),
269 Rule::value => value = inner.as_str().to_string(),
270 _ => unreachable!(),
271 }
272 }
273 to.params.insert(name, value);
274 }
275 Rule::EOI => {}
276 _ => unreachable!(),
277 }
278 }
279 Ok(to)
280 }
281}
282
283#[cfg(feature = "pest")]
284#[derive(Parser)]
285#[grammar = "dsn.pest"]
286struct DsnParser;
287
288#[derive(Debug, Error)]
290pub enum DsnError {
291 #[cfg(feature = "pest")]
292 #[error("{0}")]
293 ParseErr(#[from] pest::error::Error<Rule>),
294 #[error("unable to parse port from {0}")]
295 PortErr(#[from] ParseIntError),
296 #[error("invalid driver {0}")]
297 InvalidDriver(String),
298 #[error("invalid protocol {0}")]
299 InvalidProtocol(String),
300 #[error("invalid password {0}: {1}")]
301 InvalidPassword(String, FromUtf8Error),
302 #[error("invalid connection {0}")]
303 InvalidConnection(String),
304 #[error("invalid addresses: {0}, error: {1}")]
305 InvalidAddresses(String, String),
306 #[error("requires database: {0}")]
307 RequireDatabase(String),
308 #[error("requires parameter: {0}")]
309 RequireParam(String),
310 #[error("invalid parameter for {0}: {1}")]
311 InvalidParam(String, String),
312 #[error("non utf8 character: {0}")]
313 NonUtf8Character(#[from] FromUtf8Error),
314}
315
316#[derive(Debug, Default, PartialEq, Eq, Clone)]
318pub struct Address {
319 pub host: Option<String>,
321 pub port: Option<u16>,
323 pub path: Option<String>,
325}
326
327impl Address {
328 #[inline]
330 pub fn new(host: impl Into<String>, port: u16) -> Self {
331 let host = host.into();
332 let host = if host.is_empty() { None } else { Some(host) };
333 Self {
334 host,
335 port: Some(port),
336 ..Default::default()
337 }
338 }
339 #[inline]
342 pub fn from_host(host: impl Into<String>) -> Self {
343 let host = host.into();
344 let host = if host.is_empty() { None } else { Some(host) };
345 Self {
346 host,
347 ..Default::default()
348 }
349 }
350
351 #[inline]
353 pub fn from_path(path: impl Into<String>) -> Self {
354 Self {
355 path: Some(path.into()),
356 ..Default::default()
357 }
358 }
359
360 #[inline]
361 pub fn is_empty(&self) -> bool {
362 self.host.is_none() && self.port.is_none() && self.path.is_none()
363 }
364}
365
366impl FromStr for Address {
367 type Err = DsnError;
368
369 fn from_str(s: &str) -> Result<Self, Self::Err> {
370 #[cfg(feature = "pest")]
371 {
372 let mut addr = Self::default();
373 if let Some(dsn) = DsnParser::parse(Rule::address, &s)?.next() {
374 for inner in dsn.into_inner() {
375 match inner.as_rule() {
376 Rule::host => addr.host = Some(inner.as_str().to_string()),
377 Rule::port => addr.port = Some(inner.as_str().parse()?),
378 Rule::path => {
379 addr.path = Some(
380 urlencoding::decode(inner.as_str())
381 .expect("UTF-8")
382 .to_string(),
383 )
384 }
385 _ => unreachable!(),
386 }
387 }
388 }
389 Ok(addr)
390 }
391 #[cfg(not(feature = "pest"))]
392 {
393 if s.is_empty() {
394 Ok(Self::default())
395 } else if let Some((host, port)) = s.split_once(':') {
396 Ok(Address::new(host, port.parse().unwrap()))
397 } else if s.contains('%') {
398 Ok(Address::from_path(urlencoding::decode(s).unwrap()))
399 } else {
400 Ok(Address::from_host(s))
401 }
402 }
403 }
404}
405
406impl Display for Address {
407 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
408 match (&self.host, self.port, &self.path) {
409 (Some(host), None, None) => write!(f, "{host}"),
410 (Some(host), Some(port), None) => write!(f, "{host}:{port}"),
411 (None, Some(port), None) => write!(f, ":{port}"),
412 (None, None, Some(path)) => write!(f, "{}", urlencoding::encode(path)),
413 (None, None, None) => Ok(()),
414 _ => unreachable!("path will be conflict with host/port"),
415 }
416 }
417}
418
419#[test]
420#[cfg(feature = "pest")]
421fn addr_parse() {
422 let s = "taosdata:6030";
423 let addr = Address::from_str(s).unwrap();
424 assert_eq!(addr.to_string(), s);
425 assert!(!addr.is_empty());
426
427 let s = "/var/lib/taos";
428 let addr = Address::from_str(&urlencoding::encode(s)).unwrap();
429 assert_eq!(addr.path.as_ref().unwrap(), s);
430 assert_eq!(addr.to_string(), urlencoding::encode(s));
431
432 assert_eq!(
433 Address::new("localhost", 6030).to_string(),
434 "localhost:6030"
435 );
436 assert_eq!(Address::from_host("localhost").to_string(), "localhost");
437 assert_eq!(
438 Address::from_path("/path/unix.sock").to_string(),
439 "%2Fpath%2Funix.sock"
440 );
441
442 let none = Address {
443 host: None,
444 port: None,
445 path: None,
446 };
447 assert!(none.is_empty());
448 assert_eq!(none.to_string(), "");
449}
450
451#[derive(Debug, Default, PartialEq, Eq, Clone)]
453pub struct Dsn {
454 pub driver: String,
455 pub protocol: Option<String>,
456 pub username: Option<String>,
457 pub password: Option<String>,
458 pub addresses: Vec<Address>,
459 pub path: Option<String>,
460 pub subject: Option<String>,
461 pub params: BTreeMap<String, String>,
462}
463
464impl Dsn {
465 fn is_path_like(&self) -> bool {
466 self.username.is_none()
467 && self.password.is_none()
468 && self.addresses.is_empty()
469 && self.path.is_some()
470 }
471}
472
473pub trait IntoDsn {
474 fn into_dsn(self) -> Result<Dsn, DsnError>;
475}
476
477impl IntoDsn for &str {
478 fn into_dsn(self) -> Result<Dsn, DsnError> {
479 self.parse()
480 }
481}
482
483impl IntoDsn for String {
484 fn into_dsn(self) -> Result<Dsn, DsnError> {
485 self.as_str().into_dsn()
486 }
487}
488
489impl IntoDsn for &String {
490 fn into_dsn(self) -> Result<Dsn, DsnError> {
491 self.as_str().into_dsn()
492 }
493}
494
495impl IntoDsn for &Dsn {
496 fn into_dsn(self) -> Result<Dsn, DsnError> {
497 Ok(self.clone())
498 }
499}
500impl IntoDsn for Dsn {
501 fn into_dsn(self) -> Result<Dsn, DsnError> {
502 Ok(self)
503 }
504}
505
506impl Display for Dsn {
507 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
508 write!(f, "{}", self.driver)?;
509 if let Some(protocol) = &self.protocol {
510 write!(f, "+{protocol}")?;
511 }
512
513 if self.is_path_like() {
514 write!(f, ":")?;
515 } else {
516 write!(f, "://")?;
517 }
518
519 match (&self.username, &self.password) {
520 (Some(username), Some(password)) => {
521 write!(f, "{}:{}@", encode(username), encode(password))?
522 }
523 (Some(username), None) => write!(f, "{}@", encode(username))?,
524 (None, Some(password)) => write!(f, ":{}@", encode(password))?,
525 (None, None) => {}
526 }
527
528 if !self.addresses.is_empty() {
529 write!(
530 f,
531 "{}",
532 self.addresses.iter().map(ToString::to_string).join(",")
533 )?;
534 }
535 if let Some(database) = &self.subject {
536 write!(f, "/{database}")?;
537 } else if let Some(path) = &self.path {
538 write!(f, "{path}")?;
539 }
540
541 if !self.params.is_empty() {
542 fn percent_encode_or_not(v: &str) -> Cow<str> {
543 if v.contains(|c| c == '=' || c == '&' || c == '#' || c == '@') {
544 urlencoding::encode(v)
545 } else {
546 v.into()
547 }
548 }
549 write!(
550 f,
551 "?{}",
552 self.params
553 .iter()
554 .map(|(k, v)| format!(
555 "{}={}",
556 percent_encode_or_not(k),
557 percent_encode_or_not(v)
558 ))
559 .join("&")
560 )?;
561 }
562 Ok(())
563 }
564}
565
566impl Dsn {
567 #[inline]
568 pub fn drain_params(&mut self) -> BTreeMap<String, String> {
569 let drained = self
570 .params
571 .iter()
572 .map(|(k, v)| (k.clone(), v.clone()))
573 .collect();
574 self.params.clear();
575 drained
576 }
577
578 #[inline]
579 pub fn set(&mut self, key: impl Into<String>, value: impl Into<String>) -> Option<String> {
580 self.params.insert(key.into(), value.into())
581 }
582
583 #[inline]
584 pub fn get(&self, key: impl AsRef<str>) -> Option<&String> {
585 self.params.get(key.as_ref())
586 }
587
588 #[inline]
589 pub fn remove(&mut self, key: impl AsRef<str>) -> Option<String> {
590 self.params.remove(key.as_ref())
591 }
592}
593
594impl TryFrom<&str> for Dsn {
595 type Error = DsnError;
596
597 fn try_from(value: &str) -> Result<Self, Self::Error> {
598 Dsn::from_str(value)
599 }
600}
601
602impl TryFrom<&String> for Dsn {
603 type Error = DsnError;
604
605 fn try_from(value: &String) -> Result<Self, Self::Error> {
606 Dsn::from_str(value)
607 }
608}
609
610impl TryFrom<String> for Dsn {
611 type Error = DsnError;
612
613 fn try_from(value: String) -> Result<Self, Self::Error> {
614 Dsn::from_str(&value)
615 }
616}
617
618impl TryFrom<&Dsn> for Dsn {
619 type Error = DsnError;
620
621 fn try_from(value: &Dsn) -> Result<Self, Self::Error> {
622 Ok(value.clone())
623 }
624}
625impl FromStr for Dsn {
626 type Err = DsnError;
627
628 fn from_str(s: &str) -> Result<Self, Self::Err> {
629 #[cfg(feature = "pest")]
630 return Self::from_pest(s);
631 #[cfg(not(feature = "pest"))]
632 return Self::from_regex(s);
633 }
634}
635
636#[cfg(test)]
637mod tests {
638 use super::*;
639
640 #[test]
641 fn test_into_dsn() {
642 let _ = "taos://".into_dsn().unwrap();
643 let _ = "taos://".to_string().into_dsn().unwrap();
644 let _ = (&"taos://".to_string()).into_dsn().unwrap();
645
646 let a: Dsn = "taos://".parse().unwrap();
647 let b = a.into_dsn().unwrap();
648 let c = (&b).into_dsn().unwrap();
649
650 let d: Dsn = (&c).try_into().unwrap();
651 let _: Dsn = (d).try_into().unwrap();
652 let _: Dsn = ("taos://").try_into().unwrap();
653 let _: Dsn = ("taos://".to_string()).try_into().unwrap();
654 let _: Dsn = (&"taos://:password@".parse::<Dsn>().unwrap().to_string())
655 .try_into()
656 .unwrap();
657 }
658
659 #[test]
660 fn test_methods() {
661 let mut dsn: Dsn = "taos://localhost:6030/test?debugFlag=135".parse().unwrap();
662
663 let flag = dsn.get("debugFlag").unwrap();
664 assert_eq!(flag, "135");
665
666 dsn.set("configDir", "/tmp/taos");
667 assert_eq!(dsn.get("configDir").unwrap(), "/tmp/taos");
668 let config = dsn.remove("configDir").unwrap();
669 assert_eq!(config, "/tmp/taos");
670
671 let params = dsn.drain_params();
672 assert!(dsn.params.is_empty());
673 assert!(params.contains_key("debugFlag"));
674 assert!(params.len() == 1);
675 }
676
677 #[test]
678 fn username_with_password() {
679 let s = "taos://";
680
681 let dsn = Dsn::from_str(s).unwrap();
682 assert_eq!(
683 dsn,
684 Dsn {
685 driver: "taos".to_string(),
686 ..Default::default()
687 }
688 );
689 assert_eq!(dsn.to_string(), s);
690
691 let s = "taos:///";
692
693 let dsn = Dsn::from_str(s).unwrap();
694 assert_eq!(
695 dsn,
696 Dsn {
697 driver: "taos".to_string(),
698 ..Default::default()
699 }
700 );
701 assert_eq!(dsn.to_string(), "taos://");
702
703 let s = "taos://root@";
704
705 let dsn = Dsn::from_str(s).unwrap();
706 assert_eq!(
707 dsn,
708 Dsn {
709 driver: "taos".to_string(),
710 username: Some("root".to_string()),
711 ..Default::default()
712 }
713 );
714 assert_eq!(dsn.to_string(), s);
715 let s = "taos://root:taosdata@";
716
717 let dsn = Dsn::from_str(s).unwrap();
718 assert_eq!(
719 dsn,
720 Dsn {
721 driver: "taos".to_string(),
722 username: Some("root".to_string()),
723 password: Some("taosdata".to_string()),
724 ..Default::default()
725 }
726 );
727 assert_eq!(dsn.to_string(), s);
728 }
729
730 #[test]
731 fn host_port_mix() {
732 let s = "taos://localhost";
733 let dsn = Dsn::from_str(s).unwrap();
734 assert_eq!(
735 dsn,
736 Dsn {
737 driver: "taos".to_string(),
738 addresses: vec![Address {
739 host: Some("localhost".to_string()),
740 ..Default::default()
741 }],
742 ..Default::default()
743 }
744 );
745 assert_eq!(dsn.to_string(), s);
746
747 let s = "taos://root@:6030";
748 let dsn = Dsn::from_str(s).unwrap();
749 assert_eq!(
750 dsn,
751 Dsn {
752 driver: "taos".to_string(),
753 username: Some("root".to_string()),
754 addresses: vec![Address {
755 port: Some(6030),
756 ..Default::default()
757 }],
758 ..Default::default()
759 }
760 );
761 assert_eq!(dsn.to_string(), s);
762
763 let s = "taos://root@localhost:6030";
764 let dsn = Dsn::from_str(s).unwrap();
765 assert_eq!(
766 dsn,
767 Dsn {
768 driver: "taos".to_string(),
769 username: Some("root".to_string()),
770 addresses: vec![Address {
771 host: Some("localhost".to_string()),
772 port: Some(6030),
773 ..Default::default()
774 }],
775 ..Default::default()
776 }
777 );
778 assert_eq!(dsn.to_string(), s);
779 }
780 #[test]
781 fn username_with_host() {
782 let s = "taos://root@localhost";
783 let dsn = Dsn::from_str(s).unwrap();
784 assert_eq!(
785 dsn,
786 Dsn {
787 driver: "taos".to_string(),
788 username: Some("root".to_string()),
789 addresses: vec![Address {
790 host: Some("localhost".to_string()),
791 ..Default::default()
792 }],
793 ..Default::default()
794 }
795 );
796 assert_eq!(dsn.to_string(), s);
797
798 let s = "taos://root@:6030";
799 let dsn = Dsn::from_str(s).unwrap();
800 assert_eq!(
801 dsn,
802 Dsn {
803 driver: "taos".to_string(),
804 username: Some("root".to_string()),
805 addresses: vec![Address {
806 port: Some(6030),
807 ..Default::default()
808 }],
809 ..Default::default()
810 }
811 );
812 assert_eq!(dsn.to_string(), s);
813
814 let s = "taos://root@localhost:6030";
815 let dsn = Dsn::from_str(s).unwrap();
816 assert_eq!(
817 dsn,
818 Dsn {
819 driver: "taos".to_string(),
820 username: Some("root".to_string()),
821 addresses: vec![Address::new("localhost", 6030)],
822 ..Default::default()
823 }
824 );
825
826 let s = "taos://root:taosdata@localhost:6030";
827 let dsn = Dsn::from_str(s).unwrap();
828 assert_eq!(
829 dsn,
830 Dsn {
831 driver: "taos".to_string(),
832 username: Some("root".to_string()),
833 password: Some("taosdata".to_string()),
834 addresses: vec![Address::new("localhost", 6030)],
835 ..Default::default()
836 }
837 );
838 assert_eq!(dsn.to_string(), s);
839 }
840
841 #[test]
842 fn username_with_multi_addresses() {
843 let s = "taos://root@host1.domain:6030,host2.domain:6031";
844 let dsn = Dsn::from_str(s).unwrap();
845 assert_eq!(
846 dsn,
847 Dsn {
848 driver: "taos".to_string(),
849 username: Some("root".to_string()),
850 addresses: vec![
851 Address::new("host1.domain", 6030),
852 Address::new("host2.domain", 6031)
853 ],
854 ..Default::default()
855 }
856 );
857 assert_eq!(dsn.to_string(), s);
858
859 let s = "taos://root:taosdata@host1:6030,host2:6031";
860 let dsn = Dsn::from_str(s).unwrap();
861 assert_eq!(
862 dsn,
863 Dsn {
864 driver: "taos".to_string(),
865 username: Some("root".to_string()),
866 password: Some("taosdata".to_string()),
867 addresses: vec![Address::new("host1", 6030), Address::new("host2", 6031)],
868 ..Default::default()
869 }
870 );
871 assert_eq!(dsn.to_string(), s);
872 }
873
874 #[test]
875 fn db_only() {
876 let s = "taos:///db1";
877 let dsn = Dsn::from_str(s).unwrap();
878 assert_eq!(
879 dsn,
880 Dsn {
881 driver: "taos".to_string(),
882 subject: Some("db1".to_string()),
883 ..Default::default()
884 }
885 );
886 assert_eq!(dsn.to_string(), s);
887
888 let s = "taos:///db1";
889 let dsn = Dsn::from_str(s).unwrap();
890 assert_eq!(
891 dsn,
892 Dsn {
893 driver: "taos".to_string(),
894 subject: Some("db1".to_string()),
895 ..Default::default()
896 }
897 );
898 assert_eq!(dsn.to_string(), s);
899 }
900
901 #[test]
902 fn username_with_multi_addresses_database() {
903 let s = "taos://root@host1:6030,host2:6031/db1";
904 let dsn = Dsn::from_str(s).unwrap();
905 assert_eq!(
906 dsn,
907 Dsn {
908 driver: "taos".to_string(),
909 username: Some("root".to_string()),
910 subject: Some("db1".to_string()),
911 addresses: vec![Address::new("host1", 6030), Address::new("host2", 6031)],
912 ..Default::default()
913 }
914 );
915 assert_eq!(dsn.to_string(), s);
916
917 let s = "taos://root:taosdata@host1:6030,host2:6031/db1";
918 let dsn = Dsn::from_str(s).unwrap();
919 assert_eq!(
920 dsn,
921 Dsn {
922 driver: "taos".to_string(),
923 username: Some("root".to_string()),
924 password: Some("taosdata".to_string()),
925 subject: Some("db1".to_string()),
926 addresses: vec![Address::new("host1", 6030), Address::new("host2", 6031)],
927 ..Default::default()
928 }
929 );
930 assert_eq!(dsn.to_string(), s);
931 }
932
933 #[test]
934 fn protocol() {
935 let s = "taos://root@tcp(host1:6030,host2:6031)/db1";
936 let dsn = Dsn::from_str(s).unwrap();
937 assert_eq!(
938 dsn,
939 Dsn {
940 driver: "taos".to_string(),
941 username: Some("root".to_string()),
942 subject: Some("db1".to_string()),
943 protocol: Some("tcp".to_string()),
944 addresses: vec![Address::new("host1", 6030), Address::new("host2", 6031)],
945 ..Default::default()
946 }
947 );
948 assert_eq!(dsn.to_string(), "taos+tcp://root@host1:6030,host2:6031/db1");
949
950 let s = "taos+tcp://root@host1:6030,host2:6031/db1";
951 let dsn = Dsn::from_str(s).unwrap();
952 assert_eq!(
953 dsn,
954 Dsn {
955 driver: "taos".to_string(),
956 username: Some("root".to_string()),
957 subject: Some("db1".to_string()),
958 protocol: Some("tcp".to_string()),
959 addresses: vec![Address::new("host1", 6030), Address::new("host2", 6031)],
960 ..Default::default()
961 }
962 );
963 assert_eq!(dsn.to_string(), "taos+tcp://root@host1:6030,host2:6031/db1");
964 }
965
966 #[test]
967 fn fragment() {
968 let s = "postgresql://%2Fvar%2Flib%2Fpostgresql/dbname";
969 let dsn = Dsn::from_str(s).unwrap();
970 assert_eq!(
971 dsn,
972 Dsn {
973 driver: "postgresql".to_string(),
974 subject: Some("dbname".to_string()),
975 addresses: vec![Address {
976 path: Some("/var/lib/postgresql".to_string()),
977 ..Default::default()
978 }],
979 ..Default::default()
980 }
981 );
982 assert_eq!(dsn.to_string(), s);
983
984 let s = "unix:/path/to/unix.sock";
985 let dsn = Dsn::from_str(s).unwrap();
986 assert_eq!(
987 dsn,
988 Dsn {
989 driver: "unix".to_string(),
990 path: Some("/path/to/unix.sock".to_string()),
991 ..Default::default()
992 }
993 );
994 assert_eq!(dsn.to_string(), s);
995
996 let s = "sqlite:/c:/full/windows/path/to/file.db";
997 let dsn = Dsn::from_str(s).unwrap();
998 assert_eq!(
999 dsn,
1000 Dsn {
1001 driver: "sqlite".to_string(),
1002 path: Some("/c:/full/windows/path/to/file.db".to_string()),
1003 ..Default::default()
1004 }
1005 );
1006 assert_eq!(dsn.to_string(), s);
1007
1008 let s = "sqlite:./file.db";
1009 let dsn = Dsn::from_str(s).unwrap();
1010 assert_eq!(
1011 dsn,
1012 Dsn {
1013 driver: "sqlite".to_string(),
1014 path: Some("./file.db".to_string()),
1015 ..Default::default()
1016 }
1017 );
1018 assert_eq!(dsn.to_string(), s);
1019
1020 let s = "sqlite://root:pass@//full/unix/path/to/file.db?mode=0666&readonly=true";
1021 let dsn = Dsn::from_str(s).unwrap();
1022 assert_eq!(
1023 dsn,
1024 Dsn {
1025 driver: "sqlite".to_string(),
1026 username: Some("root".to_string()),
1027 password: Some("pass".to_string()),
1028 subject: Some("/full/unix/path/to/file.db".to_string()),
1029 params: (BTreeMap::from_iter(vec![
1030 ("mode".to_string(), "0666".to_string()),
1031 ("readonly".to_string(), "true".to_string())
1032 ])),
1033 ..Default::default()
1034 }
1035 );
1036 assert_eq!(dsn.to_string(), s);
1037 }
1038
1039 #[test]
1040 fn path_special_chars() {
1041 let s = "csv:./files/1718243049903/文件Aa1 %$@.-_()[]{}()【】{}.csv";
1043 let dsn = Dsn::from_str(s).unwrap();
1044 assert_eq!(
1045 dsn,
1046 Dsn {
1047 driver: "csv".to_string(),
1048 path: Some(
1049 "./files/1718243049903/文件Aa1 %$@.-_()[]{}()【】{}.csv".to_string()
1050 ),
1051 ..Default::default()
1052 }
1053 );
1054 assert_eq!(dsn.to_string(), s);
1055
1056 let s = "csv:./files/1718243049903/文件Aa1 %$@.-_()[]{}()【】{}&.csv";
1058 let dsn = Dsn::from_str(s).unwrap();
1059 assert_eq!(
1060 dsn,
1061 Dsn {
1062 driver: "csv".to_string(),
1063 path: Some("./files/1718243049903/文件Aa1 %$@.-_()[]{}()【】{}".to_string()),
1064 ..Default::default()
1065 }
1066 );
1067 assert_ne!(dsn.to_string(), s);
1068 }
1069
1070 #[test]
1071 fn params() {
1072 let s = r#"taos://?abc=abc"#;
1073 let dsn = Dsn::from_str(s).unwrap();
1074 assert_eq!(
1075 dsn,
1076 Dsn {
1077 driver: "taos".to_string(),
1078 params: (BTreeMap::from_iter(vec![("abc".to_string(), "abc".to_string())])),
1079 ..Default::default()
1080 }
1081 );
1082 assert_eq!(dsn.to_string(), s);
1083
1084 let s = r#"taos://root@localhost?abc=abc"#;
1085 let dsn = Dsn::from_str(s).unwrap();
1086 assert_eq!(
1087 dsn,
1088 Dsn {
1089 driver: "taos".to_string(),
1090 username: Some("root".to_string()),
1091 addresses: vec![Address::from_host("localhost")],
1092 params: (BTreeMap::from_iter(vec![("abc".to_string(), "abc".to_string())])),
1093 ..Default::default()
1094 }
1095 );
1096 assert_eq!(dsn.to_string(), s);
1097 }
1098
1099 #[test]
1100 fn parse_taos_tmq() {
1101 let s = "taos://root:taosdata@localhost/aa23d04011eca42cf7d8c1dd05a37985?topics=aa23d04011eca42cf7d8c1dd05a37985&group.id=tg2";
1102 let _ = Dsn::from_str(s).unwrap();
1103 }
1104
1105 #[test]
1106 fn tmq_ws_driver() {
1107 let dsn = Dsn::from_str("tmq+ws:///abc1?group.id=abc3&timeout=50ms").unwrap();
1108 assert_eq!(dsn.driver, "tmq");
1109 assert_eq!(dsn.to_string(), "tmq+ws:///abc1?group.id=abc3&timeout=50ms");
1110 }
1111
1112 #[test]
1113 fn tmq_offset() {
1114 let dsn = Dsn::from_str("tmq+ws:///abc1?offset=10:20,11:40").unwrap();
1115 let offset = dsn.get("offset").unwrap();
1116 dbg!(&dsn);
1117 dbg!(&offset);
1118 }
1119
1120 #[test]
1121 fn password_special_chars() {
1122 let p = "!@#$%^&*()";
1123 let e = urlencoding::encode(p);
1124 dbg!(&e);
1125
1126 let dsn = Dsn::from_str(&format!("taos://root:{e}@localhost:6030/")).unwrap();
1127 dbg!(&dsn);
1128 assert_eq!(dsn.password.as_deref().unwrap(), p);
1129 assert_eq!(dsn.to_string(), format!("taos://root:{e}@localhost:6030"));
1130 }
1131 #[test]
1132 fn param_special_chars() {
1133 let p = "!@#$%^&*()";
1134 let e = urlencoding::encode(p);
1135 dbg!(&e);
1136
1137 let dsn = Dsn::from_str(&format!("taos://root:{e}@localhost:6030?code1={e}")).unwrap();
1138 dbg!(&dsn);
1139 assert_eq!(dsn.password.as_deref().unwrap(), p);
1140 assert_eq!(dsn.get("code1").unwrap(), p);
1141 assert_eq!(
1142 dsn.to_string(),
1143 format!("taos://root:{e}@localhost:6030?code1={e}")
1144 );
1145 }
1146 #[test]
1147 fn param_special_chars_all() {
1148 let p = "!@#$%^&*()";
1149 let e = urlencoding::encode(p);
1150 dbg!(&e);
1151
1152 let dsn = Dsn::from_str(&format!("taos://{e}:{e}@localhost:6030?{e}={e}")).unwrap();
1153 dbg!(&dsn);
1154 assert_eq!(dsn.password.as_deref().unwrap(), p);
1155 assert_eq!(dsn.get(p).unwrap(), p);
1156 assert_eq!(
1157 dsn.to_string(),
1158 format!("taos://{e}:{e}@localhost:6030?{e}={e}")
1159 );
1160 }
1161 #[test]
1162 fn unix_path_with_glob() {
1163 let dsn = Dsn::from_str(&format!("csv:./**.csv?param=1")).unwrap();
1164 dbg!(&dsn);
1165 assert!(dsn.path.is_some());
1166 assert_eq!(dsn.get("param").unwrap(), "1");
1167
1168 let dsn = Dsn::from_str(&format!("csv:./**.csv?param=1")).unwrap();
1169 dbg!(&dsn);
1170 assert!(dsn.path.is_some());
1171 assert_eq!(dsn.get("param").unwrap(), "1");
1172
1173 let dsn = Dsn::from_str(&format!("csv:.\\**.csv?param=1")).unwrap();
1174 dbg!(&dsn);
1175 assert!(dsn.path.is_some());
1176 assert_eq!(dsn.get("param").unwrap(), "1");
1177 }
1178
1179 #[test]
1180 fn unix_path_with_space() {
1181 let dsn = Dsn::from_str(&format!("csv:./a b.csv?param=1")).unwrap();
1182 dbg!(&dsn);
1183 assert_eq!(dsn.path.unwrap(), "./a b.csv");
1184 }
1185
1186 #[test]
1187 fn with_space() {
1188 let dsn = Dsn::from_str("pi:///Met1 ABC").unwrap();
1189 assert_eq!(dsn.subject, Some("Met1 ABC".to_string()));
1190
1191 let dsn = Dsn::from_str("pi://u ser:pa ss@host:80/Met1 ABC").unwrap();
1192 dbg!(&dsn);
1193 assert_eq!(dsn.subject, Some("Met1 ABC".to_string()));
1194 assert_eq!(dsn.username, Some("u ser".to_string()));
1195 assert_eq!(dsn.password, Some("pa ss".to_string()));
1196 }
1197}