Skip to content

Commit 6185c6b

Browse files
committed
Add composite alerts
1 parent 30e7f96 commit 6185c6b

File tree

6 files changed

+364
-3
lines changed

6 files changed

+364
-3
lines changed

Cargo.lock

Lines changed: 17 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

server/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ ulid = { version = "1.0", features = ["serde"] }
8787
uptime_lib = "0.2.2"
8888
xxhash-rust = { version = "0.8", features = ["xxh3"] }
8989
xz2 = { version = "*", features = ["static"] }
90+
nom = "7.1.3"
9091

9192

9293
[build-dependencies]

server/src/alerts/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ use regex::Regex;
2626
use serde::{Deserialize, Serialize};
2727
use std::fmt;
2828

29+
pub mod parser;
2930
pub mod rule;
3031
pub mod target;
3132

server/src/alerts/parser.rs

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
use std::str::FromStr;
2+
3+
use nom::{
4+
branch::alt,
5+
bytes::complete::{tag, take_until, take_while1},
6+
character::complete::{char, multispace0, multispace1},
7+
combinator::map,
8+
sequence::{delimited, separated_pair},
9+
IResult,
10+
};
11+
12+
use super::rule::{
13+
base::{
14+
ops::{NumericOperator, StringOperator},
15+
NumericRule, StringRule,
16+
},
17+
CompositeRule,
18+
};
19+
20+
fn parse_numeric_op(input: &str) -> IResult<&str, NumericOperator> {
21+
alt((
22+
map(tag("<"), |_| NumericOperator::LessThan),
23+
map(tag(">"), |_| NumericOperator::GreaterThan),
24+
map(tag("<="), |_| NumericOperator::LessThanEquals),
25+
map(tag(">="), |_| NumericOperator::GreaterThanEquals),
26+
map(tag("="), |_| NumericOperator::EqualTo),
27+
map(tag("!="), |_| NumericOperator::NotEqualTo),
28+
))(input)
29+
}
30+
31+
fn parse_string_op(input: &str) -> IResult<&str, StringOperator> {
32+
alt((
33+
map(tag("="), |_| StringOperator::Exact),
34+
map(tag("!="), |_| StringOperator::NotExact),
35+
map(tag("%"), |_| StringOperator::Contains),
36+
map(tag("!%"), |_| StringOperator::NotContains),
37+
map(tag("*="), |_| StringOperator::Regex),
38+
))(input)
39+
}
40+
41+
fn parse_numeric_rule(input: &str) -> IResult<&str, CompositeRule> {
42+
let (remaining, key) = map(parse_identifier, |s: &str| s.to_string())(input)?;
43+
let (remaining, op) = delimited(multispace0, parse_numeric_op, multispace0)(remaining)?;
44+
let (remaining, value) = map(take_while1(|c: char| c.is_ascii_digit()), |x| {
45+
str::parse(x).unwrap()
46+
})(remaining)?;
47+
48+
Ok((
49+
remaining,
50+
CompositeRule::Numeric(NumericRule {
51+
column: key,
52+
operator: op,
53+
value,
54+
}),
55+
))
56+
}
57+
58+
fn parse_string_rule(input: &str) -> IResult<&str, CompositeRule> {
59+
let (remaining, key) = map(parse_identifier, |s: &str| s.to_string())(input)?;
60+
let (remaining, op) = delimited(multispace0, parse_string_op, multispace0)(remaining)?;
61+
let (remaining, value) = map(
62+
delimited(char('"'), take_until("\""), char('"')),
63+
|x: &str| x.to_string(),
64+
)(remaining)?;
65+
66+
Ok((
67+
remaining,
68+
CompositeRule::String(StringRule {
69+
column: key,
70+
operator: op,
71+
value,
72+
ignore_case: None,
73+
}),
74+
))
75+
}
76+
77+
fn parse_identifier(input: &str) -> IResult<&str, &str> {
78+
take_while1(|c: char| c.is_alphanumeric())(input)
79+
}
80+
81+
fn parse_unary_expr(input: &str) -> IResult<&str, CompositeRule> {
82+
map(delimited(tag("!("), parse_expression, char(')')), |x| {
83+
CompositeRule::Not(Box::new(x))
84+
})(input)
85+
}
86+
87+
fn parse_bracket_expr(input: &str) -> IResult<&str, CompositeRule> {
88+
delimited(
89+
char('('),
90+
delimited(multispace0, parse_expression, multispace0),
91+
char(')'),
92+
)(input)
93+
}
94+
95+
fn parse_and(input: &str) -> IResult<&str, CompositeRule> {
96+
let (remaining, (lhs, rhs)) = separated_pair(
97+
parse_atom,
98+
delimited(multispace1, tag("and"), multispace1),
99+
parse_term,
100+
)(input)?;
101+
102+
Ok((remaining, CompositeRule::And(vec![lhs, rhs])))
103+
}
104+
105+
fn parse_or(input: &str) -> IResult<&str, CompositeRule> {
106+
let (remaining, (lhs, rhs)) = separated_pair(
107+
parse_term,
108+
delimited(multispace1, tag("or"), multispace1),
109+
parse_expression,
110+
)(input)?;
111+
112+
Ok((remaining, CompositeRule::Or(vec![lhs, rhs])))
113+
}
114+
115+
fn parse_expression(input: &str) -> IResult<&str, CompositeRule> {
116+
alt((parse_or, parse_term))(input)
117+
}
118+
fn parse_term(input: &str) -> IResult<&str, CompositeRule> {
119+
alt((parse_and, parse_atom))(input)
120+
}
121+
fn parse_atom(input: &str) -> IResult<&str, CompositeRule> {
122+
alt((
123+
alt((parse_numeric_rule, parse_string_rule)),
124+
parse_unary_expr,
125+
parse_bracket_expr,
126+
))(input)
127+
}
128+
129+
impl FromStr for CompositeRule {
130+
type Err = Box<dyn std::error::Error>;
131+
132+
fn from_str(s: &str) -> Result<Self, Self::Err> {
133+
parse_expression(s)
134+
.map(|(_, x)| x)
135+
.map_err(|x| x.to_string().into())
136+
}
137+
}
138+
139+
#[cfg(test)]
140+
mod tests {
141+
use std::str::FromStr;
142+
143+
use crate::alerts::rule::{
144+
base::{
145+
ops::{NumericOperator, StringOperator},
146+
NumericRule, StringRule,
147+
},
148+
CompositeRule,
149+
};
150+
151+
#[test]
152+
fn test_and_or_not() {
153+
let input = r#"key=500 and key="value" or !(key=300)"#;
154+
let rule = CompositeRule::from_str(input).unwrap();
155+
156+
let numeric1 = NumericRule {
157+
column: "key".to_string(),
158+
operator: NumericOperator::EqualTo,
159+
value: serde_json::Number::from(500),
160+
};
161+
162+
let string1 = StringRule {
163+
column: "key".to_string(),
164+
operator: StringOperator::Exact,
165+
value: "value".to_string(),
166+
ignore_case: None,
167+
};
168+
169+
let numeric3 = NumericRule {
170+
column: "key".to_string(),
171+
operator: NumericOperator::EqualTo,
172+
value: serde_json::Number::from(300),
173+
};
174+
175+
assert_eq!(
176+
rule,
177+
CompositeRule::Or(vec![
178+
CompositeRule::And(vec![
179+
CompositeRule::Numeric(numeric1),
180+
CompositeRule::String(string1)
181+
]),
182+
CompositeRule::Not(Box::new(CompositeRule::Numeric(numeric3)))
183+
])
184+
)
185+
}
186+
}

0 commit comments

Comments
 (0)