1#![deny(unsafe_code)]
2
3#[cfg(feature = "serde")]
31mod de;
32pub(crate) mod parser;
33#[cfg(feature = "serde")]
34mod ser;
35#[cfg(test)]
36mod tests;
37
38use parser::{
39 authority::{userinfo::UserSpec, Authority},
40 ConnectionUri,
41};
42use std::{fmt::Display, path::PathBuf, str::FromStr};
43use tracing::{debug, trace};
44
45pub use parser::authority::host::Host;
46
47#[derive(Clone, Debug, Default, PartialEq, Eq)]
52pub struct Parameter {
53 pub keyword: String,
55
56 pub value: String,
58}
59
60#[derive(Clone, Debug, Default, PartialEq, Eq)]
66pub struct ConnectionString {
67 pub user: Option<String>,
68 pub password: Option<String>,
69 pub hostspecs: Vec<HostSpec>,
70 pub database: Option<String>,
71 pub parameters: Vec<Parameter>,
72 pub fragment: Option<String>,
73}
74
75#[derive(Clone, Debug, PartialEq, Eq)]
76pub struct HostSpec {
77 pub host: Host,
78 pub port: Option<u16>,
79}
80
81impl Display for ConnectionString {
82 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83 write!(f, "postgresql://",)?;
84
85 if let Some(user) = &self.user {
86 write!(f, "{user}",)?;
87
88 if let Some(password) = &self.password {
89 write!(f, ":{password}",)?;
90 }
91
92 write!(f, "@")?;
93 }
94
95 for (n, HostSpec { host, port }) in self.hostspecs.iter().enumerate() {
96 if let Host::Path(_) = host {
97 continue;
98 }
99
100 write!(f, "{host}")?;
101
102 if let Some(p) = port {
103 write!(f, ":{p}")?;
104 }
105
106 if n + 1 < self.hostspecs.len() {
107 write!(f, ",")?;
108 }
109 }
110
111 if let Some(database) = &self.database {
112 write!(f, "/{database}")?;
113 }
114
115 for (n, Parameter { keyword, value }) in self.parameters.iter().enumerate() {
116 if n == 0 {
117 write!(f, "?")?;
118 }
119 write!(f, "{keyword}={value}")?;
120
121 if n + 1 < self.parameters.len() {
122 write!(f, "&")?;
123 }
124 }
125
126 for HostSpec { host, .. } in &self.hostspecs {
128 match host {
129 Host::Path(path) => {
130 write!(f, "{}", if self.parameters.is_empty() { "?" } else { "&" })?;
131
132 write!(f, "host={}", path.to_str().unwrap_or("invalid"))?;
133 }
134 _ => continue,
135 }
136 }
137
138 if let Some(frag) = &self.fragment {
139 write!(f, "#{frag}")?;
140 }
141
142 Ok(())
143 }
144}
145
146impl TryFrom<ConnectionUri> for ConnectionString {
147 type Error = anyhow::Error;
148
149 fn try_from(mut uri: ConnectionUri) -> Result<Self, Self::Error> {
150 let mut addtl_hosts = vec![];
153 if let Some(params) = &mut uri.parameters {
154 if let Some(pos) = params.iter().position(|p| p.keyword == "host") {
155 let param = params.remove(pos);
156
157 addtl_hosts.push(param);
158 }
159 }
160
161 let mut out = ConnectionString {
164 database: uri.database,
165 parameters: uri.parameters.unwrap_or(vec![]),
166 fragment: uri.fragment,
167 ..ConnectionString::default()
168 };
169 trace!(?out, "populated unchanging pieces");
170
171 if let Some(Authority { userspec, hostspec }) = uri.authority {
173 trace!(?userspec, ?hostspec, "found authority");
174
175 if let Some(UserSpec { user, password }) = userspec {
177 trace!(?user, ?password, "found userspec");
178
179 out.user = Some(user);
180 out.password = password;
181 }
182
183 for spec in hostspec {
185 trace!(?spec, "adding hostspec");
186
187 if let Some(host) = spec.host {
188 out.hostspecs.push(HostSpec {
189 host,
190 port: spec.port,
191 });
192 }
193 }
194 }
195
196 for Parameter { value, .. } in addtl_hosts {
197 let host = if value.starts_with('/') {
198 Host::Path(PathBuf::from(value))
199 } else {
200 value.parse()?
201 };
202
203 out.hostspecs.push(HostSpec { host, port: None });
204 }
205
206 Ok(out)
207 }
208}
209
210impl FromStr for ConnectionString {
212 type Err = anyhow::Error;
213
214 fn from_str(s: &str) -> Result<Self, Self::Err> {
215 let parsed = parser::consuming_connection_string(s)?;
216 debug!(?parsed);
217
218 parsed.try_into()
219 }
220}
221
222pub fn from_multi_str(i: &str, sep: &str) -> anyhow::Result<Vec<ConnectionString>> {
257 let parsed = parser::multi_connection_string(i, sep)?;
258 debug!("{parsed:?}");
260
261 let mut out: Vec<ConnectionString> = vec![];
262
263 for c in parsed {
264 out.push(TryFrom::try_from(c)?);
265 }
266
267 Ok(out)
268}