Skip to main content

tl_stream/
alert.rs

1// ThinkingLanguage — Alerting for pipeline failures/successes
2
3use std::fmt;
4
5/// Target for sending alerts.
6#[derive(Debug, Clone)]
7pub enum AlertTarget {
8    /// Slack webhook URL
9    Slack(String),
10    /// Generic webhook URL
11    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
23/// Send an alert message to the specified target.
24pub 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    // Note: actual alert sending requires external services, tested via integration tests
85}