xxai_tokio_postgres/
config.rs

1//! Connection configuration.
2
3#[cfg(feature = "runtime")]
4use crate::connect::connect;
5use crate::connect_raw::connect_raw;
6#[cfg(not(target_arch = "wasm32"))]
7use crate::keepalive::KeepaliveConfig;
8#[cfg(feature = "runtime")]
9use crate::tls::MakeTlsConnect;
10use crate::tls::TlsConnect;
11#[cfg(feature = "runtime")]
12use crate::Socket;
13use crate::{Client, Connection, Error};
14use std::borrow::Cow;
15#[cfg(unix)]
16use std::ffi::OsStr;
17use std::net::IpAddr;
18use std::ops::Deref;
19#[cfg(unix)]
20use std::os::unix::ffi::OsStrExt;
21#[cfg(unix)]
22use std::path::{Path, PathBuf};
23use std::str;
24use std::str::FromStr;
25use std::time::Duration;
26use std::{error, fmt, iter, mem};
27use tokio::io::{AsyncRead, AsyncWrite};
28
29/// Properties required of a session.
30#[derive(Debug, Copy, Clone, PartialEq, Eq)]
31#[non_exhaustive]
32pub enum TargetSessionAttrs {
33    /// No special properties are required.
34    Any,
35    /// The session must allow writes.
36    ReadWrite,
37}
38
39/// TLS configuration.
40#[derive(Debug, Copy, Clone, PartialEq, Eq)]
41#[non_exhaustive]
42pub enum SslMode {
43    /// Do not use TLS.
44    Disable,
45    /// Attempt to connect with TLS but allow sessions without.
46    Prefer,
47    /// Require the use of TLS.
48    Require,
49}
50
51/// Channel binding configuration.
52#[derive(Debug, Copy, Clone, PartialEq, Eq)]
53#[non_exhaustive]
54pub enum ChannelBinding {
55    /// Do not use channel binding.
56    Disable,
57    /// Attempt to use channel binding but allow sessions without.
58    Prefer,
59    /// Require the use of channel binding.
60    Require,
61}
62
63/// Load balancing configuration.
64#[derive(Debug, Copy, Clone, PartialEq, Eq)]
65#[non_exhaustive]
66pub enum LoadBalanceHosts {
67    /// Make connection attempts to hosts in the order provided.
68    Disable,
69    /// Make connection attempts to hosts in a random order.
70    Random,
71}
72
73/// A host specification.
74#[derive(Debug, Clone, PartialEq, Eq)]
75pub enum Host {
76    /// A TCP hostname.
77    Tcp(String),
78    /// A path to a directory containing the server's Unix socket.
79    ///
80    /// This variant is only available on Unix platforms.
81    #[cfg(unix)]
82    Unix(PathBuf),
83}
84
85/// Connection configuration.
86///
87/// Configuration can be parsed from libpq-style connection strings. These strings come in two formats:
88///
89/// # Key-Value
90///
91/// This format consists of space-separated key-value pairs. Values which are either the empty string or contain
92/// whitespace should be wrapped in `'`. `'` and `\` characters should be backslash-escaped.
93///
94/// ## Keys
95///
96/// * `user` - The username to authenticate with. Required.
97/// * `password` - The password to authenticate with.
98/// * `dbname` - The name of the database to connect to. Defaults to the username.
99/// * `options` - Command line options used to configure the server.
100/// * `application_name` - Sets the `application_name` parameter on the server.
101/// * `sslmode` - Controls usage of TLS. If set to `disable`, TLS will not be used. If set to `prefer`, TLS will be used
102///     if available, but not used otherwise. If set to `require`, TLS will be forced to be used. Defaults to `prefer`.
103/// * `host` - The host to connect to. On Unix platforms, if the host starts with a `/` character it is treated as the
104///     path to the directory containing Unix domain sockets. Otherwise, it is treated as a hostname. Multiple hosts
105///     can be specified, separated by commas. Each host will be tried in turn when connecting. Required if connecting
106///     with the `connect` method.
107/// * `hostaddr` - Numeric IP address of host to connect to. This should be in the standard IPv4 address format,
108///     e.g., 172.28.40.9. If your machine supports IPv6, you can also use those addresses.
109///     If this parameter is not specified, the value of `host` will be looked up to find the corresponding IP address,
110///     or if host specifies an IP address, that value will be used directly.
111///     Using `hostaddr` allows the application to avoid a host name look-up, which might be important in applications
112///     with time constraints. However, a host name is required for TLS certificate verification.
113///     Specifically:
114///         * If `hostaddr` is specified without `host`, the value for `hostaddr` gives the server network address.
115///             The connection attempt will fail if the authentication method requires a host name;
116///         * If `host` is specified without `hostaddr`, a host name lookup occurs;
117///         * If both `host` and `hostaddr` are specified, the value for `hostaddr` gives the server network address.
118///             The value for `host` is ignored unless the authentication method requires it,
119///             in which case it will be used as the host name.
120/// * `port` - The port to connect to. Multiple ports can be specified, separated by commas. The number of ports must be
121///     either 1, in which case it will be used for all hosts, or the same as the number of hosts. Defaults to 5432 if
122///     omitted or the empty string.
123/// * `connect_timeout` - The time limit in seconds applied to each socket-level connection attempt. Note that hostnames
124///     can resolve to multiple IP addresses, and this limit is applied to each address. Defaults to no timeout.
125/// * `tcp_user_timeout` - The time limit that transmitted data may remain unacknowledged before a connection is forcibly closed.
126///     This is ignored for Unix domain socket connections. It is only supported on systems where TCP_USER_TIMEOUT is available
127///     and will default to the system default if omitted or set to 0; on other systems, it has no effect.
128/// * `keepalives` - Controls the use of TCP keepalive. A value of 0 disables keepalive and nonzero integers enable it.
129///     This option is ignored when connecting with Unix sockets. Defaults to on.
130/// * `keepalives_idle` - The number of seconds of inactivity after which a keepalive message is sent to the server.
131///     This option is ignored when connecting with Unix sockets. Defaults to 2 hours.
132/// * `keepalives_interval` - The time interval between TCP keepalive probes.
133///     This option is ignored when connecting with Unix sockets.
134/// * `keepalives_retries` - The maximum number of TCP keepalive probes that will be sent before dropping a connection.
135///     This option is ignored when connecting with Unix sockets.
136/// * `target_session_attrs` - Specifies requirements of the session. If set to `read-write`, the client will check that
137///     the `transaction_read_write` session parameter is set to `on`. This can be used to connect to the primary server
138///     in a database cluster as opposed to the secondary read-only mirrors. Defaults to `all`.
139/// * `channel_binding` - Controls usage of channel binding in the authentication process. If set to `disable`, channel
140///     binding will not be used. If set to `prefer`, channel binding will be used if available, but not used otherwise.
141///     If set to `require`, the authentication process will fail if channel binding is not used. Defaults to `prefer`.
142/// * `load_balance_hosts` - Controls the order in which the client tries to connect to the available hosts and
143///     addresses. Once a connection attempt is successful no other hosts and addresses will be tried. This parameter
144///     is typically used in combination with multiple host names or a DNS record that returns multiple IPs. If set to
145///     `disable`, hosts and addresses will be tried in the order provided. If set to `random`, hosts will be tried
146///     in a random order, and the IP addresses resolved from a hostname will also be tried in a random order. Defaults
147///     to `disable`.
148///
149/// ## Examples
150///
151/// ```not_rust
152/// host=localhost user=postgres connect_timeout=10 keepalives=0
153/// ```
154///
155/// ```not_rust
156/// host=/var/lib/postgresql,localhost port=1234 user=postgres password='password with spaces'
157/// ```
158///
159/// ```not_rust
160/// host=host1,host2,host3 port=1234,,5678 hostaddr=127.0.0.1,127.0.0.2,127.0.0.3 user=postgres target_session_attrs=read-write
161/// ```
162///
163/// ```not_rust
164/// host=host1,host2,host3 port=1234,,5678 user=postgres target_session_attrs=read-write
165/// ```
166///
167/// # Url
168///
169/// This format resembles a URL with a scheme of either `postgres://` or `postgresql://`. All components are optional,
170/// and the format accepts query parameters for all of the key-value pairs described in the section above. Multiple
171/// host/port pairs can be comma-separated. Unix socket paths in the host section of the URL should be percent-encoded,
172/// as the path component of the URL specifies the database name.
173///
174/// ## Examples
175///
176/// ```not_rust
177/// postgresql://user@localhost
178/// ```
179///
180/// ```not_rust
181/// postgresql://user:password@%2Fvar%2Flib%2Fpostgresql/mydb?connect_timeout=10
182/// ```
183///
184/// ```not_rust
185/// postgresql://user@host1:1234,host2,host3:5678?target_session_attrs=read-write
186/// ```
187///
188/// ```not_rust
189/// postgresql:///mydb?user=user&host=/var/lib/postgresql
190/// ```
191#[derive(Clone, PartialEq, Eq)]
192pub struct Config {
193    pub(crate) user: Option<String>,
194    pub(crate) password: Option<Vec<u8>>,
195    pub(crate) dbname: Option<String>,
196    pub(crate) options: Option<String>,
197    pub(crate) application_name: Option<String>,
198    pub(crate) ssl_mode: SslMode,
199    pub(crate) host: Vec<Host>,
200    pub(crate) hostaddr: Vec<IpAddr>,
201    pub(crate) port: Vec<u16>,
202    pub(crate) connect_timeout: Option<Duration>,
203    pub(crate) tcp_user_timeout: Option<Duration>,
204    pub(crate) keepalives: bool,
205    #[cfg(not(target_arch = "wasm32"))]
206    pub(crate) keepalive_config: KeepaliveConfig,
207    pub(crate) target_session_attrs: TargetSessionAttrs,
208    pub(crate) channel_binding: ChannelBinding,
209    pub(crate) load_balance_hosts: LoadBalanceHosts,
210}
211
212impl Default for Config {
213    fn default() -> Config {
214        Config::new()
215    }
216}
217
218impl Config {
219    /// Creates a new configuration.
220    pub fn new() -> Config {
221        Config {
222            user: None,
223            password: None,
224            dbname: None,
225            options: None,
226            application_name: None,
227            ssl_mode: SslMode::Prefer,
228            host: vec![],
229            hostaddr: vec![],
230            port: vec![],
231            connect_timeout: None,
232            tcp_user_timeout: None,
233            keepalives: true,
234            #[cfg(not(target_arch = "wasm32"))]
235            keepalive_config: KeepaliveConfig {
236                idle: Duration::from_secs(2 * 60 * 60),
237                interval: None,
238                retries: None,
239            },
240            target_session_attrs: TargetSessionAttrs::Any,
241            channel_binding: ChannelBinding::Prefer,
242            load_balance_hosts: LoadBalanceHosts::Disable,
243        }
244    }
245
246    /// Sets the user to authenticate with.
247    ///
248    /// Required.
249    pub fn user(&mut self, user: &str) -> &mut Config {
250        self.user = Some(user.to_string());
251        self
252    }
253
254    /// Gets the user to authenticate with, if one has been configured with
255    /// the `user` method.
256    pub fn get_user(&self) -> Option<&str> {
257        self.user.as_deref()
258    }
259
260    /// Sets the password to authenticate with.
261    pub fn password<T>(&mut self, password: T) -> &mut Config
262    where
263        T: AsRef<[u8]>,
264    {
265        self.password = Some(password.as_ref().to_vec());
266        self
267    }
268
269    /// Gets the password to authenticate with, if one has been configured with
270    /// the `password` method.
271    pub fn get_password(&self) -> Option<&[u8]> {
272        self.password.as_deref()
273    }
274
275    /// Sets the name of the database to connect to.
276    ///
277    /// Defaults to the user.
278    pub fn dbname(&mut self, dbname: &str) -> &mut Config {
279        self.dbname = Some(dbname.to_string());
280        self
281    }
282
283    /// Gets the name of the database to connect to, if one has been configured
284    /// with the `dbname` method.
285    pub fn get_dbname(&self) -> Option<&str> {
286        self.dbname.as_deref()
287    }
288
289    /// Sets command line options used to configure the server.
290    pub fn options(&mut self, options: &str) -> &mut Config {
291        self.options = Some(options.to_string());
292        self
293    }
294
295    /// Gets the command line options used to configure the server, if the
296    /// options have been set with the `options` method.
297    pub fn get_options(&self) -> Option<&str> {
298        self.options.as_deref()
299    }
300
301    /// Sets the value of the `application_name` runtime parameter.
302    pub fn application_name(&mut self, application_name: &str) -> &mut Config {
303        self.application_name = Some(application_name.to_string());
304        self
305    }
306
307    /// Gets the value of the `application_name` runtime parameter, if it has
308    /// been set with the `application_name` method.
309    pub fn get_application_name(&self) -> Option<&str> {
310        self.application_name.as_deref()
311    }
312
313    /// Sets the SSL configuration.
314    ///
315    /// Defaults to `prefer`.
316    pub fn ssl_mode(&mut self, ssl_mode: SslMode) -> &mut Config {
317        self.ssl_mode = ssl_mode;
318        self
319    }
320
321    /// Gets the SSL configuration.
322    pub fn get_ssl_mode(&self) -> SslMode {
323        self.ssl_mode
324    }
325
326    /// Adds a host to the configuration.
327    ///
328    /// Multiple hosts can be specified by calling this method multiple times, and each will be tried in order. On Unix
329    /// systems, a host starting with a `/` is interpreted as a path to a directory containing Unix domain sockets.
330    /// There must be either no hosts, or the same number of hosts as hostaddrs.
331    pub fn host(&mut self, host: &str) -> &mut Config {
332        #[cfg(unix)]
333        {
334            if host.starts_with('/') {
335                return self.host_path(host);
336            }
337        }
338
339        self.host.push(Host::Tcp(host.to_string()));
340        self
341    }
342
343    /// Gets the hosts that have been added to the configuration with `host`.
344    pub fn get_hosts(&self) -> &[Host] {
345        &self.host
346    }
347
348    /// Gets the hostaddrs that have been added to the configuration with `hostaddr`.
349    pub fn get_hostaddrs(&self) -> &[IpAddr] {
350        self.hostaddr.deref()
351    }
352
353    /// Adds a Unix socket host to the configuration.
354    ///
355    /// Unlike `host`, this method allows non-UTF8 paths.
356    #[cfg(unix)]
357    pub fn host_path<T>(&mut self, host: T) -> &mut Config
358    where
359        T: AsRef<Path>,
360    {
361        self.host.push(Host::Unix(host.as_ref().to_path_buf()));
362        self
363    }
364
365    /// Adds a hostaddr to the configuration.
366    ///
367    /// Multiple hostaddrs can be specified by calling this method multiple times, and each will be tried in order.
368    /// There must be either no hostaddrs, or the same number of hostaddrs as hosts.
369    pub fn hostaddr(&mut self, hostaddr: IpAddr) -> &mut Config {
370        self.hostaddr.push(hostaddr);
371        self
372    }
373
374    /// Adds a port to the configuration.
375    ///
376    /// Multiple ports can be specified by calling this method multiple times. There must either be no ports, in which
377    /// case the default of 5432 is used, a single port, in which it is used for all hosts, or the same number of ports
378    /// as hosts.
379    pub fn port(&mut self, port: u16) -> &mut Config {
380        self.port.push(port);
381        self
382    }
383
384    /// Gets the ports that have been added to the configuration with `port`.
385    pub fn get_ports(&self) -> &[u16] {
386        &self.port
387    }
388
389    /// Sets the timeout applied to socket-level connection attempts.
390    ///
391    /// Note that hostnames can resolve to multiple IP addresses, and this timeout will apply to each address of each
392    /// host separately. Defaults to no limit.
393    pub fn connect_timeout(&mut self, connect_timeout: Duration) -> &mut Config {
394        self.connect_timeout = Some(connect_timeout);
395        self
396    }
397
398    /// Gets the connection timeout, if one has been set with the
399    /// `connect_timeout` method.
400    pub fn get_connect_timeout(&self) -> Option<&Duration> {
401        self.connect_timeout.as_ref()
402    }
403
404    /// Sets the TCP user timeout.
405    ///
406    /// This is ignored for Unix domain socket connections. It is only supported on systems where
407    /// TCP_USER_TIMEOUT is available and will default to the system default if omitted or set to 0;
408    /// on other systems, it has no effect.
409    pub fn tcp_user_timeout(&mut self, tcp_user_timeout: Duration) -> &mut Config {
410        self.tcp_user_timeout = Some(tcp_user_timeout);
411        self
412    }
413
414    /// Gets the TCP user timeout, if one has been set with the
415    /// `user_timeout` method.
416    pub fn get_tcp_user_timeout(&self) -> Option<&Duration> {
417        self.tcp_user_timeout.as_ref()
418    }
419
420    /// Controls the use of TCP keepalive.
421    ///
422    /// This is ignored for Unix domain socket connections. Defaults to `true`.
423    pub fn keepalives(&mut self, keepalives: bool) -> &mut Config {
424        self.keepalives = keepalives;
425        self
426    }
427
428    /// Reports whether TCP keepalives will be used.
429    pub fn get_keepalives(&self) -> bool {
430        self.keepalives
431    }
432
433    /// Sets the amount of idle time before a keepalive packet is sent on the connection.
434    ///
435    /// This is ignored for Unix domain sockets, or if the `keepalives` option is disabled. Defaults to 2 hours.
436    #[cfg(not(target_arch = "wasm32"))]
437    pub fn keepalives_idle(&mut self, keepalives_idle: Duration) -> &mut Config {
438        self.keepalive_config.idle = keepalives_idle;
439        self
440    }
441
442    /// Gets the configured amount of idle time before a keepalive packet will
443    /// be sent on the connection.
444    #[cfg(not(target_arch = "wasm32"))]
445    pub fn get_keepalives_idle(&self) -> Duration {
446        self.keepalive_config.idle
447    }
448
449    /// Sets the time interval between TCP keepalive probes.
450    /// On Windows, this sets the value of the tcp_keepalive struct’s keepaliveinterval field.
451    ///
452    /// This is ignored for Unix domain sockets, or if the `keepalives` option is disabled.
453    #[cfg(not(target_arch = "wasm32"))]
454    pub fn keepalives_interval(&mut self, keepalives_interval: Duration) -> &mut Config {
455        self.keepalive_config.interval = Some(keepalives_interval);
456        self
457    }
458
459    /// Gets the time interval between TCP keepalive probes.
460    #[cfg(not(target_arch = "wasm32"))]
461    pub fn get_keepalives_interval(&self) -> Option<Duration> {
462        self.keepalive_config.interval
463    }
464
465    /// Sets the maximum number of TCP keepalive probes that will be sent before dropping a connection.
466    ///
467    /// This is ignored for Unix domain sockets, or if the `keepalives` option is disabled.
468    #[cfg(not(target_arch = "wasm32"))]
469    pub fn keepalives_retries(&mut self, keepalives_retries: u32) -> &mut Config {
470        self.keepalive_config.retries = Some(keepalives_retries);
471        self
472    }
473
474    /// Gets the maximum number of TCP keepalive probes that will be sent before dropping a connection.
475    #[cfg(not(target_arch = "wasm32"))]
476    pub fn get_keepalives_retries(&self) -> Option<u32> {
477        self.keepalive_config.retries
478    }
479
480    /// Sets the requirements of the session.
481    ///
482    /// This can be used to connect to the primary server in a clustered database rather than one of the read-only
483    /// secondary servers. Defaults to `Any`.
484    pub fn target_session_attrs(
485        &mut self,
486        target_session_attrs: TargetSessionAttrs,
487    ) -> &mut Config {
488        self.target_session_attrs = target_session_attrs;
489        self
490    }
491
492    /// Gets the requirements of the session.
493    pub fn get_target_session_attrs(&self) -> TargetSessionAttrs {
494        self.target_session_attrs
495    }
496
497    /// Sets the channel binding behavior.
498    ///
499    /// Defaults to `prefer`.
500    pub fn channel_binding(&mut self, channel_binding: ChannelBinding) -> &mut Config {
501        self.channel_binding = channel_binding;
502        self
503    }
504
505    /// Gets the channel binding behavior.
506    pub fn get_channel_binding(&self) -> ChannelBinding {
507        self.channel_binding
508    }
509
510    /// Sets the host load balancing behavior.
511    ///
512    /// Defaults to `disable`.
513    pub fn load_balance_hosts(&mut self, load_balance_hosts: LoadBalanceHosts) -> &mut Config {
514        self.load_balance_hosts = load_balance_hosts;
515        self
516    }
517
518    /// Gets the host load balancing behavior.
519    pub fn get_load_balance_hosts(&self) -> LoadBalanceHosts {
520        self.load_balance_hosts
521    }
522
523    fn param(&mut self, key: &str, value: &str) -> Result<(), Error> {
524        match key {
525            "user" => {
526                self.user(value);
527            }
528            "password" => {
529                self.password(value);
530            }
531            "dbname" => {
532                self.dbname(value);
533            }
534            "options" => {
535                self.options(value);
536            }
537            "application_name" => {
538                self.application_name(value);
539            }
540            "sslmode" => {
541                let mode = match value {
542                    "disable" => SslMode::Disable,
543                    "prefer" => SslMode::Prefer,
544                    "require" => SslMode::Require,
545                    _ => return Err(Error::config_parse(Box::new(InvalidValue("sslmode")))),
546                };
547                self.ssl_mode(mode);
548            }
549            "host" => {
550                for host in value.split(',') {
551                    self.host(host);
552                }
553            }
554            "hostaddr" => {
555                for hostaddr in value.split(',') {
556                    let addr = hostaddr
557                        .parse()
558                        .map_err(|_| Error::config_parse(Box::new(InvalidValue("hostaddr"))))?;
559                    self.hostaddr(addr);
560                }
561            }
562            "port" => {
563                for port in value.split(',') {
564                    let port = if port.is_empty() {
565                        5432
566                    } else {
567                        port.parse()
568                            .map_err(|_| Error::config_parse(Box::new(InvalidValue("port"))))?
569                    };
570                    self.port(port);
571                }
572            }
573            "connect_timeout" => {
574                let timeout = value
575                    .parse::<i64>()
576                    .map_err(|_| Error::config_parse(Box::new(InvalidValue("connect_timeout"))))?;
577                if timeout > 0 {
578                    self.connect_timeout(Duration::from_secs(timeout as u64));
579                }
580            }
581            "tcp_user_timeout" => {
582                let timeout = value
583                    .parse::<i64>()
584                    .map_err(|_| Error::config_parse(Box::new(InvalidValue("tcp_user_timeout"))))?;
585                if timeout > 0 {
586                    self.tcp_user_timeout(Duration::from_secs(timeout as u64));
587                }
588            }
589            #[cfg(not(target_arch = "wasm32"))]
590            "keepalives" => {
591                let keepalives = value
592                    .parse::<u64>()
593                    .map_err(|_| Error::config_parse(Box::new(InvalidValue("keepalives"))))?;
594                self.keepalives(keepalives != 0);
595            }
596            #[cfg(not(target_arch = "wasm32"))]
597            "keepalives_idle" => {
598                let keepalives_idle = value
599                    .parse::<i64>()
600                    .map_err(|_| Error::config_parse(Box::new(InvalidValue("keepalives_idle"))))?;
601                if keepalives_idle > 0 {
602                    self.keepalives_idle(Duration::from_secs(keepalives_idle as u64));
603                }
604            }
605            #[cfg(not(target_arch = "wasm32"))]
606            "keepalives_interval" => {
607                let keepalives_interval = value.parse::<i64>().map_err(|_| {
608                    Error::config_parse(Box::new(InvalidValue("keepalives_interval")))
609                })?;
610                if keepalives_interval > 0 {
611                    self.keepalives_interval(Duration::from_secs(keepalives_interval as u64));
612                }
613            }
614            #[cfg(not(target_arch = "wasm32"))]
615            "keepalives_retries" => {
616                let keepalives_retries = value.parse::<u32>().map_err(|_| {
617                    Error::config_parse(Box::new(InvalidValue("keepalives_retries")))
618                })?;
619                self.keepalives_retries(keepalives_retries);
620            }
621            "target_session_attrs" => {
622                let target_session_attrs = match value {
623                    "any" => TargetSessionAttrs::Any,
624                    "read-write" => TargetSessionAttrs::ReadWrite,
625                    _ => {
626                        return Err(Error::config_parse(Box::new(InvalidValue(
627                            "target_session_attrs",
628                        ))));
629                    }
630                };
631                self.target_session_attrs(target_session_attrs);
632            }
633            "channel_binding" => {
634                let channel_binding = match value {
635                    "disable" => ChannelBinding::Disable,
636                    "prefer" => ChannelBinding::Prefer,
637                    "require" => ChannelBinding::Require,
638                    _ => {
639                        return Err(Error::config_parse(Box::new(InvalidValue(
640                            "channel_binding",
641                        ))))
642                    }
643                };
644                self.channel_binding(channel_binding);
645            }
646            "load_balance_hosts" => {
647                let load_balance_hosts = match value {
648                    "disable" => LoadBalanceHosts::Disable,
649                    "random" => LoadBalanceHosts::Random,
650                    _ => {
651                        return Err(Error::config_parse(Box::new(InvalidValue(
652                            "load_balance_hosts",
653                        ))))
654                    }
655                };
656                self.load_balance_hosts(load_balance_hosts);
657            }
658            key => {
659                return Err(Error::config_parse(Box::new(UnknownOption(
660                    key.to_string(),
661                ))));
662            }
663        }
664
665        Ok(())
666    }
667
668    /// Opens a connection to a PostgreSQL database.
669    ///
670    /// Requires the `runtime` Cargo feature (enabled by default).
671    #[cfg(feature = "runtime")]
672    pub async fn connect<T>(&self, tls: T) -> Result<(Client, Connection<Socket, T::Stream>), Error>
673    where
674        T: MakeTlsConnect<Socket>,
675    {
676        connect(tls, self).await
677    }
678
679    /// Connects to a PostgreSQL database over an arbitrary stream.
680    ///
681    /// All of the settings other than `user`, `password`, `dbname`, `options`, and `application_name` name are ignored.
682    pub async fn connect_raw<S, T>(
683        &self,
684        stream: S,
685        tls: T,
686    ) -> Result<(Client, Connection<S, T::Stream>), Error>
687    where
688        S: AsyncRead + AsyncWrite + Unpin,
689        T: TlsConnect<S>,
690    {
691        connect_raw(stream, tls, true, self).await
692    }
693}
694
695impl FromStr for Config {
696    type Err = Error;
697
698    fn from_str(s: &str) -> Result<Config, Error> {
699        match UrlParser::parse(s)? {
700            Some(config) => Ok(config),
701            None => Parser::parse(s),
702        }
703    }
704}
705
706// Omit password from debug output
707impl fmt::Debug for Config {
708    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
709        struct Redaction {}
710        impl fmt::Debug for Redaction {
711            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
712                write!(f, "_")
713            }
714        }
715
716        let mut config_dbg = &mut f.debug_struct("Config");
717        config_dbg = config_dbg
718            .field("user", &self.user)
719            .field("password", &self.password.as_ref().map(|_| Redaction {}))
720            .field("dbname", &self.dbname)
721            .field("options", &self.options)
722            .field("application_name", &self.application_name)
723            .field("ssl_mode", &self.ssl_mode)
724            .field("host", &self.host)
725            .field("hostaddr", &self.hostaddr)
726            .field("port", &self.port)
727            .field("connect_timeout", &self.connect_timeout)
728            .field("tcp_user_timeout", &self.tcp_user_timeout)
729            .field("keepalives", &self.keepalives);
730
731        #[cfg(not(target_arch = "wasm32"))]
732        {
733            config_dbg = config_dbg
734                .field("keepalives_idle", &self.keepalive_config.idle)
735                .field("keepalives_interval", &self.keepalive_config.interval)
736                .field("keepalives_retries", &self.keepalive_config.retries);
737        }
738
739        config_dbg
740            .field("target_session_attrs", &self.target_session_attrs)
741            .field("channel_binding", &self.channel_binding)
742            .finish()
743    }
744}
745
746#[derive(Debug)]
747struct UnknownOption(String);
748
749impl fmt::Display for UnknownOption {
750    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
751        write!(fmt, "unknown option `{}`", self.0)
752    }
753}
754
755impl error::Error for UnknownOption {}
756
757#[derive(Debug)]
758struct InvalidValue(&'static str);
759
760impl fmt::Display for InvalidValue {
761    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
762        write!(fmt, "invalid value for option `{}`", self.0)
763    }
764}
765
766impl error::Error for InvalidValue {}
767
768struct Parser<'a> {
769    s: &'a str,
770    it: iter::Peekable<str::CharIndices<'a>>,
771}
772
773impl<'a> Parser<'a> {
774    fn parse(s: &'a str) -> Result<Config, Error> {
775        let mut parser = Parser {
776            s,
777            it: s.char_indices().peekable(),
778        };
779
780        let mut config = Config::new();
781
782        while let Some((key, value)) = parser.parameter()? {
783            config.param(key, &value)?;
784        }
785
786        Ok(config)
787    }
788
789    fn skip_ws(&mut self) {
790        self.take_while(char::is_whitespace);
791    }
792
793    fn take_while<F>(&mut self, f: F) -> &'a str
794    where
795        F: Fn(char) -> bool,
796    {
797        let start = match self.it.peek() {
798            Some(&(i, _)) => i,
799            None => return "",
800        };
801
802        loop {
803            match self.it.peek() {
804                Some(&(_, c)) if f(c) => {
805                    self.it.next();
806                }
807                Some(&(i, _)) => return &self.s[start..i],
808                None => return &self.s[start..],
809            }
810        }
811    }
812
813    fn eat(&mut self, target: char) -> Result<(), Error> {
814        match self.it.next() {
815            Some((_, c)) if c == target => Ok(()),
816            Some((i, c)) => {
817                let m = format!(
818                    "unexpected character at byte {}: expected `{}` but got `{}`",
819                    i, target, c
820                );
821                Err(Error::config_parse(m.into()))
822            }
823            None => Err(Error::config_parse("unexpected EOF".into())),
824        }
825    }
826
827    fn eat_if(&mut self, target: char) -> bool {
828        match self.it.peek() {
829            Some(&(_, c)) if c == target => {
830                self.it.next();
831                true
832            }
833            _ => false,
834        }
835    }
836
837    fn keyword(&mut self) -> Option<&'a str> {
838        let s = self.take_while(|c| match c {
839            c if c.is_whitespace() => false,
840            '=' => false,
841            _ => true,
842        });
843
844        if s.is_empty() {
845            None
846        } else {
847            Some(s)
848        }
849    }
850
851    fn value(&mut self) -> Result<String, Error> {
852        let value = if self.eat_if('\'') {
853            let value = self.quoted_value()?;
854            self.eat('\'')?;
855            value
856        } else {
857            self.simple_value()?
858        };
859
860        Ok(value)
861    }
862
863    fn simple_value(&mut self) -> Result<String, Error> {
864        let mut value = String::new();
865
866        while let Some(&(_, c)) = self.it.peek() {
867            if c.is_whitespace() {
868                break;
869            }
870
871            self.it.next();
872            if c == '\\' {
873                if let Some((_, c2)) = self.it.next() {
874                    value.push(c2);
875                }
876            } else {
877                value.push(c);
878            }
879        }
880
881        if value.is_empty() {
882            return Err(Error::config_parse("unexpected EOF".into()));
883        }
884
885        Ok(value)
886    }
887
888    fn quoted_value(&mut self) -> Result<String, Error> {
889        let mut value = String::new();
890
891        while let Some(&(_, c)) = self.it.peek() {
892            if c == '\'' {
893                return Ok(value);
894            }
895
896            self.it.next();
897            if c == '\\' {
898                if let Some((_, c2)) = self.it.next() {
899                    value.push(c2);
900                }
901            } else {
902                value.push(c);
903            }
904        }
905
906        Err(Error::config_parse(
907            "unterminated quoted connection parameter value".into(),
908        ))
909    }
910
911    fn parameter(&mut self) -> Result<Option<(&'a str, String)>, Error> {
912        self.skip_ws();
913        let keyword = match self.keyword() {
914            Some(keyword) => keyword,
915            None => return Ok(None),
916        };
917        self.skip_ws();
918        self.eat('=')?;
919        self.skip_ws();
920        let value = self.value()?;
921
922        Ok(Some((keyword, value)))
923    }
924}
925
926// This is a pretty sloppy "URL" parser, but it matches the behavior of libpq, where things really aren't very strict
927struct UrlParser<'a> {
928    s: &'a str,
929    config: Config,
930}
931
932impl<'a> UrlParser<'a> {
933    fn parse(s: &'a str) -> Result<Option<Config>, Error> {
934        let s = match Self::remove_url_prefix(s) {
935            Some(s) => s,
936            None => return Ok(None),
937        };
938
939        let mut parser = UrlParser {
940            s,
941            config: Config::new(),
942        };
943
944        parser.parse_credentials()?;
945        parser.parse_host()?;
946        parser.parse_path()?;
947        parser.parse_params()?;
948
949        Ok(Some(parser.config))
950    }
951
952    fn remove_url_prefix(s: &str) -> Option<&str> {
953        for prefix in &["postgres://", "postgresql://"] {
954            if let Some(stripped) = s.strip_prefix(prefix) {
955                return Some(stripped);
956            }
957        }
958
959        None
960    }
961
962    fn take_until(&mut self, end: &[char]) -> Option<&'a str> {
963        match self.s.find(end) {
964            Some(pos) => {
965                let (head, tail) = self.s.split_at(pos);
966                self.s = tail;
967                Some(head)
968            }
969            None => None,
970        }
971    }
972
973    fn take_all(&mut self) -> &'a str {
974        mem::take(&mut self.s)
975    }
976
977    fn eat_byte(&mut self) {
978        self.s = &self.s[1..];
979    }
980
981    fn parse_credentials(&mut self) -> Result<(), Error> {
982        let creds = match self.take_until(&['@']) {
983            Some(creds) => creds,
984            None => return Ok(()),
985        };
986        self.eat_byte();
987
988        let mut it = creds.splitn(2, ':');
989        let user = self.decode(it.next().unwrap())?;
990        self.config.user(&user);
991
992        if let Some(password) = it.next() {
993            let password = Cow::from(percent_encoding::percent_decode(password.as_bytes()));
994            self.config.password(password);
995        }
996
997        Ok(())
998    }
999
1000    fn parse_host(&mut self) -> Result<(), Error> {
1001        let host = match self.take_until(&['/', '?']) {
1002            Some(host) => host,
1003            None => self.take_all(),
1004        };
1005
1006        if host.is_empty() {
1007            return Ok(());
1008        }
1009
1010        for chunk in host.split(',') {
1011            let (host, port) = if chunk.starts_with('[') {
1012                let idx = match chunk.find(']') {
1013                    Some(idx) => idx,
1014                    None => return Err(Error::config_parse(InvalidValue("host").into())),
1015                };
1016
1017                let host = &chunk[1..idx];
1018                let remaining = &chunk[idx + 1..];
1019                let port = if let Some(port) = remaining.strip_prefix(':') {
1020                    Some(port)
1021                } else if remaining.is_empty() {
1022                    None
1023                } else {
1024                    return Err(Error::config_parse(InvalidValue("host").into()));
1025                };
1026
1027                (host, port)
1028            } else {
1029                let mut it = chunk.splitn(2, ':');
1030                (it.next().unwrap(), it.next())
1031            };
1032
1033            self.host_param(host)?;
1034            let port = self.decode(port.unwrap_or("5432"))?;
1035            self.config.param("port", &port)?;
1036        }
1037
1038        Ok(())
1039    }
1040
1041    fn parse_path(&mut self) -> Result<(), Error> {
1042        if !self.s.starts_with('/') {
1043            return Ok(());
1044        }
1045        self.eat_byte();
1046
1047        let dbname = match self.take_until(&['?']) {
1048            Some(dbname) => dbname,
1049            None => self.take_all(),
1050        };
1051
1052        if !dbname.is_empty() {
1053            self.config.dbname(&self.decode(dbname)?);
1054        }
1055
1056        Ok(())
1057    }
1058
1059    fn parse_params(&mut self) -> Result<(), Error> {
1060        if !self.s.starts_with('?') {
1061            return Ok(());
1062        }
1063        self.eat_byte();
1064
1065        while !self.s.is_empty() {
1066            let key = match self.take_until(&['=']) {
1067                Some(key) => self.decode(key)?,
1068                None => return Err(Error::config_parse("unterminated parameter".into())),
1069            };
1070            self.eat_byte();
1071
1072            let value = match self.take_until(&['&']) {
1073                Some(value) => {
1074                    self.eat_byte();
1075                    value
1076                }
1077                None => self.take_all(),
1078            };
1079
1080            if key == "host" {
1081                self.host_param(value)?;
1082            } else {
1083                let value = self.decode(value)?;
1084                self.config.param(&key, &value)?;
1085            }
1086        }
1087
1088        Ok(())
1089    }
1090
1091    #[cfg(unix)]
1092    fn host_param(&mut self, s: &str) -> Result<(), Error> {
1093        let decoded = Cow::from(percent_encoding::percent_decode(s.as_bytes()));
1094        if decoded.first() == Some(&b'/') {
1095            self.config.host_path(OsStr::from_bytes(&decoded));
1096        } else {
1097            let decoded = str::from_utf8(&decoded).map_err(|e| Error::config_parse(Box::new(e)))?;
1098            self.config.host(decoded);
1099        }
1100
1101        Ok(())
1102    }
1103
1104    #[cfg(not(unix))]
1105    fn host_param(&mut self, s: &str) -> Result<(), Error> {
1106        let s = self.decode(s)?;
1107        self.config.param("host", &s)
1108    }
1109
1110    fn decode(&self, s: &'a str) -> Result<Cow<'a, str>, Error> {
1111        percent_encoding::percent_decode(s.as_bytes())
1112            .decode_utf8()
1113            .map_err(|e| Error::config_parse(e.into()))
1114    }
1115}
1116
1117#[cfg(test)]
1118mod tests {
1119    use std::net::IpAddr;
1120
1121    use crate::{config::Host, Config};
1122
1123    #[test]
1124    fn test_simple_parsing() {
1125        let s = "user=pass_user dbname=postgres host=host1,host2 hostaddr=127.0.0.1,127.0.0.2 port=26257";
1126        let config = s.parse::<Config>().unwrap();
1127        assert_eq!(Some("pass_user"), config.get_user());
1128        assert_eq!(Some("postgres"), config.get_dbname());
1129        assert_eq!(
1130            [
1131                Host::Tcp("host1".to_string()),
1132                Host::Tcp("host2".to_string())
1133            ],
1134            config.get_hosts(),
1135        );
1136
1137        assert_eq!(
1138            [
1139                "127.0.0.1".parse::<IpAddr>().unwrap(),
1140                "127.0.0.2".parse::<IpAddr>().unwrap()
1141            ],
1142            config.get_hostaddrs(),
1143        );
1144
1145        assert_eq!(1, 1);
1146    }
1147
1148    #[test]
1149    fn test_invalid_hostaddr_parsing() {
1150        let s = "user=pass_user dbname=postgres host=host1 hostaddr=127.0.0 port=26257";
1151        s.parse::<Config>().err().unwrap();
1152    }
1153}