From 1b12eda19da67ea10d7cbb3189850c797cc6bf51 Mon Sep 17 00:00:00 2001 From: Satyam Singh Date: Wed, 26 Oct 2022 15:14:04 +0530 Subject: [PATCH] Add StringRule for alerts Add a rule for string matching which supports following matching stratergies. note that all of these work for both case sensitive and insensitive case which is set through `ignoreCase`. - Exact Match - Not Exact Match ( opposite of exact ) - Contains - Not Contains Other variants that could be added but were not in this PR. - Regex - Starts with - Ends with --- server/src/alerts/rule.rs | 74 +++++++++++++++++++++++++++++++++++++++ server/src/validator.rs | 5 +++ 2 files changed, 79 insertions(+) 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(())