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, clap::ValueEnum)]
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 const fn supports_per_command_routing(&self) -> bool {
37 matches!(self, Self::PerCommand | Self::Hybrid)
38 }
39
40 #[must_use]
42 pub const fn supports_stateful_commands(&self) -> bool {
43 matches!(self, Self::Standard | Self::Hybrid)
44 }
45
46 #[must_use]
48 pub const fn as_str(&self) -> &'static str {
49 match self {
50 Self::Standard => "standard 1:1 mode",
51 Self::PerCommand => "per-command routing mode",
52 Self::Hybrid => "hybrid routing mode",
53 }
54 }
55}
56
57impl std::fmt::Display for RoutingMode {
58 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
59 f.write_str(self.as_str())
60 }
61}
62
63#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
65pub struct Config {
66 #[serde(default)]
68 pub servers: Vec<ServerConfig>,
69 #[serde(default)]
71 pub health_check: HealthCheckConfig,
72 #[serde(skip_serializing_if = "Option::is_none")]
74 pub cache: Option<CacheConfig>,
75 #[serde(default)]
77 pub client_auth: ClientAuthConfig,
78}
79
80#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
82pub struct CacheConfig {
83 #[serde(default = "super::defaults::cache_max_capacity")]
85 pub max_capacity: CacheCapacity,
86 #[serde(with = "duration_serde", default = "super::defaults::cache_ttl")]
88 pub ttl: Duration,
89}
90
91impl Default for CacheConfig {
92 fn default() -> Self {
93 Self {
94 max_capacity: super::defaults::cache_max_capacity(),
95 ttl: super::defaults::cache_ttl(),
96 }
97 }
98}
99
100#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
102pub struct HealthCheckConfig {
103 #[serde(
105 with = "duration_serde",
106 default = "super::defaults::health_check_interval"
107 )]
108 pub interval: Duration,
109 #[serde(
111 with = "duration_serde",
112 default = "super::defaults::health_check_timeout"
113 )]
114 pub timeout: Duration,
115 #[serde(default = "super::defaults::unhealthy_threshold")]
117 pub unhealthy_threshold: MaxErrors,
118}
119
120impl Default for HealthCheckConfig {
121 fn default() -> Self {
122 Self {
123 interval: super::defaults::health_check_interval(),
124 timeout: super::defaults::health_check_timeout(),
125 unhealthy_threshold: super::defaults::unhealthy_threshold(),
126 }
127 }
128}
129
130#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
132pub struct ClientAuthConfig {
133 #[serde(skip_serializing_if = "Option::is_none")]
135 pub username: Option<String>,
136 #[serde(skip_serializing_if = "Option::is_none")]
138 pub password: Option<String>,
139 #[serde(skip_serializing_if = "Option::is_none")]
141 pub greeting: Option<String>,
142}
143
144impl ClientAuthConfig {
145 pub fn is_enabled(&self) -> bool {
147 self.username.is_some() && self.password.is_some()
148 }
149}
150
151#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
153pub struct ServerConfig {
154 pub host: HostName,
155 pub port: Port,
156 pub name: ServerName,
157 #[serde(skip_serializing_if = "Option::is_none")]
158 pub username: Option<String>,
159 #[serde(skip_serializing_if = "Option::is_none")]
160 pub password: Option<String>,
161 #[serde(default = "super::defaults::max_connections")]
163 pub max_connections: MaxConnections,
164
165 #[serde(default)]
167 pub use_tls: bool,
168 #[serde(default = "super::defaults::tls_verify_cert")]
170 pub tls_verify_cert: bool,
171 #[serde(skip_serializing_if = "Option::is_none")]
173 pub tls_cert_path: Option<String>,
174 #[serde(
177 with = "option_duration_serde",
178 default,
179 skip_serializing_if = "Option::is_none"
180 )]
181 pub connection_keepalive: Option<Duration>,
182 #[serde(default = "super::defaults::health_check_max_per_cycle")]
185 pub health_check_max_per_cycle: usize,
186 #[serde(
189 with = "duration_serde",
190 default = "super::defaults::health_check_pool_timeout"
191 )]
192 pub health_check_pool_timeout: Duration,
193}
194
195pub struct ServerConfigBuilder {
221 host: String,
222 port: u16,
223 name: Option<String>,
224 username: Option<String>,
225 password: Option<String>,
226 max_connections: Option<usize>,
227 use_tls: bool,
228 tls_verify_cert: bool,
229 tls_cert_path: Option<String>,
230 connection_keepalive: Option<Duration>,
231 health_check_max_per_cycle: Option<usize>,
232 health_check_pool_timeout: Option<Duration>,
233}
234
235impl ServerConfigBuilder {
236 #[must_use]
242 pub fn new(host: impl Into<String>, port: u16) -> Self {
243 Self {
244 host: host.into(),
245 port,
246 name: None,
247 username: None,
248 password: None,
249 max_connections: None,
250 use_tls: false,
251 tls_verify_cert: true, tls_cert_path: None,
253 connection_keepalive: None,
254 health_check_max_per_cycle: None,
255 health_check_pool_timeout: None,
256 }
257 }
258
259 #[must_use]
261 pub fn name(mut self, name: impl Into<String>) -> Self {
262 self.name = Some(name.into());
263 self
264 }
265
266 #[must_use]
268 pub fn username(mut self, username: impl Into<String>) -> Self {
269 self.username = Some(username.into());
270 self
271 }
272
273 #[must_use]
275 pub fn password(mut self, password: impl Into<String>) -> Self {
276 self.password = Some(password.into());
277 self
278 }
279
280 #[must_use]
282 pub fn max_connections(mut self, max: usize) -> Self {
283 self.max_connections = Some(max);
284 self
285 }
286
287 #[must_use]
289 pub fn use_tls(mut self, enabled: bool) -> Self {
290 self.use_tls = enabled;
291 self
292 }
293
294 #[must_use]
296 pub fn tls_verify_cert(mut self, verify: bool) -> Self {
297 self.tls_verify_cert = verify;
298 self
299 }
300
301 #[must_use]
303 pub fn tls_cert_path(mut self, path: impl Into<String>) -> Self {
304 self.tls_cert_path = Some(path.into());
305 self
306 }
307
308 #[must_use]
310 pub fn connection_keepalive(mut self, interval: Duration) -> Self {
311 self.connection_keepalive = Some(interval);
312 self
313 }
314
315 #[must_use]
317 pub fn health_check_max_per_cycle(mut self, max: usize) -> Self {
318 self.health_check_max_per_cycle = Some(max);
319 self
320 }
321
322 #[must_use]
324 pub fn health_check_pool_timeout(mut self, timeout: Duration) -> Self {
325 self.health_check_pool_timeout = Some(timeout);
326 self
327 }
328
329 pub fn build(self) -> Result<ServerConfig, anyhow::Error> {
339 use crate::types::{HostName, MaxConnections, Port, ServerName};
340
341 let host = HostName::new(self.host.clone())?;
342
343 let port = Port::new(self.port)
344 .ok_or_else(|| anyhow::anyhow!("Invalid port: {} (must be 1-65535)", self.port))?;
345
346 let name_str = self
347 .name
348 .unwrap_or_else(|| format!("{}:{}", self.host, self.port));
349 let name = ServerName::new(name_str)?;
350
351 let max_connections = if let Some(max) = self.max_connections {
352 MaxConnections::new(max)
353 .ok_or_else(|| anyhow::anyhow!("Invalid max_connections: {} (must be > 0)", max))?
354 } else {
355 super::defaults::max_connections()
356 };
357
358 let health_check_max_per_cycle = self
359 .health_check_max_per_cycle
360 .unwrap_or_else(super::defaults::health_check_max_per_cycle);
361
362 let health_check_pool_timeout = self
363 .health_check_pool_timeout
364 .unwrap_or_else(super::defaults::health_check_pool_timeout);
365
366 Ok(ServerConfig {
367 host,
368 port,
369 name,
370 username: self.username,
371 password: self.password,
372 max_connections,
373 use_tls: self.use_tls,
374 tls_verify_cert: self.tls_verify_cert,
375 tls_cert_path: self.tls_cert_path,
376 connection_keepalive: self.connection_keepalive,
377 health_check_max_per_cycle,
378 health_check_pool_timeout,
379 })
380 }
381}
382
383impl ServerConfig {
384 #[must_use]
398 pub fn builder(host: impl Into<String>, port: u16) -> ServerConfigBuilder {
399 ServerConfigBuilder::new(host, port)
400 }
401}