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        }
197    }
198}
199
200/// This is a 1:1 copy of the [`redis::ConnectionInfo`] struct.
201/// This is duplicated here in order to add support for the
202/// [`serde::Deserialize`] trait which is required for the [`serde`] support.
203#[derive(Clone, Debug, Default)]
204#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
205#[cfg_attr(feature = "serde", serde(crate = "serde"))]
206pub struct ConnectionInfo {
207    /// A connection address for where to connect to.
208    pub addr: ConnectionAddr,
209
210    /// A boxed connection address for where to connect to.
211    #[cfg_attr(feature = "serde", serde(flatten))]
212    pub redis: RedisConnectionInfo,
213}
214
215impl From<ConnectionInfo> for redis::ConnectionInfo {
216    fn from(info: ConnectionInfo) -> Self {
217        Self {
218            addr: info.addr.into(),
219            redis: info.redis.into(),
220        }
221    }
222}
223
224impl From<redis::ConnectionInfo> for ConnectionInfo {
225    fn from(info: redis::ConnectionInfo) -> Self {
226        Self {
227            addr: info.addr.into(),
228            redis: info.redis.into(),
229        }
230    }
231}
232
233impl redis::IntoConnectionInfo for ConnectionInfo {
234    fn into_connection_info(self) -> RedisResult<redis::ConnectionInfo> {
235        Ok(self.into())
236    }
237}
238
239/// This is a 1:1 copy of the [`redis::RedisConnectionInfo`] struct.
240/// This is duplicated here in order to add support for the
241/// [`serde::Deserialize`] trait which is required for the [`serde`] support.
242#[derive(Clone, Debug, Default)]
243#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
244#[cfg_attr(feature = "serde", serde(crate = "serde"))]
245pub struct RedisConnectionInfo {
246    /// The database number to use. This is usually `0`.
247    pub db: i64,
248
249    /// Optionally a username that should be used for connection.
250    pub username: Option<String>,
251
252    /// Optionally a password that should be used for connection.
253    pub password: Option<String>,
254
255    /// Version of the protocol to use.
256    pub protocol: ProtocolVersion,
257}
258
259/// This is a 1:1 copy of the [`redis::ProtocolVersion`] struct.
260/// Enum representing the communication protocol with the server. This enum represents the types
261/// of data that the server can send to the client, and the capabilities that the client can use.
262#[derive(Clone, Eq, PartialEq, Default, Debug, Copy)]
263#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
264#[cfg_attr(feature = "serde", serde(crate = "serde"))]
265pub enum ProtocolVersion {
266    /// <https://github.com/redis/redis-specifications/blob/master/protocol/RESP2.md>
267    #[default]
268    RESP2,
269    /// <https://github.com/redis/redis-specifications/blob/master/protocol/RESP3.md>
270    RESP3,
271}
272
273impl From<RedisConnectionInfo> for redis::RedisConnectionInfo {
274    fn from(info: RedisConnectionInfo) -> Self {
275        let protocol = match info.protocol {
276            ProtocolVersion::RESP2 => redis::ProtocolVersion::RESP2,
277            ProtocolVersion::RESP3 => redis::ProtocolVersion::RESP3,
278        };
279        Self {
280            db: info.db,
281            username: info.username,
282            password: info.password,
283            protocol,
284        }
285    }
286}
287
288impl From<redis::RedisConnectionInfo> for RedisConnectionInfo {
289    fn from(info: redis::RedisConnectionInfo) -> Self {
290        let protocol = match info.protocol {
291            redis::ProtocolVersion::RESP2 => ProtocolVersion::RESP2,
292            redis::ProtocolVersion::RESP3 => ProtocolVersion::RESP3,
293        };
294        Self {
295            db: info.db,
296            username: info.username,
297            password: info.password,
298            protocol,
299        }
300    }
301}
302
303/// This error is returned if the configuration contains an error
304#[derive(Debug)]
305pub enum ConfigError {
306    /// Both url and connection were specified in the config
307    UrlAndConnectionSpecified,
308    /// The [`redis`] crate returned an error when parsing the config
309    Redis(RedisError),
310}
311
312impl From<RedisError> for ConfigError {
313    fn from(e: RedisError) -> Self {
314        Self::Redis(e)
315    }
316}
317
318impl fmt::Display for ConfigError {
319    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
320        match self {
321            Self::UrlAndConnectionSpecified => write!(
322                f,
323                "url and connection must not be specified at the same time."
324            ),
325            Self::Redis(e) => write!(f, "Redis: {}", e),
326        }
327    }
328}
329
330impl std::error::Error for ConfigError {}