use super::strategy::DeduplicationStrategy;
use crate::error::StateResult;
use serde::{Deserialize, Serialize};
use std::time::Duration;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeduplicationConfig {
pub strategy: DeduplicationStrategy,
#[serde(with = "humantime_serde")]
pub ttl: Duration,
pub key_prefix: String,
#[serde(default = "default_true")]
pub metrics_enabled: bool,
pub circuit_breaker_threshold: usize,
#[serde(with = "humantime_serde")]
pub circuit_breaker_timeout: Duration,
pub batch_size: usize,
#[serde(default = "default_true")]
pub tracing_enabled: bool,
pub trace_sample_rate: f64,
}
impl Default for DeduplicationConfig {
fn default() -> Self {
Self {
strategy: DeduplicationStrategy::BestEffort,
ttl: Duration::from_secs(3600), key_prefix: "dedup:".to_string(),
metrics_enabled: true,
circuit_breaker_threshold: 5,
circuit_breaker_timeout: Duration::from_secs(30),
batch_size: 100,
tracing_enabled: true,
trace_sample_rate: 0.1,
}
}
}
impl DeduplicationConfig {
pub fn builder() -> DeduplicationConfigBuilder {
DeduplicationConfigBuilder::default()
}
pub fn validate(&self) -> StateResult<()> {
if self.key_prefix.is_empty() {
return Err(crate::error::StateError::StorageError {
backend_type: "deduplication".to_string(),
details: "key_prefix cannot be empty".to_string(),
});
}
if self.ttl.as_secs() == 0 {
return Err(crate::error::StateError::StorageError {
backend_type: "deduplication".to_string(),
details: "TTL must be greater than 0".to_string(),
});
}
if self.circuit_breaker_threshold == 0 {
return Err(crate::error::StateError::StorageError {
backend_type: "deduplication".to_string(),
details: "circuit_breaker_threshold must be greater than 0".to_string(),
});
}
if self.batch_size == 0 {
return Err(crate::error::StateError::StorageError {
backend_type: "deduplication".to_string(),
details: "batch_size must be greater than 0".to_string(),
});
}
if !(0.0..=1.0).contains(&self.trace_sample_rate) {
return Err(crate::error::StateError::StorageError {
backend_type: "deduplication".to_string(),
details: "trace_sample_rate must be between 0.0 and 1.0".to_string(),
});
}
Ok(())
}
}
#[derive(Debug, Default)]
pub struct DeduplicationConfigBuilder {
config: DeduplicationConfig,
}
impl DeduplicationConfigBuilder {
pub fn strategy(mut self, strategy: DeduplicationStrategy) -> Self {
self.config.strategy = strategy;
self
}
pub fn ttl(mut self, ttl: Duration) -> Self {
self.config.ttl = ttl;
self
}
pub fn key_prefix(mut self, prefix: impl Into<String>) -> Self {
self.config.key_prefix = prefix.into();
self
}
pub fn metrics_enabled(mut self, enabled: bool) -> Self {
self.config.metrics_enabled = enabled;
self
}
pub fn circuit_breaker(mut self, threshold: usize, timeout: Duration) -> Self {
self.config.circuit_breaker_threshold = threshold;
self.config.circuit_breaker_timeout = timeout;
self
}
pub fn batch_size(mut self, size: usize) -> Self {
self.config.batch_size = size;
self
}
pub fn tracing_enabled(mut self, enabled: bool) -> Self {
self.config.tracing_enabled = enabled;
self
}
pub fn trace_sample_rate(mut self, rate: f64) -> Self {
self.config.trace_sample_rate = rate;
self
}
pub fn build(self) -> StateResult<DeduplicationConfig> {
self.config.validate()?;
Ok(self.config)
}
}
fn default_true() -> bool {
true
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config() {
let config = DeduplicationConfig::default();
assert!(config.validate().is_ok());
assert_eq!(config.strategy, DeduplicationStrategy::BestEffort);
assert_eq!(config.ttl.as_secs(), 3600);
assert_eq!(config.key_prefix, "dedup:");
}
#[test]
fn test_config_builder() {
let config = DeduplicationConfig::builder()
.strategy(DeduplicationStrategy::Strict)
.ttl(Duration::from_secs(7200))
.key_prefix("myapp:dedup:")
.metrics_enabled(true)
.circuit_breaker(10, Duration::from_secs(60))
.batch_size(200)
.tracing_enabled(true)
.trace_sample_rate(0.5)
.build()
.unwrap();
assert_eq!(config.strategy, DeduplicationStrategy::Strict);
assert_eq!(config.ttl.as_secs(), 7200);
assert_eq!(config.key_prefix, "myapp:dedup:");
assert_eq!(config.circuit_breaker_threshold, 10);
assert_eq!(config.batch_size, 200);
assert_eq!(config.trace_sample_rate, 0.5);
}
#[test]
fn test_config_validation() {
let mut config = DeduplicationConfig::default();
config.key_prefix = String::new();
assert!(config.validate().is_err());
let mut config = DeduplicationConfig::default();
config.ttl = Duration::from_secs(0);
assert!(config.validate().is_err());
let mut config = DeduplicationConfig::default();
config.trace_sample_rate = 1.5;
assert!(config.validate().is_err());
}
#[test]
fn test_config_serialization() {
let config = DeduplicationConfig::default();
let json = serde_json::to_string(&config).unwrap();
let deserialized: DeduplicationConfig = serde_json::from_str(&json).unwrap();
assert_eq!(config.strategy, deserialized.strategy);
}
}