use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, zerompk::ToMessagePack, zerompk::FromMessagePack)]
pub struct AlertDef {
pub tenant_id: u64,
pub name: String,
pub collection: String,
pub where_filter: Option<String>,
pub condition: AlertCondition,
pub group_by: Vec<String>,
pub window_ms: u64,
pub fire_after: u32,
pub recover_after: u32,
pub severity: String,
pub notify_targets: Vec<NotifyTarget>,
pub enabled: bool,
pub owner: String,
pub created_at: u64,
}
#[derive(Debug, Clone, zerompk::ToMessagePack, zerompk::FromMessagePack)]
pub struct AlertCondition {
pub agg_func: String,
pub column: String,
pub op: CompareOp,
pub threshold: f64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, zerompk::ToMessagePack, zerompk::FromMessagePack)]
#[repr(u8)]
#[msgpack(c_enum)]
pub enum CompareOp {
Gt = 0,
Gte = 1,
Lt = 2,
Lte = 3,
Eq = 4,
Neq = 5,
}
impl CompareOp {
pub fn parse(s: &str) -> Option<Self> {
match s.trim() {
">" => Some(Self::Gt),
">=" => Some(Self::Gte),
"<" => Some(Self::Lt),
"<=" => Some(Self::Lte),
"=" | "==" => Some(Self::Eq),
"!=" | "<>" => Some(Self::Neq),
_ => None,
}
}
pub fn evaluate(&self, value: f64, threshold: f64) -> bool {
match self {
Self::Gt => value > threshold,
Self::Gte => value >= threshold,
Self::Lt => value < threshold,
Self::Lte => value <= threshold,
Self::Eq => (value - threshold).abs() < f64::EPSILON,
Self::Neq => (value - threshold).abs() >= f64::EPSILON,
}
}
pub fn as_sql(&self) -> &'static str {
match self {
Self::Gt => ">",
Self::Gte => ">=",
Self::Lt => "<",
Self::Lte => "<=",
Self::Eq => "=",
Self::Neq => "!=",
}
}
}
#[derive(Debug, Clone, zerompk::ToMessagePack, zerompk::FromMessagePack)]
pub enum NotifyTarget {
Topic { name: String },
Webhook { url: String },
InsertInto { table: String, columns: Vec<String> },
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
pub enum AlertStatus {
#[default]
Cleared,
Active,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AlertEvent {
pub alert_name: String,
pub group_key: String,
pub severity: String,
pub status: String,
pub value: f64,
pub threshold: f64,
pub timestamp_ms: u64,
pub collection: String,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn compare_op_evaluate() {
assert!(CompareOp::Gt.evaluate(91.0, 90.0));
assert!(!CompareOp::Gt.evaluate(90.0, 90.0));
assert!(CompareOp::Gte.evaluate(90.0, 90.0));
assert!(CompareOp::Lt.evaluate(89.0, 90.0));
assert!(CompareOp::Lte.evaluate(90.0, 90.0));
assert!(CompareOp::Eq.evaluate(90.0, 90.0));
assert!(CompareOp::Neq.evaluate(91.0, 90.0));
}
#[test]
fn compare_op_parse() {
assert_eq!(CompareOp::parse(">"), Some(CompareOp::Gt));
assert_eq!(CompareOp::parse(">="), Some(CompareOp::Gte));
assert_eq!(CompareOp::parse("<"), Some(CompareOp::Lt));
assert_eq!(CompareOp::parse("<="), Some(CompareOp::Lte));
assert_eq!(CompareOp::parse("="), Some(CompareOp::Eq));
assert_eq!(CompareOp::parse("!="), Some(CompareOp::Neq));
assert_eq!(CompareOp::parse("<>"), Some(CompareOp::Neq));
assert_eq!(CompareOp::parse("LIKE"), None);
}
}