rustis/client/
config.rs

1use crate::{Error, Result};
2#[cfg(feature = "native-tls")]
3use native_tls::{Certificate, Identity, Protocol, TlsConnector, TlsConnectorBuilder};
4#[cfg(feature = "rustls")]
5use std::sync::Arc;
6use std::{
7    collections::HashMap,
8    fmt::{self, Display, Write},
9    str::FromStr,
10    time::Duration,
11};
12use url::Url;
13
14const DEFAULT_PORT: u16 = 6379;
15const DEFAULT_DATABASE: usize = 0;
16const DEFAULT_WAIT_BETWEEN_FAILURES: u64 = 250;
17const DEFAULT_CONNECT_TIMEOUT: u64 = 10_000;
18const DEFAULT_COMMAND_TIMEOUT: u64 = 0;
19const DEFAULT_AUTO_RESUBSCRTBE: bool = true;
20const DEFAULT_AUTO_REMONITOR: bool = true;
21const DEFAULT_KEEP_ALIVE: Option<Duration> = None;
22const DEFAULT_NO_DELAY: bool = true;
23const DEFAULT_RETRY_ON_ERROR: bool = false;
24
25type Uri<'a> = (
26    &'a str,
27    Option<&'a str>,
28    Option<&'a str>,
29    Vec<(&'a str, u16)>,
30    Vec<&'a str>,
31    Option<HashMap<String, String>>,
32);
33
34/// Configuration options for a [`client`](crate::client::Client)
35/// or a [`pooled client`](crate::client::PooledClientManager)
36#[derive(Debug, Clone)]
37pub struct Config {
38    /// Connection server configuration (standalone, sentinel, or cluster)
39    pub server: ServerConfig,
40    /// An optional ACL username for authentication.
41    ///
42    /// See [`ACL`](https://redis.io/docs/management/security/acl/)
43    pub username: Option<String>,
44    /// An optional password for authentication.
45    ///
46    /// The password could be either coupled with an ACL username either used alone.
47    ///
48    /// See:
49    /// * [`ACL`](https://redis.io/docs/management/security/acl/)
50    /// * [`Authentication`](https://redis.io/docs/management/security/#authentication)
51    pub password: Option<String>,
52    /// The default database for this connection.
53    ///
54    /// If `database` is not set to `0`, a [`SELECT`](https://redis.io/commands/select/)
55    /// command will be automatically issued at connection or reconnection.
56    pub database: usize,
57    /// An optional TLS configuration.
58    #[cfg_attr(docsrs, doc(cfg(any(feature = "native-tls", feature = "rustls"))))]
59    #[cfg(any(feature = "native-tls", feature = "rustls"))]
60    pub tls_config: Option<TlsConfig>,
61    /// The time to attempt a connection before timing out. The default is 10 seconds
62    pub connect_timeout: Duration,
63    /// If a command does not return a reply within a set number of milliseconds,
64    /// a timeout error will be thrown.
65    ///
66    /// If set to 0, no timeout is apply
67    ///
68    /// The default is 0
69    pub command_timeout: Duration,
70    /// When the client reconnects, channels subscribed in the previous connection will be
71    /// resubscribed automatically if `auto_resubscribe` is `true`.
72    ///
73    /// The default is `true`
74    pub auto_resubscribe: bool,
75    /// When the client reconnects, if in `monitor` mode, the
76    /// [`monitor`](crate::commands::BlockingCommands::monitor) command
77    /// will be resent automatically
78    ///
79    /// The default is `true`
80    pub auto_remonitor: bool,
81    /// Set the name of the connection to make it easier to identity the connection in client list.
82    ///
83    /// See [`client_setname`](crate::commands::ConnectionCommands::client_setname)
84    pub connection_name: String,
85    /// Enable/disable keep-alive functionality (default `None`)
86    ///
87    /// See [`TcpKeepAlive::with_time`](https://docs.rs/socket2/latest/socket2/struct.TcpKeepalive.html#method.with_time)
88    pub keep_alive: Option<Duration>,
89    /// Enable/disable the use of Nagle's algorithm (default `true`)
90    ///
91    /// See [`TcpStream::set_nodelay`](https://docs.rs/tokio/latest/tokio/net/struct.TcpStream.html#method.set_nodelay)    
92    pub no_delay: bool,
93    /// Defines the default strategy for retries on network error (default `false`):
94    /// * `true` - retry sending the command/batch of commands on network error
95    /// * `false` - do not retry sending the command/batch of commands on network error
96    ///
97    /// This strategy can be overriden for each command/batch
98    /// of commands in the following functions:
99    /// * [`PreparedCommand::retry_on_error`](crate::client::PreparedCommand::retry_on_error)
100    /// * [`Pipeline::retry_on_error`](crate::client::Pipeline::retry_on_error)
101    /// * [`Transaction::retry_on_error`](crate::client::Transaction::retry_on_error)
102    /// * [`Client::send`](crate::client::Client::send)
103    /// * [`Client::send_and_forget`](crate::client::Client::send_and_forget)
104    /// * [`Client::send_batch`](crate::client::Client::send_batch)
105    pub retry_on_error: bool,
106    /// Reconnection policy configuration (Constant, Linear or Exponential)
107    pub reconnection: ReconnectionConfig,
108}
109
110impl Default for Config {
111    fn default() -> Self {
112        Self {
113            server: Default::default(),
114            username: Default::default(),
115            password: Default::default(),
116            database: Default::default(),
117            #[cfg(any(feature = "native-tls", feature = "rustls"))]
118            tls_config: Default::default(),
119            connect_timeout: Duration::from_millis(DEFAULT_CONNECT_TIMEOUT),
120            command_timeout: Duration::from_millis(DEFAULT_COMMAND_TIMEOUT),
121            auto_resubscribe: DEFAULT_AUTO_RESUBSCRTBE,
122            auto_remonitor: DEFAULT_AUTO_REMONITOR,
123            connection_name: String::from(""),
124            keep_alive: DEFAULT_KEEP_ALIVE,
125            no_delay: DEFAULT_NO_DELAY,
126            retry_on_error: DEFAULT_RETRY_ON_ERROR,
127            reconnection: Default::default(),
128        }
129    }
130}
131
132impl FromStr for Config {
133    type Err = Error;
134
135    /// Build a config from an URI or a standard address format `host`:`port`
136    fn from_str(str: &str) -> Result<Config> {
137        if let Some(config) = Self::parse_uri(str) {
138            Ok(config)
139        } else if let Some(addr) = Self::parse_addr(str) {
140            addr.into_config()
141        } else {
142            Err(Error::Config(format!("Cannot parse config from {str}")))
143        }
144    }
145}
146
147impl Config {
148    /// Build a config from an URI in the format `redis[s]://[[username]:password@]host[:port]/[database]`
149    pub fn from_uri(uri: Url) -> Result<Config> {
150        Self::from_str(uri.as_str())
151    }
152
153    /// Parse address in the standard formart `host`:`port`
154    fn parse_addr(str: &str) -> Option<(&str, u16)> {
155        let mut iter = str.split(':');
156
157        match (iter.next(), iter.next(), iter.next()) {
158            (Some(host), Some(port), None) => {
159                if let Ok(port) = port.parse::<u16>() {
160                    Some((host, port))
161                } else {
162                    None
163                }
164            }
165            (Some(host), None, None) => Some((host, DEFAULT_PORT)),
166            _ => None,
167        }
168    }
169
170    fn parse_uri(uri: &str) -> Option<Config> {
171        let (scheme, username, password, hosts, path_segments, mut query) =
172            Self::break_down_uri(uri)?;
173        let mut hosts = hosts;
174        let mut path_segments = path_segments.into_iter();
175
176        enum ServerType {
177            Standalone,
178            Sentinel,
179            Cluster,
180        }
181
182        #[cfg(any(feature = "native-tls", feature = "rustls"))]
183        let (tls_config, server_type) = match scheme {
184            "redis" => (None, ServerType::Standalone),
185            "rediss" => (Some(TlsConfig::default()), ServerType::Standalone),
186            "redis+sentinel" | "redis-sentinel" => (None, ServerType::Sentinel),
187            "rediss+sentinel" | "rediss-sentinel" => {
188                (Some(TlsConfig::default()), ServerType::Sentinel)
189            }
190            "redis+cluster" | "redis-cluster" => (None, ServerType::Cluster),
191            "rediss+cluster" | "rediss-cluster" => {
192                (Some(TlsConfig::default()), ServerType::Cluster)
193            }
194            _ => {
195                return None;
196            }
197        };
198
199        #[cfg(not(any(feature = "native-tls", feature = "rustls")))]
200        let server_type = match scheme {
201            "redis" => ServerType::Standalone,
202            "redis+sentinel" | "redis-sentinel" => ServerType::Sentinel,
203            "redis+cluster" | "redis-cluster" => ServerType::Cluster,
204            _ => {
205                return None;
206            }
207        };
208
209        let server = match server_type {
210            ServerType::Standalone => {
211                if hosts.len() > 1 {
212                    return None;
213                } else {
214                    let (host, port) = hosts.pop()?;
215                    ServerConfig::Standalone {
216                        host: host.to_owned(),
217                        port,
218                    }
219                }
220            }
221            ServerType::Sentinel => {
222                let instances = hosts
223                    .iter()
224                    .map(|(host, port)| ((*host).to_owned(), *port))
225                    .collect::<Vec<_>>();
226
227                let service_name = match path_segments.next() {
228                    Some(service_name) => service_name.to_owned(),
229                    None => {
230                        return None;
231                    }
232                };
233
234                let mut sentinel_config = SentinelConfig {
235                    instances,
236                    service_name,
237                    ..Default::default()
238                };
239
240                if let Some(ref mut query) = query {
241                    if let Some(millis) = query.remove("wait_between_failures")
242                        && let Ok(millis) = millis.parse::<u64>()
243                    {
244                        sentinel_config.wait_between_failures = Duration::from_millis(millis);
245                    }
246
247                    sentinel_config.username = query.remove("sentinel_username");
248                    sentinel_config.password = query.remove("sentinel_password");
249                }
250
251                ServerConfig::Sentinel(sentinel_config)
252            }
253            ServerType::Cluster => {
254                let nodes = hosts
255                    .iter()
256                    .map(|(host, port)| ((*host).to_owned(), *port))
257                    .collect::<Vec<_>>();
258
259                ServerConfig::Cluster(ClusterConfig { nodes })
260            }
261        };
262
263        let database = match path_segments.next() {
264            Some(database) => match database.parse::<usize>() {
265                Ok(database) => database,
266                Err(_) => {
267                    return None;
268                }
269            },
270            None => DEFAULT_DATABASE,
271        };
272
273        let mut config = Config {
274            server,
275            username: username.map(|u| u.to_owned()),
276            password: password.map(|p| p.to_owned()),
277            database,
278            #[cfg(any(feature = "native-tls", feature = "rustls"))]
279            tls_config,
280            ..Default::default()
281        };
282
283        if let Some(ref mut query) = query {
284            if let Some(millis) = query.remove("connect_timeout")
285                && let Ok(millis) = millis.parse::<u64>()
286            {
287                config.connect_timeout = Duration::from_millis(millis);
288            }
289
290            if let Some(millis) = query.remove("command_timeout")
291                && let Ok(millis) = millis.parse::<u64>()
292            {
293                config.command_timeout = Duration::from_millis(millis);
294            }
295
296            if let Some(auto_resubscribe) = query.remove("auto_resubscribe")
297                && let Ok(auto_resubscribe) = auto_resubscribe.parse::<bool>()
298            {
299                config.auto_resubscribe = auto_resubscribe;
300            }
301
302            if let Some(auto_remonitor) = query.remove("auto_remonitor")
303                && let Ok(auto_remonitor) = auto_remonitor.parse::<bool>()
304            {
305                config.auto_remonitor = auto_remonitor;
306            }
307
308            if let Some(connection_name) = query.remove("connection_name") {
309                config.connection_name = connection_name;
310            }
311
312            if let Some(keep_alive) = query.remove("keep_alive")
313                && let Ok(keep_alive) = keep_alive.parse::<u64>()
314            {
315                config.keep_alive = Some(Duration::from_millis(keep_alive));
316            }
317
318            if let Some(no_delay) = query.remove("no_delay")
319                && let Ok(no_delay) = no_delay.parse::<bool>()
320            {
321                config.no_delay = no_delay;
322            }
323
324            if let Some(retry_on_error) = query.remove("retry_on_error")
325                && let Ok(retry_on_error) = retry_on_error.parse::<bool>()
326            {
327                config.retry_on_error = retry_on_error;
328            }
329        }
330
331        Some(config)
332    }
333
334    /// break down an uri in a tuple (scheme, username, password, hosts, path_segments)
335    fn break_down_uri<'a>(uri: &'a str) -> Option<Uri<'a>> {
336        let end_of_scheme = match uri.find("://") {
337            Some(index) => index,
338            None => {
339                return None;
340            }
341        };
342
343        let scheme = &uri[..end_of_scheme];
344
345        let after_scheme = &uri[end_of_scheme + 3..];
346
347        let (before_query, query) = match after_scheme.find('?') {
348            Some(index) => match Self::exclusive_split_at(after_scheme, index) {
349                (Some(before_query), after_query) => (before_query, after_query),
350                _ => {
351                    return None;
352                }
353            },
354            None => (after_scheme, None),
355        };
356
357        let (authority, path) = match before_query.find('/') {
358            Some(index) => match Self::exclusive_split_at(before_query, index) {
359                (Some(authority), path) => (authority, path),
360                _ => {
361                    return None;
362                }
363            },
364            None => (before_query, None),
365        };
366
367        let (user_info, hosts) = match authority.rfind('@') {
368            Some(index) => {
369                // if '@' is in the host section, it MUST be interpreted as a request for
370                // authentication, even if the credentials are empty.
371                let (user_info, hosts) = Self::exclusive_split_at(authority, index);
372                match hosts {
373                    Some(hosts) => (user_info, hosts),
374                    None => {
375                        // missing hosts
376                        return None;
377                    }
378                }
379            }
380            None => (None, authority),
381        };
382
383        let (username, password) = match user_info {
384            Some(user_info) => match user_info.find(':') {
385                Some(index) => match Self::exclusive_split_at(user_info, index) {
386                    (username, None) => (username, Some("")),
387                    (username, password) => (username, password),
388                },
389                None => {
390                    // username without password is not accepted
391                    return None;
392                }
393            },
394            None => (None, None),
395        };
396
397        let hosts = hosts
398            .split(',')
399            .map(Self::parse_addr)
400            .collect::<Option<Vec<_>>>();
401        let hosts = hosts?;
402
403        let path_segments = match path {
404            Some(path) => path.split('/').collect::<Vec<_>>(),
405            None => Vec::new(),
406        };
407
408        let query = match query.map(|q| {
409            q.split('&')
410                .map(|s| s.split_once('=').map(|(k, v)| (k.to_owned(), v.to_owned())))
411                .collect::<Option<HashMap<String, String>>>()
412        }) {
413            Some(Some(query)) => Some(query),
414            Some(None) => return None,
415            None => None,
416        };
417
418        Some((scheme, username, password, hosts, path_segments, query))
419    }
420
421    /// Splits a string into a section before a given index and a section exclusively after the index.
422    /// Empty portions are returned as `None`.
423    fn exclusive_split_at(s: &str, i: usize) -> (Option<&str>, Option<&str>) {
424        let (l, r) = s.split_at(i);
425
426        let lout = if !l.is_empty() { Some(l) } else { None };
427        let rout = if r.len() > 1 { Some(&r[1..]) } else { None };
428
429        (lout, rout)
430    }
431}
432
433impl Display for Config {
434    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
435        #[cfg(any(feature = "native-tls", feature = "rustls"))]
436        if self.tls_config.is_some() {
437            match &self.server {
438                ServerConfig::Standalone { host: _, port: _ } => f.write_str("rediss://")?,
439                ServerConfig::Sentinel(_) => f.write_str("rediss+sentinel://")?,
440                ServerConfig::Cluster(_) => f.write_str("rediss+cluster://")?,
441            }
442        } else {
443            match &self.server {
444                ServerConfig::Standalone { host: _, port: _ } => f.write_str("redis://")?,
445                ServerConfig::Sentinel(_) => f.write_str("redis+sentinel://")?,
446                ServerConfig::Cluster(_) => f.write_str("redis+cluster://")?,
447            }
448        }
449
450        #[cfg(not(any(feature = "native-tls", feature = "rustls")))]
451        match &self.server {
452            ServerConfig::Standalone { host: _, port: _ } => f.write_str("redis://")?,
453            ServerConfig::Sentinel(_) => f.write_str("redis+sentinel://")?,
454            ServerConfig::Cluster(_) => f.write_str("redis+cluster://")?,
455        }
456
457        if let Some(username) = &self.username {
458            f.write_str(username)?;
459        }
460
461        if let Some(password) = &self.password {
462            f.write_char(':')?;
463            f.write_str(password)?;
464            f.write_char('@')?;
465        }
466
467        match &self.server {
468            ServerConfig::Standalone { host, port } => {
469                f.write_str(host)?;
470                if *port != DEFAULT_PORT {
471                    f.write_char(':')?;
472                    f.write_str(&port.to_string())?;
473                }
474            }
475            ServerConfig::Sentinel(SentinelConfig {
476                instances,
477                service_name,
478                wait_between_failures: _,
479                password: _,
480                username: _,
481            }) => {
482                f.write_str(
483                    &instances
484                        .iter()
485                        .map(|(host, port)| format!("{host}:{port}"))
486                        .collect::<Vec<String>>()
487                        .join(","),
488                )?;
489                f.write_char('/')?;
490                f.write_str(service_name)?;
491            }
492            ServerConfig::Cluster(ClusterConfig { nodes }) => {
493                f.write_str(
494                    &nodes
495                        .iter()
496                        .map(|(host, port)| format!("{host}:{port}"))
497                        .collect::<Vec<String>>()
498                        .join(","),
499                )?;
500            }
501        }
502
503        if self.database > 0 {
504            f.write_char('/')?;
505            f.write_str(&self.database.to_string())?;
506        }
507
508        // query
509
510        let mut query_separator = false;
511
512        let connect_timeout = self.connect_timeout.as_millis() as u64;
513        if connect_timeout != DEFAULT_CONNECT_TIMEOUT {
514            if !query_separator {
515                query_separator = true;
516                f.write_char('?')?;
517            } else {
518                f.write_char('&')?;
519            }
520            f.write_fmt(format_args!("connect_timeout={connect_timeout}"))?;
521        }
522
523        let command_timeout = self.command_timeout.as_millis() as u64;
524        if command_timeout != DEFAULT_COMMAND_TIMEOUT {
525            if !query_separator {
526                query_separator = true;
527                f.write_char('?')?;
528            } else {
529                f.write_char('&')?;
530            }
531            f.write_fmt(format_args!("command_timeout={command_timeout}"))?;
532        }
533
534        if self.auto_resubscribe != DEFAULT_AUTO_RESUBSCRTBE {
535            if !query_separator {
536                query_separator = true;
537                f.write_char('?')?;
538            } else {
539                f.write_char('&')?;
540            }
541            f.write_fmt(format_args!("auto_resubscribe={}", self.auto_resubscribe))?;
542        }
543
544        if self.auto_remonitor != DEFAULT_AUTO_REMONITOR {
545            if !query_separator {
546                query_separator = true;
547                f.write_char('?')?;
548            } else {
549                f.write_char('&')?;
550            }
551            f.write_fmt(format_args!("auto_remonitor={}", self.auto_remonitor))?;
552        }
553
554        if !self.connection_name.is_empty() {
555            if !query_separator {
556                query_separator = true;
557                f.write_char('?')?;
558            } else {
559                f.write_char('&')?;
560            }
561            f.write_fmt(format_args!("connection_name={}", self.connection_name))?;
562        }
563
564        if let Some(keep_alive) = self.keep_alive {
565            if !query_separator {
566                query_separator = true;
567                f.write_char('?')?;
568            } else {
569                f.write_char('&')?;
570            }
571            f.write_fmt(format_args!("keep_alive={}", keep_alive.as_millis()))?;
572        }
573
574        if self.no_delay != DEFAULT_NO_DELAY {
575            if !query_separator {
576                query_separator = true;
577                f.write_char('?')?;
578            } else {
579                f.write_char('&')?;
580            }
581            f.write_fmt(format_args!("no_delay={}", self.no_delay))?;
582        }
583
584        if self.retry_on_error != DEFAULT_RETRY_ON_ERROR {
585            if !query_separator {
586                query_separator = true;
587                f.write_char('?')?;
588            } else {
589                f.write_char('&')?;
590            }
591            f.write_fmt(format_args!("retry_on_error={}", self.retry_on_error))?;
592        }
593
594        if let ServerConfig::Sentinel(SentinelConfig {
595            instances: _,
596            service_name: _,
597            wait_between_failures: wait_beetween_failures,
598            password,
599            username,
600        }) = &self.server
601        {
602            let wait_between_failures = wait_beetween_failures.as_millis() as u64;
603            if wait_between_failures != DEFAULT_WAIT_BETWEEN_FAILURES {
604                if !query_separator {
605                    query_separator = true;
606                    f.write_char('?')?;
607                } else {
608                    f.write_char('&')?;
609                }
610                f.write_fmt(format_args!(
611                    "wait_between_failures={wait_between_failures}"
612                ))?;
613            }
614            if let Some(username) = username {
615                if !query_separator {
616                    query_separator = true;
617                    f.write_char('?')?;
618                } else {
619                    f.write_char('&')?;
620                }
621                f.write_str("sentinel_username=")?;
622                f.write_str(username)?;
623            }
624            if let Some(password) = password {
625                if !query_separator {
626                    f.write_char('?')?;
627                } else {
628                    f.write_char('&')?;
629                }
630                f.write_str("sentinel_password=")?;
631                f.write_str(password)?;
632            }
633        }
634
635        Ok(())
636    }
637}
638
639/// Configuration for connecting to a Redis server
640#[derive(Debug, Clone)]
641pub enum ServerConfig {
642    /// Configuration for connecting to a standalone server (no master-replica, no cluster)
643    Standalone {
644        /// The hostname or IP address of the Redis server.
645        host: String,
646        /// The port on which the Redis server is listening.
647        port: u16,
648    },
649    /// Configuration for connecting to a Redis server via [`Sentinel`](https://redis.io/docs/management/sentinel/)
650    Sentinel(SentinelConfig),
651    /// Configuration for connecting to a Redis [`Cluster`](https://redis.io/docs/management/scaling/)
652    Cluster(ClusterConfig),
653}
654
655impl Default for ServerConfig {
656    fn default() -> Self {
657        ServerConfig::Standalone {
658            host: "127.0.0.1".to_owned(),
659            port: 6379,
660        }
661    }
662}
663
664/// Configuration for connecting to a Redis server via [`Sentinel`](https://redis.io/docs/management/sentinel/)
665#[derive(Debug, Clone)]
666pub struct SentinelConfig {
667    /// An array of `(host, port)` tuples for each known sentinel instance.
668    pub instances: Vec<(String, u16)>,
669
670    /// The service name
671    pub service_name: String,
672
673    /// Waiting time after failing before connecting to the next Sentinel instance (default 250ms).
674    pub wait_between_failures: Duration,
675
676    /// Sentinel username
677    pub username: Option<String>,
678
679    /// Sentinel password
680    pub password: Option<String>,
681}
682
683impl Default for SentinelConfig {
684    fn default() -> Self {
685        Self {
686            instances: Default::default(),
687            service_name: Default::default(),
688            wait_between_failures: Duration::from_millis(DEFAULT_WAIT_BETWEEN_FAILURES),
689            password: None,
690            username: None,
691        }
692    }
693}
694
695/// Configuration for connecting to a Redis [`Cluster`](https://redis.io/docs/management/scaling/)
696#[derive(Debug, Clone, Default)]
697pub struct ClusterConfig {
698    /// An array of `(host, port)` tuples for each known cluster node.
699    pub nodes: Vec<(String, u16)>,
700}
701
702/// Config for TLS.
703///
704/// See [rustls::client::ClientConfig](https://docs.rs/rustls/latest/rustls/client/struct.ClientConfig.html) documentation
705#[cfg(feature = "rustls")]
706#[derive(Debug, Clone)]
707pub struct TlsConfig {
708    pub rustls_config: Arc<rustls::ClientConfig>,
709}
710
711#[cfg(feature = "rustls")]
712impl Default for TlsConfig {
713    fn default() -> Self {
714        let root_store =
715            rustls::RootCertStore::from_iter(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
716        let rustls_config = rustls::ClientConfig::builder()
717            .with_root_certificates(root_store)
718            .with_no_client_auth();
719
720        Self {
721            rustls_config: Arc::new(rustls_config),
722        }
723    }
724}
725
726/// Config for TLS.
727///
728/// See [TlsConnectorBuilder](https://docs.rs/tokio-native-tls/latest/tokio_native_tls/native_tls/struct.TlsConnectorBuilder.html) documentation
729#[cfg(feature = "native-tls")]
730#[derive(Clone)]
731pub struct TlsConfig {
732    identity: Option<Identity>,
733    root_certificates: Option<Vec<Certificate>>,
734    min_protocol_version: Option<Protocol>,
735    max_protocol_version: Option<Protocol>,
736    disable_built_in_roots: bool,
737    danger_accept_invalid_certs: bool,
738    danger_accept_invalid_hostnames: bool,
739    use_sni: bool,
740}
741
742#[cfg(feature = "native-tls")]
743impl Default for TlsConfig {
744    fn default() -> Self {
745        Self {
746            identity: None,
747            root_certificates: None,
748            min_protocol_version: Some(Protocol::Tlsv10),
749            max_protocol_version: None,
750            disable_built_in_roots: false,
751            danger_accept_invalid_certs: false,
752            danger_accept_invalid_hostnames: false,
753            use_sni: true,
754        }
755    }
756}
757
758#[cfg(feature = "native-tls")]
759impl std::fmt::Debug for TlsConfig {
760    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
761        f.debug_struct("TlsConfig")
762            .field("min_protocol_version", &self.min_protocol_version)
763            .field("max_protocol_version", &self.max_protocol_version)
764            .field("disable_built_in_roots", &self.disable_built_in_roots)
765            .field(
766                "danger_accept_invalid_certs",
767                &self.danger_accept_invalid_certs,
768            )
769            .field(
770                "danger_accept_invalid_hostnames",
771                &self.danger_accept_invalid_hostnames,
772            )
773            .field("use_sni", &self.use_sni)
774            .finish()
775    }
776}
777
778#[cfg(feature = "native-tls")]
779impl TlsConfig {
780    pub fn identity(&mut self, identity: Identity) -> &mut Self {
781        self.identity = Some(identity);
782        self
783    }
784
785    pub fn root_certificates(&mut self, root_certificates: Vec<Certificate>) -> &mut Self {
786        self.root_certificates = Some(root_certificates);
787        self
788    }
789
790    pub fn min_protocol_version(&mut self, min_protocol_version: Protocol) -> &mut Self {
791        self.min_protocol_version = Some(min_protocol_version);
792        self
793    }
794
795    pub fn max_protocol_version(&mut self, max_protocol_version: Protocol) -> &mut Self {
796        self.max_protocol_version = Some(max_protocol_version);
797        self
798    }
799
800    pub fn disable_built_in_roots(&mut self, disable_built_in_roots: bool) -> &mut Self {
801        self.disable_built_in_roots = disable_built_in_roots;
802        self
803    }
804
805    pub fn danger_accept_invalid_certs(&mut self, danger_accept_invalid_certs: bool) -> &mut Self {
806        self.danger_accept_invalid_certs = danger_accept_invalid_certs;
807        self
808    }
809
810    pub fn use_sni(&mut self, use_sni: bool) -> &mut Self {
811        self.use_sni = use_sni;
812        self
813    }
814
815    pub fn danger_accept_invalid_hostnames(
816        &mut self,
817        danger_accept_invalid_hostnames: bool,
818    ) -> &mut Self {
819        self.danger_accept_invalid_hostnames = danger_accept_invalid_hostnames;
820        self
821    }
822
823    pub fn into_tls_connector_builder(&self) -> TlsConnectorBuilder {
824        let mut builder = TlsConnector::builder();
825
826        if let Some(root_certificates) = &self.root_certificates {
827            for root_certificate in root_certificates {
828                builder.add_root_certificate(root_certificate.clone());
829            }
830        }
831
832        builder.min_protocol_version(self.min_protocol_version);
833        builder.max_protocol_version(self.max_protocol_version);
834        builder.disable_built_in_roots(self.disable_built_in_roots);
835        builder.danger_accept_invalid_certs(self.danger_accept_invalid_certs);
836        builder.danger_accept_invalid_hostnames(self.danger_accept_invalid_hostnames);
837        builder.use_sni(self.use_sni);
838
839        builder
840    }
841}
842
843/// A value-to-[`Config`](crate::client::Config) conversion that consumes the input value.
844///
845/// This allows the `connect` associated function of the [`client`](crate::client::Client),
846/// or [`pooled client`](crate::client::PooledClientManager)
847/// to accept connection information in a range of different formats.
848pub trait IntoConfig {
849    /// Converts this type into a [`Config`](crate::client::Config).
850    fn into_config(self) -> Result<Config>;
851}
852
853impl IntoConfig for Config {
854    fn into_config(self) -> Result<Config> {
855        Ok(self)
856    }
857}
858
859impl<T: Into<String>> IntoConfig for (T, u16) {
860    fn into_config(self) -> Result<Config> {
861        Ok(Config {
862            server: ServerConfig::Standalone {
863                host: self.0.into(),
864                port: self.1,
865            },
866            ..Default::default()
867        })
868    }
869}
870
871impl IntoConfig for &str {
872    fn into_config(self) -> Result<Config> {
873        Config::from_str(self)
874    }
875}
876
877impl IntoConfig for String {
878    fn into_config(self) -> Result<Config> {
879        Config::from_str(&self)
880    }
881}
882
883impl IntoConfig for Url {
884    fn into_config(self) -> Result<Config> {
885        Config::from_uri(self)
886    }
887}
888
889/// The type of reconnection policy to use. This will apply to every connection used by the client.
890/// This code has been mostly inpisred by [fred ReconnectPolicy](https://docs.rs/fred/latest/fred/types/enum.ReconnectPolicy.html)
891#[derive(Debug, Clone)]
892pub enum ReconnectionConfig {
893    /// Wait a constant amount of time between reconnection attempts, in ms.
894    Constant {
895        /// Maximum number of attemps, set `0` to retry forever.
896        max_attempts: u32,
897        /// Delay in ms to wait between reconnection attempts
898        delay: u32,
899        /// Add jitter in ms to each delay
900        jitter: u32,
901    },
902    /// Backoff reconnection attempts linearly, adding `delay` each time.
903    Linear {
904        /// Maximum number of attemps, set `0` to retry forever.
905        max_attempts: u32,
906        /// Maximum delay in ms
907        max_delay: u32,
908        /// Delay in ms to add to the total waiting time at each attemp
909        delay: u32,
910        /// Add jitter in ms to each delay
911        jitter: u32,
912    },
913    /// Backoff reconnection attempts exponentially, multiplying the last delay by `multiplicative_factor` each time.
914    ///
915    /// see <https://en.wikipedia.org/wiki/Exponential_backoff>
916    Exponential {
917        /// Maximum number of attemps, set `0` to retry forever.
918        max_attempts: u32,
919        /// Minimum delay in ms
920        min_delay: u32,
921        /// Maximum delay in ms
922        max_delay: u32,
923        // multiplicative factor
924        multiplicative_factor: u32,
925        /// Add jitter in ms to each delay
926        jitter: u32,
927    },
928}
929
930/// The default amount of jitter when waiting to reconnect.
931const DEFAULT_JITTER_MS: u32 = 100;
932const DEFAULT_DELAY_MS: u32 = 1000;
933
934impl Default for ReconnectionConfig {
935    fn default() -> Self {
936        Self::Constant {
937            max_attempts: 0,
938            delay: DEFAULT_DELAY_MS,
939            jitter: DEFAULT_JITTER_MS,
940        }
941    }
942}
943
944impl ReconnectionConfig {
945    /// Create a new reconnect policy with a constant backoff.
946    pub fn new_constant(max_attempts: u32, delay: u32) -> Self {
947        Self::Constant {
948            max_attempts,
949            delay,
950            jitter: DEFAULT_JITTER_MS,
951        }
952    }
953
954    /// Create a new reconnect policy with a linear backoff.
955    pub fn new_linear(max_attempts: u32, max_delay: u32, delay: u32) -> Self {
956        Self::Linear {
957            max_attempts,
958            max_delay,
959            delay,
960            jitter: DEFAULT_JITTER_MS,
961        }
962    }
963
964    /// Create a new reconnect policy with an exponential backoff.
965    pub fn new_exponential(
966        max_attempts: u32,
967        min_delay: u32,
968        max_delay: u32,
969        multiplicative_factor: u32,
970    ) -> Self {
971        Self::Exponential {
972            max_delay,
973            max_attempts,
974            min_delay,
975            multiplicative_factor,
976            jitter: DEFAULT_JITTER_MS,
977        }
978    }
979
980    /// Set the amount of jitter to add to each reconnection delay.
981    ///
982    /// Default: 100 ms
983    pub fn set_jitter(&mut self, jitter_ms: u32) {
984        match self {
985            Self::Constant { jitter, .. } => {
986                *jitter = jitter_ms;
987            }
988            Self::Linear { jitter, .. } => {
989                *jitter = jitter_ms;
990            }
991            Self::Exponential { jitter, .. } => {
992                *jitter = jitter_ms;
993            }
994        }
995    }
996}