Skip to main content

redis_oxide/core/
config.rs

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