1use crate::types::{
6 CacheCapacity, HostName, MaxConnections, MaxErrors, Port, ServerName, duration_serde,
7 option_duration_serde,
8};
9use serde::{Deserialize, Serialize};
10use std::time::Duration;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
14#[serde(rename_all = "lowercase")]
15pub enum RoutingMode {
16 Standard,
18 PerCommand,
20 Hybrid,
22}
23
24impl Default for RoutingMode {
25 fn default() -> Self {
29 Self::Hybrid
30 }
31}
32
33impl RoutingMode {
34 #[must_use]
36 pub fn supports_per_command_routing(&self) -> bool {
37 matches!(self, Self::PerCommand | Self::Hybrid)
38 }
39
40 #[must_use]
42 pub fn supports_stateful_commands(&self) -> bool {
43 matches!(self, Self::Standard | Self::Hybrid)
44 }
45}
46
47#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
49pub struct Config {
50 #[serde(default)]
52 pub servers: Vec<ServerConfig>,
53 #[serde(default)]
55 pub health_check: HealthCheckConfig,
56 #[serde(skip_serializing_if = "Option::is_none")]
58 pub cache: Option<CacheConfig>,
59 #[serde(default)]
61 pub client_auth: ClientAuthConfig,
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
66pub struct CacheConfig {
67 #[serde(default = "super::defaults::cache_max_capacity")]
69 pub max_capacity: CacheCapacity,
70 #[serde(with = "duration_serde", default = "super::defaults::cache_ttl")]
72 pub ttl: Duration,
73}
74
75impl Default for CacheConfig {
76 fn default() -> Self {
77 Self {
78 max_capacity: super::defaults::cache_max_capacity(),
79 ttl: super::defaults::cache_ttl(),
80 }
81 }
82}
83
84#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
86pub struct HealthCheckConfig {
87 #[serde(
89 with = "duration_serde",
90 default = "super::defaults::health_check_interval"
91 )]
92 pub interval: Duration,
93 #[serde(
95 with = "duration_serde",
96 default = "super::defaults::health_check_timeout"
97 )]
98 pub timeout: Duration,
99 #[serde(default = "super::defaults::unhealthy_threshold")]
101 pub unhealthy_threshold: MaxErrors,
102}
103
104impl Default for HealthCheckConfig {
105 fn default() -> Self {
106 Self {
107 interval: super::defaults::health_check_interval(),
108 timeout: super::defaults::health_check_timeout(),
109 unhealthy_threshold: super::defaults::unhealthy_threshold(),
110 }
111 }
112}
113
114#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
116pub struct ClientAuthConfig {
117 #[serde(skip_serializing_if = "Option::is_none")]
119 pub username: Option<String>,
120 #[serde(skip_serializing_if = "Option::is_none")]
122 pub password: Option<String>,
123 #[serde(skip_serializing_if = "Option::is_none")]
125 pub greeting: Option<String>,
126}
127
128impl ClientAuthConfig {
129 pub fn is_enabled(&self) -> bool {
131 self.username.is_some() && self.password.is_some()
132 }
133}
134
135#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
137pub struct ServerConfig {
138 pub host: HostName,
139 pub port: Port,
140 pub name: ServerName,
141 #[serde(skip_serializing_if = "Option::is_none")]
142 pub username: Option<String>,
143 #[serde(skip_serializing_if = "Option::is_none")]
144 pub password: Option<String>,
145 #[serde(default = "super::defaults::max_connections")]
147 pub max_connections: MaxConnections,
148
149 #[serde(default)]
151 pub use_tls: bool,
152 #[serde(default = "super::defaults::tls_verify_cert")]
154 pub tls_verify_cert: bool,
155 #[serde(skip_serializing_if = "Option::is_none")]
157 pub tls_cert_path: Option<String>,
158 #[serde(
161 with = "option_duration_serde",
162 default,
163 skip_serializing_if = "Option::is_none"
164 )]
165 pub connection_keepalive: Option<Duration>,
166 #[serde(default = "super::defaults::health_check_max_per_cycle")]
169 pub health_check_max_per_cycle: usize,
170 #[serde(
173 with = "duration_serde",
174 default = "super::defaults::health_check_pool_timeout"
175 )]
176 pub health_check_pool_timeout: Duration,
177}
178
179pub struct ServerConfigBuilder {
205 host: String,
206 port: u16,
207 name: Option<String>,
208 username: Option<String>,
209 password: Option<String>,
210 max_connections: Option<usize>,
211 use_tls: bool,
212 tls_verify_cert: bool,
213 tls_cert_path: Option<String>,
214 connection_keepalive: Option<Duration>,
215 health_check_max_per_cycle: Option<usize>,
216 health_check_pool_timeout: Option<Duration>,
217}
218
219impl ServerConfigBuilder {
220 #[must_use]
226 pub fn new(host: impl Into<String>, port: u16) -> Self {
227 Self {
228 host: host.into(),
229 port,
230 name: None,
231 username: None,
232 password: None,
233 max_connections: None,
234 use_tls: false,
235 tls_verify_cert: true, tls_cert_path: None,
237 connection_keepalive: None,
238 health_check_max_per_cycle: None,
239 health_check_pool_timeout: None,
240 }
241 }
242
243 #[must_use]
245 pub fn name(mut self, name: impl Into<String>) -> Self {
246 self.name = Some(name.into());
247 self
248 }
249
250 #[must_use]
252 pub fn username(mut self, username: impl Into<String>) -> Self {
253 self.username = Some(username.into());
254 self
255 }
256
257 #[must_use]
259 pub fn password(mut self, password: impl Into<String>) -> Self {
260 self.password = Some(password.into());
261 self
262 }
263
264 #[must_use]
266 pub fn max_connections(mut self, max: usize) -> Self {
267 self.max_connections = Some(max);
268 self
269 }
270
271 #[must_use]
273 pub fn use_tls(mut self, enabled: bool) -> Self {
274 self.use_tls = enabled;
275 self
276 }
277
278 #[must_use]
280 pub fn tls_verify_cert(mut self, verify: bool) -> Self {
281 self.tls_verify_cert = verify;
282 self
283 }
284
285 #[must_use]
287 pub fn tls_cert_path(mut self, path: impl Into<String>) -> Self {
288 self.tls_cert_path = Some(path.into());
289 self
290 }
291
292 #[must_use]
294 pub fn connection_keepalive(mut self, interval: Duration) -> Self {
295 self.connection_keepalive = Some(interval);
296 self
297 }
298
299 #[must_use]
301 pub fn health_check_max_per_cycle(mut self, max: usize) -> Self {
302 self.health_check_max_per_cycle = Some(max);
303 self
304 }
305
306 #[must_use]
308 pub fn health_check_pool_timeout(mut self, timeout: Duration) -> Self {
309 self.health_check_pool_timeout = Some(timeout);
310 self
311 }
312
313 pub fn build(self) -> Result<ServerConfig, anyhow::Error> {
323 use crate::types::{HostName, MaxConnections, Port, ServerName};
324
325 let host = HostName::new(self.host.clone())?;
326
327 let port = Port::new(self.port)
328 .ok_or_else(|| anyhow::anyhow!("Invalid port: {} (must be 1-65535)", self.port))?;
329
330 let name_str = self
331 .name
332 .unwrap_or_else(|| format!("{}:{}", self.host, self.port));
333 let name = ServerName::new(name_str)?;
334
335 let max_connections = if let Some(max) = self.max_connections {
336 MaxConnections::new(max)
337 .ok_or_else(|| anyhow::anyhow!("Invalid max_connections: {} (must be > 0)", max))?
338 } else {
339 super::defaults::max_connections()
340 };
341
342 let health_check_max_per_cycle = self
343 .health_check_max_per_cycle
344 .unwrap_or_else(super::defaults::health_check_max_per_cycle);
345
346 let health_check_pool_timeout = self
347 .health_check_pool_timeout
348 .unwrap_or_else(super::defaults::health_check_pool_timeout);
349
350 Ok(ServerConfig {
351 host,
352 port,
353 name,
354 username: self.username,
355 password: self.password,
356 max_connections,
357 use_tls: self.use_tls,
358 tls_verify_cert: self.tls_verify_cert,
359 tls_cert_path: self.tls_cert_path,
360 connection_keepalive: self.connection_keepalive,
361 health_check_max_per_cycle,
362 health_check_pool_timeout,
363 })
364 }
365}
366
367impl ServerConfig {
368 #[must_use]
382 pub fn builder(host: impl Into<String>, port: u16) -> ServerConfigBuilder {
383 ServerConfigBuilder::new(host, port)
384 }
385}