deadpool_redis_cluster/
config.rs

1use std::{fmt, path::PathBuf};
2
3use redis::RedisError;
4#[cfg(feature = "serde")]
5use serde_1::{Deserialize, Serialize};
6
7use crate::{CreatePoolError, Pool, PoolBuilder, PoolConfig, RedisResult, Runtime};
8
9/// Configuration object.
10///
11/// # Example (from environment)
12///
13/// By enabling the `serde` feature you can read the configuration using the
14/// [`config`](https://crates.io/crates/config) crate as following:
15/// ```env
16/// REDIS_CLUSTER__URLS=redis://127.0.0.1:7000,redis://127.0.0.1:7001
17/// REDIS_CLUSTER__POOL__MAX_SIZE=16
18/// REDIS_CLUSTER__POOL__TIMEOUTS__WAIT__SECS=2
19/// REDIS_CLUSTER__POOL__TIMEOUTS__WAIT__NANOS=0
20/// ```
21/// ```rust
22/// # use serde_1 as serde;
23/// #
24/// #[derive(serde::Deserialize)]
25/// # #[serde(crate = "serde_1")]
26/// struct Config {
27///     redis_cluster: deadpool_redis_cluster::Config,
28/// }
29///
30/// impl Config {
31///     pub fn from_env() -> Result<Self, config::ConfigError> {
32///         let mut cfg = config::Config::builder()
33///            .add_source(
34///                config::Environment::default()
35///                .separator("__")
36///                .try_parsing(true)
37///                .list_separator(","),
38///            )
39///            .build()?;
40///            cfg.try_deserialize()
41///     }
42/// }
43/// ```
44#[derive(Clone, Debug)]
45#[cfg_attr(feature = "serde", derive(serde_1::Deserialize, serde_1::Serialize))]
46#[cfg_attr(feature = "serde", serde(crate = "serde_1"))]
47pub struct Config {
48    /// Redis URLs.
49    ///
50    /// See [Connection Parameters](redis#connection-parameters).
51    pub urls: Option<Vec<String>>,
52
53    /// [`redis::ConnectionInfo`] structures.
54    pub connections: Option<Vec<ConnectionInfo>>,
55
56    /// Pool configuration.
57    pub pool: Option<PoolConfig>,
58}
59
60impl Config {
61    /// Creates a new [`Pool`] using this [`Config`].
62    ///
63    /// # Errors
64    ///
65    /// See [`CreatePoolError`] for details.
66    pub fn create_pool(&self, runtime: Option<Runtime>) -> Result<Pool, CreatePoolError> {
67        let mut builder = self.builder().map_err(CreatePoolError::Config)?;
68        if let Some(runtime) = runtime {
69            builder = builder.runtime(runtime);
70        }
71        builder.build().map_err(CreatePoolError::Build)
72    }
73
74    /// Creates a new [`PoolBuilder`] using this [`Config`].
75    ///
76    /// # Errors
77    ///
78    /// See [`ConfigError`] for details.
79    pub fn builder(&self) -> Result<PoolBuilder, ConfigError> {
80        let manager = match (&self.urls, &self.connections) {
81            (Some(urls), None) => {
82                crate::Manager::new(urls.iter().map(|url| url.as_str()).collect())?
83            }
84            (None, Some(connections)) => crate::Manager::new(connections.clone())?,
85            (None, None) => crate::Manager::new(vec![ConnectionInfo::default()])?,
86            (Some(_), Some(_)) => return Err(ConfigError::UrlAndConnectionSpecified),
87        };
88        let pool_config = self.get_pool_config();
89        Ok(Pool::builder(manager).config(pool_config))
90    }
91
92    /// Returns [`deadpool::managed::PoolConfig`] which can be used to construct
93    /// a [`deadpool::managed::Pool`] instance.
94    #[must_use]
95    pub fn get_pool_config(&self) -> PoolConfig {
96        self.pool.unwrap_or_default()
97    }
98
99    /// Creates a new [`Config`] from the given Redis URL (like
100    /// `redis://127.0.0.1`).
101    #[must_use]
102    pub fn from_urls<T: Into<Vec<String>>>(urls: T) -> Config {
103        Config {
104            urls: Some(urls.into()),
105            connections: None,
106            pool: None,
107        }
108    }
109}
110
111impl Default for Config {
112    fn default() -> Self {
113        Self {
114            urls: None,
115            connections: Some(vec![ConnectionInfo::default()]),
116            pool: None,
117        }
118    }
119}
120
121/// This is a 1:1 copy of the [`redis::ConnectionAddr`] enumeration.
122/// This is duplicated here in order to add support for the
123/// [`serde::Deserialize`] trait which is required for the [`serde`] support.
124#[derive(Clone, Debug)]
125#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
126#[cfg_attr(feature = "serde", serde(crate = "serde_1"))]
127pub enum ConnectionAddr {
128    /// Format for this is `(host, port)`.
129    Tcp(String, u16),
130
131    /// Format for this is `(host, port)`.
132    TcpTls {
133        /// Hostname.
134        host: String,
135
136        /// Port.
137        port: u16,
138
139        /// Disable hostname verification when connecting.
140        ///
141        /// # Warning
142        ///
143        /// You should think very carefully before you use this method. If
144        /// hostname verification is not used, any valid certificate for any
145        /// site will be trusted for use from any other. This introduces a
146        /// significant vulnerability to man-in-the-middle attacks.
147        insecure: bool,
148    },
149
150    /// Format for this is the path to the unix socket.
151    Unix(PathBuf),
152}
153
154impl Default for ConnectionAddr {
155    fn default() -> Self {
156        Self::Tcp("127.0.0.1".to_string(), 6379)
157    }
158}
159
160impl From<ConnectionAddr> for redis::ConnectionAddr {
161    fn from(addr: ConnectionAddr) -> Self {
162        match addr {
163            ConnectionAddr::Tcp(host, port) => Self::Tcp(host, port),
164            ConnectionAddr::TcpTls {
165                host,
166                port,
167                insecure,
168            } => Self::TcpTls {
169                host,
170                port,
171                insecure,
172            },
173            ConnectionAddr::Unix(path) => Self::Unix(path),
174        }
175    }
176}
177
178impl From<redis::ConnectionAddr> for ConnectionAddr {
179    fn from(addr: redis::ConnectionAddr) -> Self {
180        match addr {
181            redis::ConnectionAddr::Tcp(host, port) => Self::Tcp(host, port),
182            redis::ConnectionAddr::TcpTls {
183                host,
184                port,
185                insecure,
186            } => ConnectionAddr::TcpTls {
187                host,
188                port,
189                insecure,
190            },
191            redis::ConnectionAddr::Unix(path) => Self::Unix(path),
192        }
193    }
194}
195
196/// This is a 1:1 copy of the [`redis::ConnectionInfo`] struct.
197/// This is duplicated here in order to add support for the
198/// [`serde::Deserialize`] trait which is required for the [`serde`] support.
199#[derive(Clone, Debug, Default)]
200#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
201#[cfg_attr(feature = "serde", serde(crate = "serde_1"))]
202pub struct ConnectionInfo {
203    /// A connection address for where to connect to.
204    pub addr: ConnectionAddr,
205
206    /// A boxed connection address for where to connect to.
207    #[cfg_attr(feature = "serde", serde(flatten))]
208    pub redis: RedisConnectionInfo,
209}
210
211impl From<ConnectionInfo> for redis::ConnectionInfo {
212    fn from(info: ConnectionInfo) -> Self {
213        Self {
214            addr: info.addr.into(),
215            redis: info.redis.into(),
216        }
217    }
218}
219
220impl From<redis::ConnectionInfo> for ConnectionInfo {
221    fn from(info: redis::ConnectionInfo) -> Self {
222        Self {
223            addr: info.addr.into(),
224            redis: info.redis.into(),
225        }
226    }
227}
228
229impl redis::IntoConnectionInfo for ConnectionInfo {
230    fn into_connection_info(self) -> RedisResult<redis::ConnectionInfo> {
231        Ok(self.into())
232    }
233}
234
235/// This is a 1:1 copy of the [`redis::RedisConnectionInfo`] struct.
236/// This is duplicated here in order to add support for the
237/// [`serde::Deserialize`] trait which is required for the [`serde`] support.
238#[derive(Clone, Debug, Default)]
239#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
240#[cfg_attr(feature = "serde", serde(crate = "serde_1"))]
241pub struct RedisConnectionInfo {
242    /// The database number to use. This is usually `0`.
243    pub db: i64,
244
245    /// Optionally a username that should be used for connection.
246    pub username: Option<String>,
247
248    /// Optionally a password that should be used for connection.
249    pub password: Option<String>,
250}
251
252impl From<RedisConnectionInfo> for redis::RedisConnectionInfo {
253    fn from(info: RedisConnectionInfo) -> Self {
254        Self {
255            db: info.db,
256            username: info.username,
257            password: info.password,
258        }
259    }
260}
261
262impl From<redis::RedisConnectionInfo> for RedisConnectionInfo {
263    fn from(info: redis::RedisConnectionInfo) -> Self {
264        Self {
265            db: info.db,
266            username: info.username,
267            password: info.password,
268        }
269    }
270}
271
272/// This error is returned if the configuration contains an error
273#[derive(Debug)]
274pub enum ConfigError {
275    /// Both url and connection were specified in the config
276    UrlAndConnectionSpecified,
277    /// The [`redis`] crate returned an error when parsing the config
278    Redis(RedisError),
279}
280
281impl From<RedisError> for ConfigError {
282    fn from(e: RedisError) -> Self {
283        Self::Redis(e)
284    }
285}
286
287impl fmt::Display for ConfigError {
288    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
289        match self {
290            Self::UrlAndConnectionSpecified => write!(
291                f,
292                "url and connection must not be specified at the same time."
293            ),
294            Self::Redis(e) => write!(f, "Redis: {}", e),
295        }
296    }
297}
298
299impl std::error::Error for ConfigError {}