use super::{RouteTarget, ConsistencyLevel};
use std::collections::HashMap;
use std::time::Duration;
#[derive(Debug, Clone)]
pub struct RoutingConfig {
pub default: DefaultPolicy,
pub hints: HintConfig,
pub consistency: HashMap<ConsistencyLevel, ConsistencyConfig>,
pub aliases: HashMap<String, Vec<String>>,
pub routes: HashMap<RouteTarget, RouteConfig>,
}
impl Default for RoutingConfig {
fn default() -> Self {
let mut consistency = HashMap::new();
consistency.insert(ConsistencyLevel::Strong, ConsistencyConfig {
allowed_nodes: vec!["primary".to_string(), "standby-sync".to_string()],
max_lag_ms: 0,
});
consistency.insert(ConsistencyLevel::Bounded, ConsistencyConfig {
allowed_nodes: vec!["primary".to_string(), "standby-sync".to_string(), "standby-semisync".to_string()],
max_lag_ms: 1000,
});
consistency.insert(ConsistencyLevel::Eventual, ConsistencyConfig {
allowed_nodes: vec!["*".to_string()],
max_lag_ms: u64::MAX,
});
Self {
default: DefaultPolicy::default(),
hints: HintConfig::default(),
consistency,
aliases: HashMap::new(),
routes: HashMap::new(),
}
}
}
impl RoutingConfig {
pub fn new() -> Self {
Self::default()
}
pub fn add_alias(&mut self, alias: &str, nodes: Vec<String>) {
self.aliases.insert(alias.to_string(), nodes);
}
pub fn resolve_alias(&self, alias: &str) -> Option<&Vec<String>> {
self.aliases.get(alias)
}
pub fn get_consistency_config(&self, level: ConsistencyLevel) -> Option<&ConsistencyConfig> {
self.consistency.get(&level)
}
pub fn is_hint_allowed(&self, hint_name: &str) -> bool {
if !self.hints.enabled {
return false;
}
match hint_name {
"node" => self.hints.allow_node_hints,
"route" => true,
"primary" => self.hints.allow_primary_reads,
_ => true,
}
}
}
#[derive(Debug, Clone)]
pub struct DefaultPolicy {
pub read_target: RouteTarget,
pub write_target: RouteTarget,
pub consistency: ConsistencyLevel,
pub timeout: Duration,
pub auto_detect_writes: bool,
}
impl Default for DefaultPolicy {
fn default() -> Self {
Self {
read_target: RouteTarget::Standby,
write_target: RouteTarget::Primary,
consistency: ConsistencyLevel::Eventual,
timeout: Duration::from_secs(30),
auto_detect_writes: true,
}
}
}
#[derive(Debug, Clone)]
pub struct HintConfig {
pub enabled: bool,
pub allow_node_hints: bool,
pub allow_primary_reads: bool,
pub require_auth: bool,
pub strip_hints: bool,
pub log_decisions: bool,
pub max_lag_override: Option<Duration>,
}
impl Default for HintConfig {
fn default() -> Self {
Self {
enabled: true,
allow_node_hints: true,
allow_primary_reads: true,
require_auth: false,
strip_hints: true,
log_decisions: false,
max_lag_override: Some(Duration::from_secs(60)),
}
}
}
#[derive(Debug, Clone)]
pub struct ConsistencyConfig {
pub allowed_nodes: Vec<String>,
pub max_lag_ms: u64,
}
impl ConsistencyConfig {
pub fn allows_node(&self, node_name: &str) -> bool {
self.allowed_nodes.iter().any(|pattern| {
if pattern == "*" {
true
} else if pattern.ends_with('*') {
node_name.starts_with(&pattern[..pattern.len()-1])
} else {
node_name == pattern
}
})
}
}
#[derive(Debug, Clone)]
pub struct RouteConfig {
pub node_patterns: Vec<String>,
pub max_lag_ms: u64,
pub load_balance: Option<LoadBalanceStrategy>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LoadBalanceStrategy {
RoundRobin,
Weighted,
LeastConnections,
LatencyBased,
Random,
}
#[derive(Debug, Clone)]
pub struct AliasConfig {
pub name: String,
pub nodes: Vec<String>,
pub auto: bool,
}
impl AliasConfig {
pub fn new(name: &str, nodes: Vec<String>) -> Self {
Self {
name: name.to_string(),
nodes,
auto: false,
}
}
pub fn auto(name: &str) -> Self {
Self {
name: name.to_string(),
nodes: Vec::new(),
auto: true,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config() {
let config = RoutingConfig::default();
assert!(config.hints.enabled);
assert!(config.hints.allow_node_hints);
assert!(config.hints.allow_primary_reads);
assert_eq!(config.default.read_target, RouteTarget::Standby);
assert_eq!(config.default.write_target, RouteTarget::Primary);
}
#[test]
fn test_consistency_config() {
let config = RoutingConfig::default();
let strong = config.get_consistency_config(ConsistencyLevel::Strong).unwrap();
assert_eq!(strong.max_lag_ms, 0);
assert!(strong.allowed_nodes.contains(&"primary".to_string()));
let eventual = config.get_consistency_config(ConsistencyLevel::Eventual).unwrap();
assert!(eventual.allows_node("any-node"));
}
#[test]
fn test_alias_resolution() {
let mut config = RoutingConfig::default();
config.add_alias("vector", vec!["standby-vector-1".to_string(), "standby-vector-2".to_string()]);
let nodes = config.resolve_alias("vector").unwrap();
assert_eq!(nodes.len(), 2);
}
#[test]
fn test_node_pattern_matching() {
let config = ConsistencyConfig {
allowed_nodes: vec!["standby-*".to_string()],
max_lag_ms: 1000,
};
assert!(config.allows_node("standby-sync-1"));
assert!(config.allows_node("standby-async-2"));
assert!(!config.allows_node("primary"));
}
#[test]
fn test_hint_allowed() {
let mut config = RoutingConfig::default();
assert!(config.is_hint_allowed("route"));
assert!(config.is_hint_allowed("node"));
config.hints.allow_node_hints = false;
assert!(!config.is_hint_allowed("node"));
config.hints.enabled = false;
assert!(!config.is_hint_allowed("route"));
}
}