Skip to main content

deadpool_redis/
config.rs

1use std::{fmt, path::PathBuf};
2
3use redis::RedisError;
4#[cfg(feature = "serde")]
5use serde::{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__URL=redis.example.com
17/// REDIS__POOL__MAX_SIZE=16
18/// REDIS__POOL__TIMEOUTS__WAIT__SECS=2
19/// REDIS__POOL__TIMEOUTS__WAIT__NANOS=0
20/// ```
21/// ```rust
22/// #[derive(serde::Deserialize)]
23/// struct Config {
24///     redis: deadpool_redis::Config,
25/// }
26///
27/// impl Config {
28///     pub fn from_env() -> Result<Self, config::ConfigError> {
29///         let mut cfg = config::Config::builder()
30///            .add_source(config::Environment::default().separator("__"))
31///            .build()?;
32///            cfg.try_deserialize()
33///     }
34/// }
35/// ```
36#[derive(Clone, Debug)]
37#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
38#[cfg_attr(feature = "serde", serde(crate = "serde"))]
39pub struct Config {
40    /// Redis URL.
41    ///
42    /// See [Connection Parameters](redis#connection-parameters).
43    pub url: Option<String>,
44
45    /// [`redis::ConnectionInfo`] structure.
46    pub connection: Option<ConnectionInfo>,
47
48    /// Pool configuration.
49    pub pool: Option<PoolConfig>,
50}
51
52impl Config {
53    /// Creates a new [`Pool`] using this [`Config`].
54    ///
55    /// # Errors
56    ///
57    /// See [`CreatePoolError`] for details.
58    pub fn create_pool(&self, runtime: Option<Runtime>) -> Result<Pool, CreatePoolError> {
59        let mut builder = self.builder().map_err(CreatePoolError::Config)?;
60        if let Some(runtime) = runtime {
61            builder = builder.runtime(runtime);
62        }
63        builder.build().map_err(CreatePoolError::Build)
64    }
65
66    /// Creates a new [`PoolBuilder`] using this [`Config`].
67    ///
68    /// # Errors
69    ///
70    /// See [`ConfigError`] for details.
71    pub fn builder(&self) -> Result<PoolBuilder, ConfigError> {
72        let manager = match (&self.url, &self.connection) {
73            (Some(url), None) => crate::Manager::new(url.as_str())?,
74            (None, Some(connection)) => crate::Manager::new(connection.clone())?,
75            (None, None) => crate::Manager::new(ConnectionInfo::default())?,
76            (Some(_), Some(_)) => return Err(ConfigError::UrlAndConnectionSpecified),
77        };
78        let pool_config = self.get_pool_config();
79        Ok(Pool::builder(manager).config(pool_config))
80    }
81
82    /// Returns [`deadpool::managed::PoolConfig`] which can be used to construct
83    /// a [`deadpool::managed::Pool`] instance.
84    #[must_use]
85    pub fn get_pool_config(&self) -> PoolConfig {
86        self.pool.unwrap_or_default()
87    }
88
89    /// Creates a new [`Config`] from the given Redis URL (like
90    /// `redis://127.0.0.1`).
91    #[must_use]
92    pub fn from_url<T: Into<String>>(url: T) -> Config {
93        Config {
94            url: Some(url.into()),
95            connection: None,
96            pool: None,
97        }
98    }
99
100    /// Creates a new [`Config`] from the given Redis ConnectionInfo
101    /// structure.
102    #[must_use]
103    pub fn from_connection_info<T: Into<ConnectionInfo>>(connection_info: T) -> Config {
104        Config {
105            url: None,
106            connection: Some(connection_info.into()),
107            pool: None,
108        }
109    }
110}
111
112impl Default for Config {
113    fn default() -> Self {
114        Self {
115            url: None,
116            connection: Some(ConnectionInfo::default()),
117            pool: None,
118        }
119    }
120}
121
122/// This is a 1:1 copy of the [`redis::ConnectionAddr`] enumeration (excluding `tls_params` since it is entirely opaque to consumers).
123///
124/// This is duplicated here in order to add support for the
125/// [`serde::Deserialize`] trait which is required for the [`serde`] support.
126#[derive(Clone, Debug)]
127#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
128#[cfg_attr(feature = "serde", serde(crate = "serde"))]
129pub enum ConnectionAddr {
130    /// Format for this is `(host, port)`.
131    Tcp(String, u16),
132
133    /// Format for this is `(host, port)`.
134    TcpTls {
135        /// Hostname.
136        host: String,
137
138        /// Port.
139        port: u16,
140
141        /// Disable hostname verification when connecting.
142        ///
143        /// # Warning
144        ///
145        /// You should think very carefully before you use this method. If
146        /// hostname verification is not used, any valid certificate for any
147        /// site will be trusted for use from any other. This introduces a
148        /// significant vulnerability to man-in-the-middle attacks.
149        insecure: bool,
150    },
151
152    /// Format for this is the path to the unix socket.
153    Unix(PathBuf),
154}
155
156impl Default for ConnectionAddr {
157    fn default() -> Self {
158        Self::Tcp("127.0.0.1".to_string(), 6379)
159    }
160}
161
162impl From<ConnectionAddr> for redis::ConnectionAddr {
163    fn from(addr: ConnectionAddr) -> Self {
164        match addr {
165            ConnectionAddr::Tcp(host, port) => Self::Tcp(host, port),
166            ConnectionAddr::TcpTls {
167                host,
168                port,
169                insecure,
170            } => Self::TcpTls {
171                host,
172                port,
173                insecure,
174                tls_params: None,
175            },
176            ConnectionAddr::Unix(path) => Self::Unix(path),
177        }
178    }
179}
180
181impl From<redis::ConnectionAddr> for ConnectionAddr {
182    fn from(addr: redis::ConnectionAddr) -> Self {
183        match addr {
184            redis::ConnectionAddr::Tcp(host, port) => Self::Tcp(host, port),
185            redis::ConnectionAddr::TcpTls {
186                host,
187                port,
188                insecure,
189                ..
190            } => ConnectionAddr::TcpTls {
191                host,
192                port,
193                insecure,
194            },
195            redis::ConnectionAddr::Unix(path) => Self::Unix(path),
196            other => unimplemented!("unsupported redis::ConnectionAddr variant: {other:?}"),
197        }
198    }
199}
200
201/// This is a 1:1 copy of the [`redis::ConnectionInfo`] struct.
202/// This is duplicated here in order to add support for the
203/// [`serde::Deserialize`] trait which is required for the [`serde`] support.
204#[derive(Clone, Debug, Default)]
205#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
206#[cfg_attr(feature = "serde", serde(crate = "serde"))]
207pub struct ConnectionInfo {
208    /// A connection address for where to connect to.
209    pub addr: ConnectionAddr,
210
211    /// A boxed connection address for where to connect to.
212    #[cfg_attr(feature = "serde", serde(flatten))]
213    pub redis: RedisConnectionInfo,
214}
215
216impl From<ConnectionInfo> for redis::ConnectionInfo {
217    fn from(info: ConnectionInfo) -> Self {
218        redis::IntoConnectionInfo::into_connection_info(redis::ConnectionAddr::from(info.addr))
219            .expect("converting ConnectionAddr into redis::ConnectionInfo is infallible")
220            .set_redis_settings(info.redis.into())
221            .set_tcp_settings(Default::default())
222    }
223}
224
225impl From<redis::ConnectionInfo> for ConnectionInfo {
226    fn from(info: redis::ConnectionInfo) -> Self {
227        Self {
228            addr: info.addr().clone().into(),
229            redis: info.redis_settings().clone().into(),
230        }
231    }
232}
233
234impl redis::IntoConnectionInfo for ConnectionInfo {
235    fn into_connection_info(self) -> RedisResult<redis::ConnectionInfo> {
236        Ok(self.into())
237    }
238}
239
240/// This is a 1:1 copy of the [`redis::RedisConnectionInfo`] struct.
241/// This is duplicated here in order to add support for the
242/// [`serde::Deserialize`] trait which is required for the [`serde`] support.
243#[derive(Clone, Debug, Default)]
244#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
245#[cfg_attr(feature = "serde", serde(crate = "serde"))]
246pub struct RedisConnectionInfo {
247    /// The database number to use. This is usually `0`.
248    pub db: i64,
249
250    /// Optionally a username that should be used for connection.
251    pub username: Option<String>,
252
253    /// Optionally a password that should be used for connection.
254    pub password: Option<String>,
255
256    /// Version of the protocol to use.
257    pub protocol: ProtocolVersion,
258}
259
260/// This is a 1:1 copy of the [`redis::ProtocolVersion`] struct.
261/// Enum representing the communication protocol with the server. This enum represents the types
262/// of data that the server can send to the client, and the capabilities that the client can use.
263#[derive(Clone, Eq, PartialEq, Default, Debug, Copy)]
264#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
265#[cfg_attr(feature = "serde", serde(crate = "serde"))]
266pub enum ProtocolVersion {
267    /// <https://github.com/redis/redis-specifications/blob/master/protocol/RESP2.md>
268    #[default]
269    RESP2,
270    /// <https://github.com/redis/redis-specifications/blob/master/protocol/RESP3.md>
271    RESP3,
272}
273
274impl From<RedisConnectionInfo> for redis::RedisConnectionInfo {
275    fn from(info: RedisConnectionInfo) -> Self {
276        let protocol = match info.protocol {
277            ProtocolVersion::RESP2 => redis::ProtocolVersion::RESP2,
278            ProtocolVersion::RESP3 => redis::ProtocolVersion::RESP3,
279        };
280        let mut result = redis::RedisConnectionInfo::default()
281            .set_db(info.db)
282            .set_protocol(protocol);
283        if let Some(username) = info.username {
284            result = result.set_username(username);
285        }
286        if let Some(password) = info.password {
287            result = result.set_password(password);
288        }
289        result
290    }
291}
292
293impl From<redis::RedisConnectionInfo> for RedisConnectionInfo {
294    fn from(info: redis::RedisConnectionInfo) -> Self {
295        let protocol = match info.protocol() {
296            redis::ProtocolVersion::RESP2 => ProtocolVersion::RESP2,
297            redis::ProtocolVersion::RESP3 => ProtocolVersion::RESP3,
298            other => unimplemented!("unsupported redis::ProtocolVersion variant: {other:?}"),
299        };
300        Self {
301            db: info.db(),
302            username: info.username().map(ToOwned::to_owned),
303            password: info.password().map(ToOwned::to_owned),
304            protocol,
305        }
306    }
307}
308
309/// This error is returned if the configuration contains an error
310#[derive(Debug)]
311pub enum ConfigError {
312    /// Both url and connection were specified in the config
313    UrlAndConnectionSpecified,
314    /// The [`redis`] crate returned an error when parsing the config
315    Redis(RedisError),
316}
317
318impl From<RedisError> for ConfigError {
319    fn from(e: RedisError) -> Self {
320        Self::Redis(e)
321    }
322}
323
324impl fmt::Display for ConfigError {
325    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
326        match self {
327            Self::UrlAndConnectionSpecified => write!(
328                f,
329                "url and connection must not be specified at the same time."
330            ),
331            Self::Redis(e) => write!(f, "Redis: {}", e),
332        }
333    }
334}
335
336impl std::error::Error for ConfigError {}