1use super::{ConsistencyLevel, RouteTarget};
6use std::collections::HashMap;
7use std::time::Duration;
8
9#[derive(Debug, Clone)]
11pub struct RoutingConfig {
12 pub default: DefaultPolicy,
14 pub hints: HintConfig,
16 pub consistency: HashMap<ConsistencyLevel, ConsistencyConfig>,
18 pub aliases: HashMap<String, Vec<String>>,
20 pub routes: HashMap<RouteTarget, RouteConfig>,
22}
23
24impl Default for RoutingConfig {
25 fn default() -> Self {
26 let mut consistency = HashMap::new();
27 consistency.insert(
28 ConsistencyLevel::Strong,
29 ConsistencyConfig {
30 allowed_nodes: vec!["primary".to_string(), "standby-sync".to_string()],
31 max_lag_ms: 0,
32 },
33 );
34 consistency.insert(
35 ConsistencyLevel::Bounded,
36 ConsistencyConfig {
37 allowed_nodes: vec![
38 "primary".to_string(),
39 "standby-sync".to_string(),
40 "standby-semisync".to_string(),
41 ],
42 max_lag_ms: 1000,
43 },
44 );
45 consistency.insert(
46 ConsistencyLevel::Eventual,
47 ConsistencyConfig {
48 allowed_nodes: vec!["*".to_string()],
49 max_lag_ms: u64::MAX,
50 },
51 );
52
53 Self {
54 default: DefaultPolicy::default(),
55 hints: HintConfig::default(),
56 consistency,
57 aliases: HashMap::new(),
58 routes: HashMap::new(),
59 }
60 }
61}
62
63impl RoutingConfig {
64 pub fn new() -> Self {
66 Self::default()
67 }
68
69 pub fn add_alias(&mut self, alias: &str, nodes: Vec<String>) {
71 self.aliases.insert(alias.to_string(), nodes);
72 }
73
74 pub fn resolve_alias(&self, alias: &str) -> Option<&Vec<String>> {
76 self.aliases.get(alias)
77 }
78
79 pub fn get_consistency_config(&self, level: ConsistencyLevel) -> Option<&ConsistencyConfig> {
81 self.consistency.get(&level)
82 }
83
84 pub fn is_hint_allowed(&self, hint_name: &str) -> bool {
86 if !self.hints.enabled {
87 return false;
88 }
89
90 match hint_name {
91 "node" => self.hints.allow_node_hints,
92 "route" => true,
93 "primary" => self.hints.allow_primary_reads,
94 _ => true,
95 }
96 }
97}
98
99#[derive(Debug, Clone)]
101pub struct DefaultPolicy {
102 pub read_target: RouteTarget,
104 pub write_target: RouteTarget,
106 pub consistency: ConsistencyLevel,
108 pub timeout: Duration,
110 pub auto_detect_writes: bool,
112}
113
114impl Default for DefaultPolicy {
115 fn default() -> Self {
116 Self {
117 read_target: RouteTarget::Standby,
118 write_target: RouteTarget::Primary,
119 consistency: ConsistencyLevel::Eventual,
120 timeout: Duration::from_secs(30),
121 auto_detect_writes: true,
122 }
123 }
124}
125
126#[derive(Debug, Clone)]
128pub struct HintConfig {
129 pub enabled: bool,
131 pub allow_node_hints: bool,
133 pub allow_primary_reads: bool,
135 pub require_auth: bool,
137 pub strip_hints: bool,
139 pub log_decisions: bool,
141 pub max_lag_override: Option<Duration>,
143}
144
145impl Default for HintConfig {
146 fn default() -> Self {
147 Self {
148 enabled: true,
149 allow_node_hints: true,
150 allow_primary_reads: true,
151 require_auth: false,
152 strip_hints: true,
153 log_decisions: false,
154 max_lag_override: Some(Duration::from_secs(60)),
155 }
156 }
157}
158
159#[derive(Debug, Clone)]
161pub struct ConsistencyConfig {
162 pub allowed_nodes: Vec<String>,
164 pub max_lag_ms: u64,
166}
167
168impl ConsistencyConfig {
169 pub fn allows_node(&self, node_name: &str) -> bool {
171 self.allowed_nodes.iter().any(|pattern| {
172 if pattern == "*" {
173 true
174 } else if pattern.ends_with('*') {
175 node_name.starts_with(&pattern[..pattern.len() - 1])
176 } else {
177 node_name == pattern
178 }
179 })
180 }
181}
182
183#[derive(Debug, Clone)]
185pub struct RouteConfig {
186 pub node_patterns: Vec<String>,
188 pub max_lag_ms: u64,
190 pub load_balance: Option<LoadBalanceStrategy>,
192}
193
194#[derive(Debug, Clone, Copy, PartialEq, Eq)]
196pub enum LoadBalanceStrategy {
197 RoundRobin,
199 Weighted,
201 LeastConnections,
203 LatencyBased,
205 Random,
207}
208
209#[derive(Debug, Clone)]
211pub struct AliasConfig {
212 pub name: String,
214 pub nodes: Vec<String>,
216 pub auto: bool,
218}
219
220impl AliasConfig {
221 pub fn new(name: &str, nodes: Vec<String>) -> Self {
223 Self {
224 name: name.to_string(),
225 nodes,
226 auto: false,
227 }
228 }
229
230 pub fn auto(name: &str) -> Self {
232 Self {
233 name: name.to_string(),
234 nodes: Vec::new(),
235 auto: true,
236 }
237 }
238}
239
240#[cfg(test)]
241mod tests {
242 use super::*;
243
244 #[test]
245 fn test_default_config() {
246 let config = RoutingConfig::default();
247
248 assert!(config.hints.enabled);
249 assert!(config.hints.allow_node_hints);
250 assert!(config.hints.allow_primary_reads);
251 assert_eq!(config.default.read_target, RouteTarget::Standby);
252 assert_eq!(config.default.write_target, RouteTarget::Primary);
253 }
254
255 #[test]
256 fn test_consistency_config() {
257 let config = RoutingConfig::default();
258
259 let strong = config
260 .get_consistency_config(ConsistencyLevel::Strong)
261 .unwrap();
262 assert_eq!(strong.max_lag_ms, 0);
263 assert!(strong.allowed_nodes.contains(&"primary".to_string()));
264
265 let eventual = config
266 .get_consistency_config(ConsistencyLevel::Eventual)
267 .unwrap();
268 assert!(eventual.allows_node("any-node"));
269 }
270
271 #[test]
272 fn test_alias_resolution() {
273 let mut config = RoutingConfig::default();
274 config.add_alias(
275 "vector",
276 vec![
277 "standby-vector-1".to_string(),
278 "standby-vector-2".to_string(),
279 ],
280 );
281
282 let nodes = config.resolve_alias("vector").unwrap();
283 assert_eq!(nodes.len(), 2);
284 }
285
286 #[test]
287 fn test_node_pattern_matching() {
288 let config = ConsistencyConfig {
289 allowed_nodes: vec!["standby-*".to_string()],
290 max_lag_ms: 1000,
291 };
292
293 assert!(config.allows_node("standby-sync-1"));
294 assert!(config.allows_node("standby-async-2"));
295 assert!(!config.allows_node("primary"));
296 }
297
298 #[test]
299 fn test_hint_allowed() {
300 let mut config = RoutingConfig::default();
301 assert!(config.is_hint_allowed("route"));
302 assert!(config.is_hint_allowed("node"));
303
304 config.hints.allow_node_hints = false;
305 assert!(!config.is_hint_allowed("node"));
306
307 config.hints.enabled = false;
308 assert!(!config.is_hint_allowed("route"));
309 }
310}