redis_oxide/core/
config.rs

1//! Configuration types for Redis connections
2
3use std::time::Duration;
4
5/// Strategy for connection pooling
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum PoolStrategy {
8    /// Single multiplexed connection shared across tasks
9    Multiplexed,
10    /// Connection pool with multiple connections
11    Pool,
12}
13
14/// Configuration for connection pooling
15#[derive(Debug, Clone)]
16pub struct PoolConfig {
17    /// Pooling strategy to use
18    pub strategy: PoolStrategy,
19    /// Maximum number of connections in pool (only for Pool strategy)
20    pub max_size: usize,
21    /// Minimum number of connections to maintain (only for Pool strategy)
22    pub min_idle: usize,
23    /// Timeout for acquiring a connection from pool
24    pub connection_timeout: Duration,
25}
26
27impl Default for PoolConfig {
28    fn default() -> Self {
29        Self {
30            strategy: PoolStrategy::Multiplexed,
31            max_size: 10,
32            min_idle: 2,
33            connection_timeout: Duration::from_secs(5),
34        }
35    }
36}
37
38/// Topology detection mode
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40pub enum TopologyMode {
41    /// Automatically detect topology (Standalone or Cluster)
42    Auto,
43    /// Force standalone mode
44    Standalone,
45    /// Force cluster mode
46    Cluster,
47}
48
49/// Configuration for Redis connection
50#[derive(Debug, Clone)]
51pub struct ConnectionConfig {
52    /// Connection string (e.g., `<redis://localhost:6379>` or `<redis://host1:6379,host2:6379>`)
53    pub connection_string: String,
54
55    /// Optional password for authentication
56    pub password: Option<String>,
57
58    /// Database number (only for standalone mode)
59    pub database: u8,
60
61    /// Connection timeout
62    pub connect_timeout: Duration,
63
64    /// Read/write operation timeout
65    pub operation_timeout: Duration,
66
67    /// Enable TCP keepalive
68    pub tcp_keepalive: Option<Duration>,
69
70    /// Topology detection mode
71    pub topology_mode: TopologyMode,
72
73    /// Pool configuration
74    pub pool: PoolConfig,
75
76    /// Maximum number of retries for cluster redirects
77    pub max_redirects: usize,
78
79    /// Reconnection settings
80    pub reconnect: ReconnectConfig,
81}
82
83/// Configuration for reconnection behavior
84#[derive(Debug, Clone)]
85pub struct ReconnectConfig {
86    /// Enable automatic reconnection
87    pub enabled: bool,
88
89    /// Initial delay before first reconnect attempt
90    pub initial_delay: Duration,
91
92    /// Maximum delay between reconnect attempts
93    pub max_delay: Duration,
94
95    /// Backoff multiplier
96    pub backoff_multiplier: f64,
97
98    /// Maximum number of reconnect attempts (None = infinite)
99    pub max_attempts: Option<usize>,
100}
101
102impl Default for ReconnectConfig {
103    fn default() -> Self {
104        Self {
105            enabled: true,
106            initial_delay: Duration::from_millis(100),
107            max_delay: Duration::from_secs(30),
108            backoff_multiplier: 2.0,
109            max_attempts: None,
110        }
111    }
112}
113
114impl Default for ConnectionConfig {
115    fn default() -> Self {
116        Self {
117            connection_string: "redis://localhost:6379".to_string(),
118            password: None,
119            database: 0,
120            connect_timeout: Duration::from_secs(5),
121            operation_timeout: Duration::from_secs(30),
122            tcp_keepalive: Some(Duration::from_secs(60)),
123            topology_mode: TopologyMode::Auto,
124            pool: PoolConfig::default(),
125            max_redirects: 3,
126            reconnect: ReconnectConfig::default(),
127        }
128    }
129}
130
131impl ConnectionConfig {
132    /// Create a new configuration with the given connection string
133    pub fn new(connection_string: impl Into<String>) -> Self {
134        Self {
135            connection_string: connection_string.into(),
136            ..Default::default()
137        }
138    }
139
140    /// Set the password for authentication
141    #[must_use]
142    pub fn with_password(mut self, password: impl Into<String>) -> Self {
143        self.password = Some(password.into());
144        self
145    }
146
147    /// Set the database number
148    #[must_use]
149    pub const fn with_database(mut self, database: u8) -> Self {
150        self.database = database;
151        self
152    }
153
154    /// Set the connection timeout
155    #[must_use]
156    pub const fn with_connect_timeout(mut self, timeout: Duration) -> Self {
157        self.connect_timeout = timeout;
158        self
159    }
160
161    /// Set the operation timeout
162    #[must_use]
163    pub const fn with_operation_timeout(mut self, timeout: Duration) -> Self {
164        self.operation_timeout = timeout;
165        self
166    }
167
168    /// Set the topology mode
169    #[must_use]
170    pub const fn with_topology_mode(mut self, mode: TopologyMode) -> Self {
171        self.topology_mode = mode;
172        self
173    }
174
175    /// Set the pool configuration
176    #[must_use]
177    pub const fn with_pool_config(mut self, pool: PoolConfig) -> Self {
178        self.pool = pool;
179        self
180    }
181
182    /// Set the maximum number of redirects
183    #[must_use]
184    pub const fn with_max_redirects(mut self, max: usize) -> Self {
185        self.max_redirects = max;
186        self
187    }
188
189    /// Parse connection endpoints from connection string
190    #[must_use]
191    pub fn parse_endpoints(&self) -> Vec<(String, u16)> {
192        let conn_str = self.connection_string.trim();
193
194        // Strip redis:// prefix if present
195        let addr_part = conn_str
196            .strip_prefix("redis://")
197            .unwrap_or(conn_str)
198            .strip_prefix("rediss://")
199            .unwrap_or_else(|| conn_str.strip_prefix("redis://").unwrap_or(conn_str));
200
201        // Split by comma for multiple endpoints
202        addr_part
203            .split(',')
204            .filter_map(|endpoint| {
205                let endpoint = endpoint.trim();
206                if endpoint.is_empty() {
207                    return None;
208                }
209
210                // Parse host:port
211                if let Some((host, port_str)) = endpoint.rsplit_once(':') {
212                    if let Ok(port) = port_str.parse::<u16>() {
213                        return Some((host.to_string(), port));
214                    }
215                }
216
217                // Default port 6379 if not specified
218                Some((endpoint.to_string(), 6379))
219            })
220            .collect()
221    }
222}