Skip to content

Commit 38485a9

Browse files
authored
Add StringRule for alerts (#186)
Add a rule for string matching which supports following matching strategies. Note that all of these work for both case sensitive and insensitive case 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
1 parent ebb7a5a commit 38485a9

File tree

2 files changed

+79
-0
lines changed

2 files changed

+79
-0
lines changed

server/src/alerts/rule.rs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,14 @@ use std::sync::atomic::{AtomicU32, Ordering};
2323
#[serde(untagged)]
2424
pub enum Rule {
2525
Numeric(NumericRule),
26+
String(StringRule),
2627
}
2728

2829
impl Rule {
2930
pub(super) fn resolves(&self, event: &serde_json::Value) -> bool {
3031
match self {
3132
Rule::Numeric(rule) => rule.resolves(event),
33+
Rule::String(rule) => rule.resolves(event),
3234
}
3335
}
3436

@@ -51,6 +53,10 @@ impl Rule {
5153
),
5254
None => false,
5355
},
56+
Rule::String(StringRule { column, .. }) => match schema.column_with_name(column) {
57+
Some((_, column)) => matches!(column.data_type(), arrow_schema::DataType::Utf8),
58+
None => false,
59+
},
5460
}
5561
}
5662

@@ -88,6 +94,19 @@ impl Rule {
8894
column, value, repeats
8995
),
9096
},
97+
Rule::String(StringRule {
98+
column,
99+
operator,
100+
value,
101+
..
102+
}) => match operator {
103+
StringOperator::Exact => format!("{} column value is {}", column, value),
104+
StringOperator::NotExact => format!("{} column value is not {}", column, value),
105+
StringOperator::Contains => format!("{} column contains {}", column, value),
106+
StringOperator::NotContains => {
107+
format!("{} column does not contains {}", column, value)
108+
}
109+
},
91110
}
92111
}
93112
}
@@ -149,6 +168,44 @@ impl NumericRule {
149168
}
150169
}
151170

171+
#[derive(Debug, Serialize, Deserialize)]
172+
#[serde(rename_all = "camelCase")]
173+
pub struct StringRule {
174+
pub column: String,
175+
#[serde(default)]
176+
pub operator: StringOperator,
177+
pub ignore_case: Option<bool>,
178+
pub value: String,
179+
}
180+
181+
impl StringRule {
182+
pub fn resolves(&self, event: &serde_json::Value) -> bool {
183+
let string = match event.get(&self.column).expect("column exists") {
184+
serde_json::Value::String(s) => s,
185+
_ => unreachable!("right rule is set for right column type"),
186+
};
187+
188+
if self.ignore_case.unwrap_or_default() {
189+
match self.operator {
190+
StringOperator::Exact => string.eq_ignore_ascii_case(&self.value),
191+
StringOperator::NotExact => !string.eq_ignore_ascii_case(&self.value),
192+
StringOperator::Contains => string
193+
.to_ascii_lowercase()
194+
.contains(&self.value.to_ascii_lowercase()),
195+
StringOperator::NotContains => !string
196+
.to_ascii_lowercase()
197+
.contains(&self.value.to_ascii_lowercase()),
198+
}
199+
} else {
200+
match self.operator {
201+
StringOperator::Exact => string.eq(&self.value),
202+
StringOperator::NotExact => !string.eq(&self.value),
203+
StringOperator::Contains => string.contains(&self.value),
204+
StringOperator::NotContains => !string.contains(&self.value),
205+
}
206+
}
207+
}
208+
}
152209
// Operator for comparing values
153210

154211
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
@@ -173,3 +230,20 @@ impl Default for NumericOperator {
173230
Self::EqualTo
174231
}
175232
}
233+
234+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
235+
#[serde(rename_all = "camelCase")]
236+
pub enum StringOperator {
237+
#[serde(alias = "=")]
238+
Exact,
239+
#[serde(alias = "!=")]
240+
NotExact,
241+
Contains,
242+
NotContains,
243+
}
244+
245+
impl Default for StringOperator {
246+
fn default() -> Self {
247+
Self::Contains
248+
}
249+
}

server/src/validator.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ pub fn alert(alerts: &Alerts) -> Result<(), AlertValidationError> {
5151
return Err(AlertValidationError::InvalidRuleRepeat);
5252
}
5353
}
54+
Rule::String(ref rule) => {
55+
if rule.column.is_empty() {
56+
return Err(AlertValidationError::EmptyRuleField);
57+
}
58+
}
5459
}
5560
}
5661
Ok(())

0 commit comments

Comments
 (0)