diff --git a/server/src/alerts/rule.rs b/server/src/alerts/rule.rs index 7128bcb65..ddc522cd0 100644 --- a/server/src/alerts/rule.rs +++ b/server/src/alerts/rule.rs @@ -23,12 +23,14 @@ use std::sync::atomic::{AtomicU32, Ordering}; #[serde(untagged)] pub enum Rule { Numeric(NumericRule), + String(StringRule), } impl Rule { pub(super) fn resolves(&self, event: &serde_json::Value) -> bool { match self { Rule::Numeric(rule) => rule.resolves(event), + Rule::String(rule) => rule.resolves(event), } } @@ -51,6 +53,10 @@ impl Rule { ), None => false, }, + Rule::String(StringRule { column, .. }) => match schema.column_with_name(column) { + Some((_, column)) => matches!(column.data_type(), arrow_schema::DataType::Utf8), + None => false, + }, } } @@ -88,6 +94,19 @@ impl Rule { column, value, repeats ), }, + Rule::String(StringRule { + column, + operator, + value, + .. + }) => match operator { + StringOperator::Exact => format!("{} column value is {}", column, value), + StringOperator::NotExact => format!("{} column value is not {}", column, value), + StringOperator::Contains => format!("{} column contains {}", column, value), + StringOperator::NotContains => { + format!("{} column does not contains {}", column, value) + } + }, } } } @@ -149,6 +168,44 @@ impl NumericRule { } } +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct StringRule { + pub column: String, + #[serde(default)] + pub operator: StringOperator, + pub ignore_case: Option, + pub value: String, +} + +impl StringRule { + pub fn resolves(&self, event: &serde_json::Value) -> bool { + let string = match event.get(&self.column).expect("column exists") { + serde_json::Value::String(s) => s, + _ => unreachable!("right rule is set for right column type"), + }; + + if self.ignore_case.unwrap_or_default() { + match self.operator { + StringOperator::Exact => string.eq_ignore_ascii_case(&self.value), + StringOperator::NotExact => !string.eq_ignore_ascii_case(&self.value), + StringOperator::Contains => string + .to_ascii_lowercase() + .contains(&self.value.to_ascii_lowercase()), + StringOperator::NotContains => !string + .to_ascii_lowercase() + .contains(&self.value.to_ascii_lowercase()), + } + } else { + match self.operator { + StringOperator::Exact => string.eq(&self.value), + StringOperator::NotExact => !string.eq(&self.value), + StringOperator::Contains => string.contains(&self.value), + StringOperator::NotContains => !string.contains(&self.value), + } + } + } +} // Operator for comparing values #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -173,3 +230,20 @@ impl Default for NumericOperator { Self::EqualTo } } + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum StringOperator { + #[serde(alias = "=")] + Exact, + #[serde(alias = "!=")] + NotExact, + Contains, + NotContains, +} + +impl Default for StringOperator { + fn default() -> Self { + Self::Contains + } +} diff --git a/server/src/validator.rs b/server/src/validator.rs index 341cf99fb..6f0088f76 100644 --- a/server/src/validator.rs +++ b/server/src/validator.rs @@ -51,6 +51,11 @@ pub fn alert(alerts: &Alerts) -> Result<(), AlertValidationError> { return Err(AlertValidationError::InvalidRuleRepeat); } } + Rule::String(ref rule) => { + if rule.column.is_empty() { + return Err(AlertValidationError::EmptyRuleField); + } + } } } Ok(())