1use super::{RouteTarget, ConsistencyLevel};
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(ConsistencyLevel::Strong, ConsistencyConfig {
28 allowed_nodes: vec!["primary".to_string(), "standby-sync".to_string()],
29 max_lag_ms: 0,
30 });
31 consistency.insert(ConsistencyLevel::Bounded, ConsistencyConfig {
32 allowed_nodes: vec!["primary".to_string(), "standby-sync".to_string(), "standby-semisync".to_string()],
33 max_lag_ms: 1000,
34 });
35 consistency.insert(ConsistencyLevel::Eventual, ConsistencyConfig {
36 allowed_nodes: vec!["*".to_string()],
37 max_lag_ms: u64::MAX,
38 });
39
40 Self {
41 default: DefaultPolicy::default(),
42 hints: HintConfig::default(),
43 consistency,
44 aliases: HashMap::new(),
45 routes: HashMap::new(),
46 }
47 }
48}
49
50impl RoutingConfig {
51 pub fn new() -> Self {
53 Self::default()
54 }
55
56 pub fn add_alias(&mut self, alias: &str, nodes: Vec<String>) {
58 self.aliases.insert(alias.to_string(), nodes);
59 }
60
61 pub fn resolve_alias(&self, alias: &str) -> Option<&Vec<String>> {
63 self.aliases.get(alias)
64 }
65
66 pub fn get_consistency_config(&self, level: ConsistencyLevel) -> Option<&ConsistencyConfig> {
68 self.consistency.get(&level)
69 }
70
71 pub fn is_hint_allowed(&self, hint_name: &str) -> bool {
73 if !self.hints.enabled {
74 return false;
75 }
76
77 match hint_name {
78 "node" => self.hints.allow_node_hints,
79 "route" => true,
80 "primary" => self.hints.allow_primary_reads,
81 _ => true,
82 }
83 }
84}
85
86#[derive(Debug, Clone)]
88pub struct DefaultPolicy {
89 pub read_target: RouteTarget,
91 pub write_target: RouteTarget,
93 pub consistency: ConsistencyLevel,
95 pub timeout: Duration,
97 pub auto_detect_writes: bool,
99}
100
101impl Default for DefaultPolicy {
102 fn default() -> Self {
103 Self {
104 read_target: RouteTarget::Standby,
105 write_target: RouteTarget::Primary,
106 consistency: ConsistencyLevel::Eventual,
107 timeout: Duration::from_secs(30),
108 auto_detect_writes: true,
109 }
110 }
111}
112
113#[derive(Debug, Clone)]
115pub struct HintConfig {
116 pub enabled: bool,
118 pub allow_node_hints: bool,
120 pub allow_primary_reads: bool,
122 pub require_auth: bool,
124 pub strip_hints: bool,
126 pub log_decisions: bool,
128 pub max_lag_override: Option<Duration>,
130}
131
132impl Default for HintConfig {
133 fn default() -> Self {
134 Self {
135 enabled: true,
136 allow_node_hints: true,
137 allow_primary_reads: true,
138 require_auth: false,
139 strip_hints: true,
140 log_decisions: false,
141 max_lag_override: Some(Duration::from_secs(60)),
142 }
143 }
144}
145
146#[derive(Debug, Clone)]
148pub struct ConsistencyConfig {
149 pub allowed_nodes: Vec<String>,
151 pub max_lag_ms: u64,
153}
154
155impl ConsistencyConfig {
156 pub fn allows_node(&self, node_name: &str) -> bool {
158 self.allowed_nodes.iter().any(|pattern| {
159 if pattern == "*" {
160 true
161 } else if pattern.ends_with('*') {
162 node_name.starts_with(&pattern[..pattern.len()-1])
163 } else {
164 node_name == pattern
165 }
166 })
167 }
168}
169
170#[derive(Debug, Clone)]
172pub struct RouteConfig {
173 pub node_patterns: Vec<String>,
175 pub max_lag_ms: u64,
177 pub load_balance: Option<LoadBalanceStrategy>,
179}
180
181#[derive(Debug, Clone, Copy, PartialEq, Eq)]
183pub enum LoadBalanceStrategy {
184 RoundRobin,
186 Weighted,
188 LeastConnections,
190 LatencyBased,
192 Random,
194}
195
196#[derive(Debug, Clone)]
198pub struct AliasConfig {
199 pub name: String,
201 pub nodes: Vec<String>,
203 pub auto: bool,
205}
206
207impl AliasConfig {
208 pub fn new(name: &str, nodes: Vec<String>) -> Self {
210 Self {
211 name: name.to_string(),
212 nodes,
213 auto: false,
214 }
215 }
216
217 pub fn auto(name: &str) -> Self {
219 Self {
220 name: name.to_string(),
221 nodes: Vec::new(),
222 auto: true,
223 }
224 }
225}
226
227#[cfg(test)]
228mod tests {
229 use super::*;
230
231 #[test]
232 fn test_default_config() {
233 let config = RoutingConfig::default();
234
235 assert!(config.hints.enabled);
236 assert!(config.hints.allow_node_hints);
237 assert!(config.hints.allow_primary_reads);
238 assert_eq!(config.default.read_target, RouteTarget::Standby);
239 assert_eq!(config.default.write_target, RouteTarget::Primary);
240 }
241
242 #[test]
243 fn test_consistency_config() {
244 let config = RoutingConfig::default();
245
246 let strong = config.get_consistency_config(ConsistencyLevel::Strong).unwrap();
247 assert_eq!(strong.max_lag_ms, 0);
248 assert!(strong.allowed_nodes.contains(&"primary".to_string()));
249
250 let eventual = config.get_consistency_config(ConsistencyLevel::Eventual).unwrap();
251 assert!(eventual.allows_node("any-node"));
252 }
253
254 #[test]
255 fn test_alias_resolution() {
256 let mut config = RoutingConfig::default();
257 config.add_alias("vector", vec!["standby-vector-1".to_string(), "standby-vector-2".to_string()]);
258
259 let nodes = config.resolve_alias("vector").unwrap();
260 assert_eq!(nodes.len(), 2);
261 }
262
263 #[test]
264 fn test_node_pattern_matching() {
265 let config = ConsistencyConfig {
266 allowed_nodes: vec!["standby-*".to_string()],
267 max_lag_ms: 1000,
268 };
269
270 assert!(config.allows_node("standby-sync-1"));
271 assert!(config.allows_node("standby-async-2"));
272 assert!(!config.allows_node("primary"));
273 }
274
275 #[test]
276 fn test_hint_allowed() {
277 let mut config = RoutingConfig::default();
278 assert!(config.is_hint_allowed("route"));
279 assert!(config.is_hint_allowed("node"));
280
281 config.hints.allow_node_hints = false;
282 assert!(!config.is_hint_allowed("node"));
283
284 config.hints.enabled = false;
285 assert!(!config.is_hint_allowed("route"));
286 }
287}