Skip to main content

shape_runtime/alerts/
types.rs

1//! Alert Types
2//!
3//! Core types for the alert system.
4
5use chrono::{DateTime, Utc};
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8use uuid::Uuid;
9
10/// Alert severity levels
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
12#[serde(rename_all = "lowercase")]
13#[derive(Default)]
14pub enum AlertSeverity {
15    /// Debug-level alert (lowest priority)
16    Debug,
17    /// Informational alert
18    #[default]
19    Info,
20    /// Warning alert
21    Warning,
22    /// Error alert
23    Error,
24    /// Critical alert (highest priority)
25    Critical,
26}
27
28impl std::fmt::Display for AlertSeverity {
29    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30        match self {
31            AlertSeverity::Debug => write!(f, "DEBUG"),
32            AlertSeverity::Info => write!(f, "INFO"),
33            AlertSeverity::Warning => write!(f, "WARNING"),
34            AlertSeverity::Error => write!(f, "ERROR"),
35            AlertSeverity::Critical => write!(f, "CRITICAL"),
36        }
37    }
38}
39
40impl AlertSeverity {
41    /// Parse severity from string
42    pub fn from_str(s: &str) -> Self {
43        match s.to_lowercase().as_str() {
44            "debug" => AlertSeverity::Debug,
45            "info" => AlertSeverity::Info,
46            "warning" | "warn" => AlertSeverity::Warning,
47            "error" | "err" => AlertSeverity::Error,
48            "critical" | "crit" => AlertSeverity::Critical,
49            _ => AlertSeverity::Info,
50        }
51    }
52
53    /// Get numeric priority (higher = more severe)
54    pub fn priority(&self) -> u8 {
55        match self {
56            AlertSeverity::Debug => 0,
57            AlertSeverity::Info => 1,
58            AlertSeverity::Warning => 2,
59            AlertSeverity::Error => 3,
60            AlertSeverity::Critical => 4,
61        }
62    }
63}
64
65/// An alert to be sent through the pipeline
66#[derive(Debug, Clone, Serialize, Deserialize)]
67pub struct Alert {
68    /// Unique identifier for this alert
69    pub id: Uuid,
70
71    /// Alert severity level
72    pub severity: AlertSeverity,
73
74    /// Alert title/subject (brief description)
75    pub title: String,
76
77    /// Detailed message
78    pub message: String,
79
80    /// Structured data associated with the alert
81    #[serde(default)]
82    pub data: HashMap<String, serde_json::Value>,
83
84    /// Tags for routing (e.g., ["price", "btc", "urgent"])
85    #[serde(default)]
86    pub tags: Vec<String>,
87
88    /// Timestamp when the alert was created
89    pub timestamp: DateTime<Utc>,
90
91    /// Source of the alert (e.g., script name, strategy name)
92    #[serde(default)]
93    pub source: Option<String>,
94}
95
96impl Alert {
97    /// Create a new alert with the given title and message
98    pub fn new(title: impl Into<String>, message: impl Into<String>) -> Self {
99        Self {
100            id: Uuid::new_v4(),
101            severity: AlertSeverity::Info,
102            title: title.into(),
103            message: message.into(),
104            data: HashMap::new(),
105            tags: Vec::new(),
106            timestamp: Utc::now(),
107            source: None,
108        }
109    }
110
111    /// Set the severity level
112    pub fn with_severity(mut self, severity: AlertSeverity) -> Self {
113        self.severity = severity;
114        self
115    }
116
117    /// Add a tag
118    pub fn with_tag(mut self, tag: impl Into<String>) -> Self {
119        self.tags.push(tag.into());
120        self
121    }
122
123    /// Add multiple tags
124    pub fn with_tags(mut self, tags: impl IntoIterator<Item = impl Into<String>>) -> Self {
125        self.tags.extend(tags.into_iter().map(|t| t.into()));
126        self
127    }
128
129    /// Add structured data
130    pub fn with_data(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
131        self.data.insert(key.into(), value);
132        self
133    }
134
135    /// Set the source
136    pub fn with_source(mut self, source: impl Into<String>) -> Self {
137        self.source = Some(source.into());
138        self
139    }
140
141    /// Check if alert has a specific tag
142    pub fn has_tag(&self, tag: &str) -> bool {
143        self.tags.iter().any(|t| t == tag)
144    }
145
146    /// Check if alert has any of the specified tags
147    pub fn has_any_tag(&self, tags: &[String]) -> bool {
148        tags.iter().any(|t| self.has_tag(t))
149    }
150}
151
152impl Default for Alert {
153    fn default() -> Self {
154        Self::new("", "")
155    }
156}
157
158#[cfg(test)]
159mod tests {
160    use super::*;
161
162    #[test]
163    fn test_alert_creation() {
164        let alert = Alert::new("Test Alert", "This is a test message")
165            .with_severity(AlertSeverity::Warning)
166            .with_tag("test")
167            .with_data("key", serde_json::json!("value"));
168
169        assert_eq!(alert.title, "Test Alert");
170        assert_eq!(alert.message, "This is a test message");
171        assert_eq!(alert.severity, AlertSeverity::Warning);
172        assert!(alert.has_tag("test"));
173        assert_eq!(alert.data.get("key"), Some(&serde_json::json!("value")));
174    }
175
176    #[test]
177    fn test_severity_priority() {
178        assert!(AlertSeverity::Critical.priority() > AlertSeverity::Error.priority());
179        assert!(AlertSeverity::Error.priority() > AlertSeverity::Warning.priority());
180        assert!(AlertSeverity::Warning.priority() > AlertSeverity::Info.priority());
181        assert!(AlertSeverity::Info.priority() > AlertSeverity::Debug.priority());
182    }
183
184    #[test]
185    fn test_severity_from_str() {
186        assert_eq!(AlertSeverity::from_str("debug"), AlertSeverity::Debug);
187        assert_eq!(AlertSeverity::from_str("WARNING"), AlertSeverity::Warning);
188        assert_eq!(AlertSeverity::from_str("crit"), AlertSeverity::Critical);
189        assert_eq!(AlertSeverity::from_str("unknown"), AlertSeverity::Info);
190    }
191}