1use std::collections::HashMap;
4use std::time::{Duration, Instant};
5
6use super::{Alert, AlertChannel, AlertSeverity, DeliveryResult};
7
8#[derive(Debug)]
10struct RateLimiter {
11 max_alerts: usize,
13 window: Duration,
15 timestamps: HashMap<String, Vec<Instant>>,
17}
18
19impl RateLimiter {
20 fn new(max_alerts: usize, window: Duration) -> Self {
21 Self {
22 max_alerts,
23 window,
24 timestamps: HashMap::new(),
25 }
26 }
27
28 fn should_allow(&mut self, source: &str) -> bool {
29 let now = Instant::now();
30 let timestamps = self.timestamps.entry(source.to_string()).or_default();
31
32 timestamps.retain(|t| now.duration_since(*t) < self.window);
34
35 if timestamps.len() >= self.max_alerts {
36 false
37 } else {
38 timestamps.push(now);
39 true
40 }
41 }
42}
43
44#[derive(Debug)]
46struct Deduplicator {
47 seen: HashMap<String, Instant>,
49 window: Duration,
51}
52
53impl Deduplicator {
54 fn new(window: Duration) -> Self {
55 Self {
56 seen: HashMap::new(),
57 window,
58 }
59 }
60
61 fn is_duplicate(&mut self, alert_id: &str) -> bool {
62 let now = Instant::now();
63
64 self.seen.retain(|_, expiry| now < *expiry);
66
67 if self.seen.contains_key(alert_id) {
68 true
69 } else {
70 self.seen.insert(alert_id.to_string(), now + self.window);
71 false
72 }
73 }
74}
75
76#[derive(Debug, Clone)]
78pub struct AlertRouterConfig {
79 pub severity_routes: HashMap<AlertSeverity, Vec<AlertChannel>>,
81 pub default_channels: Vec<AlertChannel>,
83 pub rate_limit_per_minute: usize,
85 pub dedup_window_sec: u64,
87 pub dry_run: bool,
89}
90
91impl Default for AlertRouterConfig {
92 fn default() -> Self {
93 Self {
94 severity_routes: HashMap::new(),
95 default_channels: vec![AlertChannel::Console],
96 rate_limit_per_minute: 60,
97 dedup_window_sec: 300, dry_run: false,
99 }
100 }
101}
102
103#[derive(Debug)]
105pub struct AlertRouter {
106 config: AlertRouterConfig,
108 rate_limiter: RateLimiter,
110 deduplicator: Deduplicator,
112 history: Vec<Alert>,
114 max_history: usize,
116 delivery_results: Vec<DeliveryResult>,
118}
119
120impl Default for AlertRouter {
121 fn default() -> Self {
122 Self::new(AlertRouterConfig::default())
123 }
124}
125
126impl AlertRouter {
127 pub fn new(config: AlertRouterConfig) -> Self {
129 let rate_limiter = RateLimiter::new(config.rate_limit_per_minute, Duration::from_secs(60));
130 let deduplicator = Deduplicator::new(Duration::from_secs(config.dedup_window_sec));
131
132 Self {
133 config,
134 rate_limiter,
135 deduplicator,
136 history: Vec::new(),
137 max_history: 1000,
138 delivery_results: Vec::new(),
139 }
140 }
141
142 pub fn with_dry_run(mut self, dry_run: bool) -> Self {
144 self.config.dry_run = dry_run;
145 self
146 }
147
148 pub fn with_max_history(mut self, max: usize) -> Self {
150 self.max_history = max;
151 self
152 }
153
154 pub fn add_route(&mut self, severity: AlertSeverity, channel: AlertChannel) {
156 self.config
157 .severity_routes
158 .entry(severity)
159 .or_default()
160 .push(channel);
161 }
162
163 pub fn add_default_channel(&mut self, channel: AlertChannel) {
165 self.config.default_channels.push(channel);
166 }
167
168 fn get_channels(&self, severity: AlertSeverity) -> Vec<&AlertChannel> {
170 let mut channels: Vec<&AlertChannel> = self
171 .config
172 .severity_routes
173 .get(&severity)
174 .map(|c| c.iter().collect())
175 .unwrap_or_default();
176
177 if channels.is_empty() {
178 channels = self.config.default_channels.iter().collect();
179 }
180
181 channels
182 }
183
184 fn send_to_channel(&self, alert: &Alert, channel: &AlertChannel) -> DeliveryResult {
186 if self.config.dry_run {
187 return DeliveryResult::success(channel.name(), 0);
188 }
189
190 match channel {
191 AlertChannel::Console => {
192 println!(
193 "[{}] {} - {}",
194 alert.severity.name(),
195 alert.title,
196 alert.message
197 );
198 DeliveryResult::success("console", 0)
199 }
200 AlertChannel::Slack { webhook_url: _ } => {
201 DeliveryResult::success("slack", 50)
204 }
205 AlertChannel::PagerDuty { routing_key: _ } => DeliveryResult::success("pagerduty", 100),
206 AlertChannel::Email { .. } => DeliveryResult::success("email", 200),
207 AlertChannel::Webhook { url: _, method: _ } => DeliveryResult::success("webhook", 30),
208 }
209 }
210
211 pub fn send(&mut self, alert: Alert) -> Vec<DeliveryResult> {
213 if self.deduplicator.is_duplicate(&alert.id) {
215 return vec![DeliveryResult::failure("all", "duplicate alert")];
216 }
217
218 if !self.rate_limiter.should_allow(&alert.source) {
220 return vec![DeliveryResult::failure("all", "rate limited")];
221 }
222
223 let channels = self.get_channels(alert.severity);
225 let results: Vec<DeliveryResult> = channels
226 .iter()
227 .map(|ch| self.send_to_channel(&alert, ch))
228 .collect();
229
230 self.delivery_results.extend(results.clone());
232
233 self.history.push(alert);
235 while self.history.len() > self.max_history {
236 self.history.remove(0);
237 }
238
239 results
240 }
241
242 pub fn history(&self) -> &[Alert] {
244 &self.history
245 }
246
247 pub fn delivery_results(&self) -> &[DeliveryResult] {
249 &self.delivery_results
250 }
251
252 pub fn clear_history(&mut self) {
254 self.history.clear();
255 self.delivery_results.clear();
256 }
257
258 pub fn alert_count(&self) -> usize {
260 self.history.len()
261 }
262}