1use std::fmt;
4
5#[derive(Debug, Clone)]
7pub enum AlertTarget {
8 Slack(String),
10 Webhook(String),
12}
13
14impl fmt::Display for AlertTarget {
15 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
16 match self {
17 AlertTarget::Slack(url) => write!(f, "slack:{url}"),
18 AlertTarget::Webhook(url) => write!(f, "webhook:{url}"),
19 }
20 }
21}
22
23pub fn send_alert(target: &AlertTarget, message: &str) -> Result<(), String> {
25 match target {
26 AlertTarget::Slack(url) => send_slack_alert(url, message),
27 AlertTarget::Webhook(url) => send_webhook_alert(url, message),
28 }
29}
30
31fn send_slack_alert(url: &str, message: &str) -> Result<(), String> {
32 let payload = serde_json::json!({
33 "text": message,
34 });
35
36 let client = reqwest::blocking::Client::new();
37 let resp = client
38 .post(url)
39 .json(&payload)
40 .send()
41 .map_err(|e| format!("Slack alert failed: {e}"))?;
42
43 if resp.status().is_success() {
44 Ok(())
45 } else {
46 Err(format!("Slack alert returned status {}", resp.status()))
47 }
48}
49
50fn send_webhook_alert(url: &str, message: &str) -> Result<(), String> {
51 let payload = serde_json::json!({
52 "alert": true,
53 "message": message,
54 "timestamp": chrono::Utc::now().to_rfc3339(),
55 });
56
57 let client = reqwest::blocking::Client::new();
58 let resp = client
59 .post(url)
60 .json(&payload)
61 .send()
62 .map_err(|e| format!("Webhook alert failed: {e}"))?;
63
64 if resp.status().is_success() {
65 Ok(())
66 } else {
67 Err(format!("Webhook alert returned status {}", resp.status()))
68 }
69}
70
71#[cfg(test)]
72mod tests {
73 use super::*;
74
75 #[test]
76 fn test_alert_target_display() {
77 let slack = AlertTarget::Slack("https://hooks.slack.com/test".to_string());
78 assert_eq!(format!("{slack}"), "slack:https://hooks.slack.com/test");
79
80 let webhook = AlertTarget::Webhook("https://example.com/webhook".to_string());
81 assert_eq!(format!("{webhook}"), "webhook:https://example.com/webhook");
82 }
83
84 }