Skip to content

Commit dd7cd21

Browse files
trueleonitisht
andauthored
Parse escape sequence for string and display errors (#475)
Adds ability to escape ", \n, ..etc when defining string rules. Use existing verbose error type from nom to display error. The error type usually will contains stack trace for parser and their subparser. To customize error any further, this will require a custom nom error type. Error Example ``` failed to set alert configuration for log stream app due to err: 0: at line 1, in IsNot: (verb =% "\list" or verb =% "get") and (objectRef_resource = "secrets" and user_username =% "admin") ^ 1: at line 1, in Alt: (verb =% "\list" or verb =% "get") and (objectRef_resource = "secrets" and user_username =% "admin") ^ ``` ``` failed to set alert configuration for log stream app due to err: 0: at line 1: (verb =% "list" or) and (objectRef_resource = "secrets" and user_username =% "admin") ^ expected ')', found o ``` Co-authored-by: Nitish Tiwari <[email protected]>
1 parent 873f843 commit dd7cd21

File tree

2 files changed

+107
-36
lines changed

2 files changed

+107
-36
lines changed

server/src/alerts/parser.rs

Lines changed: 105 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,16 @@
1616
*
1717
*/
1818

19-
use std::str::FromStr;
19+
use std::{borrow::Cow, str::FromStr};
2020

2121
use nom::{
2222
branch::alt,
23-
bytes::complete::{tag, take_until, take_while1},
23+
bytes::complete::{is_not, tag, take_while1},
2424
character::complete::{char, multispace0, multispace1},
25-
combinator::map,
26-
sequence::{delimited, separated_pair},
27-
IResult,
25+
combinator::{cut, map, value},
26+
error::{convert_error, VerboseError},
27+
sequence::{delimited, preceded, separated_pair},
28+
IResult as NomIResult, Parser,
2829
};
2930

3031
use super::rule::{
@@ -35,7 +36,69 @@ use super::rule::{
3536
CompositeRule,
3637
};
3738

38-
fn parse_numeric_op(input: &str) -> IResult<&str, NumericOperator> {
39+
type IResult<'a, O> = NomIResult<&'a str, O, VerboseError<&'a str>>;
40+
41+
enum StrFragment<'a> {
42+
Escaped(char),
43+
Unescaped(&'a str),
44+
}
45+
46+
fn parse_escaped_char(input: &str) -> IResult<char> {
47+
preceded(
48+
char('\\'),
49+
alt((
50+
value('"', char('"')),
51+
value('\\', char('\\')),
52+
value('/', char('/')),
53+
value('\n', char('n')),
54+
value('\r', char('r')),
55+
value('\t', char('t')),
56+
value('\u{08}', char('b')),
57+
value('\u{0C}', char('f')),
58+
)),
59+
)
60+
.parse(input)
61+
}
62+
63+
fn parse_str_char(input: &str) -> IResult<StrFragment> {
64+
alt((
65+
map(parse_escaped_char, StrFragment::Escaped),
66+
map(is_not(r#""\"#), StrFragment::Unescaped),
67+
))
68+
.parse(input)
69+
}
70+
71+
fn parse_string(input: &str) -> IResult<Cow<str>> {
72+
let mut res = Cow::Borrowed("");
73+
let (mut input, _) = char('"').parse(input)?;
74+
75+
loop {
76+
match char('"').parse(input) {
77+
// If it is terminating double quotes then we can return the ok value
78+
Ok((tail, _)) => return Ok((tail, res)),
79+
// Fail to parsing in recoverable variant can mean it is a valid char that is not double quote
80+
Err(nom::Err::Error(_)) => {}
81+
Err(err) => return Err(err),
82+
};
83+
84+
input = match cut(parse_str_char)(input)? {
85+
(tail, StrFragment::Escaped(ch)) => {
86+
res.to_mut().push(ch);
87+
tail
88+
}
89+
(tail, StrFragment::Unescaped(s)) => {
90+
if res.is_empty() {
91+
res = Cow::Borrowed(s)
92+
} else {
93+
res.to_mut().push_str(s)
94+
}
95+
tail
96+
}
97+
};
98+
}
99+
}
100+
101+
fn parse_numeric_op(input: &str) -> IResult<NumericOperator> {
39102
alt((
40103
map(tag("<="), |_| NumericOperator::LessThanEquals),
41104
map(tag(">="), |_| NumericOperator::GreaterThanEquals),
@@ -46,7 +109,7 @@ fn parse_numeric_op(input: &str) -> IResult<&str, NumericOperator> {
46109
))(input)
47110
}
48111

49-
fn parse_string_op(input: &str) -> IResult<&str, StringOperator> {
112+
fn parse_string_op(input: &str) -> IResult<StringOperator> {
50113
alt((
51114
map(tag("!="), |_| StringOperator::NotExact),
52115
map(tag("=%"), |_| StringOperator::Contains),
@@ -56,7 +119,7 @@ fn parse_string_op(input: &str) -> IResult<&str, StringOperator> {
56119
))(input)
57120
}
58121

59-
fn parse_numeric_rule(input: &str) -> IResult<&str, CompositeRule> {
122+
fn parse_numeric_rule(input: &str) -> IResult<CompositeRule> {
60123
let (remaining, key) = map(parse_identifier, |s: &str| s.to_string())(input)?;
61124
let (remaining, op) = delimited(multispace0, parse_numeric_op, multispace0)(remaining)?;
62125
let (remaining, value) = map(take_while1(|c: char| c.is_ascii_digit()), |x| {
@@ -73,84 +136,92 @@ fn parse_numeric_rule(input: &str) -> IResult<&str, CompositeRule> {
73136
))
74137
}
75138

76-
fn parse_string_rule(input: &str) -> IResult<&str, CompositeRule> {
139+
fn parse_string_rule(input: &str) -> IResult<CompositeRule> {
77140
let (remaining, key) = map(parse_identifier, |s: &str| s.to_string())(input)?;
78141
let (remaining, op) = delimited(multispace0, parse_string_op, multispace0)(remaining)?;
79-
let (remaining, value) = map(
80-
delimited(char('"'), take_until("\""), char('"')),
81-
|x: &str| x.to_string(),
82-
)(remaining)?;
142+
let (remaining, value) = parse_string(remaining)?;
83143

84144
Ok((
85145
remaining,
86146
CompositeRule::String(StringRule {
87147
column: key,
88148
operator: op,
89-
value,
149+
value: value.into_owned(),
90150
ignore_case: None,
91151
}),
92152
))
93153
}
94154

95-
fn parse_identifier(input: &str) -> IResult<&str, &str> {
155+
fn parse_identifier(input: &str) -> IResult<&str> {
96156
take_while1(|c: char| c.is_alphanumeric() || c == '-' || c == '_')(input)
97157
}
98158

99-
fn parse_unary_expr(input: &str) -> IResult<&str, CompositeRule> {
100-
map(delimited(tag("!("), parse_expression, char(')')), |x| {
101-
CompositeRule::Not(Box::new(x))
102-
})(input)
159+
fn parse_unary_expr(input: &str) -> IResult<CompositeRule> {
160+
map(
161+
delimited(tag("!("), cut(parse_expression), char(')')),
162+
|x| CompositeRule::Not(Box::new(x)),
163+
)(input)
103164
}
104165

105-
fn parse_bracket_expr(input: &str) -> IResult<&str, CompositeRule> {
166+
fn parse_bracket_expr(input: &str) -> IResult<CompositeRule> {
106167
delimited(
107168
char('('),
108-
delimited(multispace0, parse_expression, multispace0),
109-
char(')'),
169+
delimited(multispace0, cut(parse_expression), multispace0),
170+
cut(char(')')),
110171
)(input)
111172
}
112173

113-
fn parse_and(input: &str) -> IResult<&str, CompositeRule> {
174+
fn parse_and(input: &str) -> IResult<CompositeRule> {
114175
let (remaining, (lhs, rhs)) = separated_pair(
115176
parse_atom,
116177
delimited(multispace1, tag("and"), multispace1),
117-
parse_term,
178+
cut(parse_term),
118179
)(input)?;
119180

120181
Ok((remaining, CompositeRule::And(vec![lhs, rhs])))
121182
}
122183

123-
fn parse_or(input: &str) -> IResult<&str, CompositeRule> {
184+
fn parse_or(input: &str) -> IResult<CompositeRule> {
124185
let (remaining, (lhs, rhs)) = separated_pair(
125186
parse_term,
126187
delimited(multispace1, tag("or"), multispace1),
127-
parse_expression,
188+
cut(parse_expression),
128189
)(input)?;
129190

130191
Ok((remaining, CompositeRule::Or(vec![lhs, rhs])))
131192
}
132193

133-
fn parse_expression(input: &str) -> IResult<&str, CompositeRule> {
194+
fn parse_expression(input: &str) -> IResult<CompositeRule> {
134195
alt((parse_or, parse_term))(input)
135196
}
136-
fn parse_term(input: &str) -> IResult<&str, CompositeRule> {
197+
198+
fn parse_term(input: &str) -> IResult<CompositeRule> {
137199
alt((parse_and, parse_atom))(input)
138200
}
139-
fn parse_atom(input: &str) -> IResult<&str, CompositeRule> {
201+
202+
fn parse_atom(input: &str) -> IResult<CompositeRule> {
140203
alt((
141-
alt((parse_numeric_rule, parse_string_rule)),
204+
alt((parse_string_rule, parse_numeric_rule)),
142205
parse_unary_expr,
143206
parse_bracket_expr,
144207
))(input)
145208
}
146209

147210
impl FromStr for CompositeRule {
148-
type Err = Box<dyn std::error::Error>;
211+
type Err = String;
149212

150213
fn from_str(s: &str) -> Result<Self, Self::Err> {
151-
parse_expression(s)
152-
.map(|(_, x)| x)
153-
.map_err(|x| x.to_string().into())
214+
let s = s.trim();
215+
let (remaining, parsed) = parse_expression(s).map_err(|err| match err {
216+
nom::Err::Incomplete(_) => "Needed more data".to_string(),
217+
nom::Err::Error(err) | nom::Err::Failure(err) => convert_error(s, err),
218+
})?;
219+
220+
if remaining.is_empty() {
221+
Ok(parsed)
222+
} else {
223+
Err(format!("Could not parse input \n{}", remaining))
224+
}
154225
}
155226
}
156227

server/src/alerts/rule.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -379,7 +379,7 @@ impl ConsecutiveRepeatState {
379379

380380
fn string_or_struct<'de, T, D>(deserializer: D) -> Result<T, D::Error>
381381
where
382-
T: Deserialize<'de> + FromStr<Err = Box<dyn std::error::Error>>,
382+
T: Deserialize<'de> + FromStr<Err = String>,
383383
D: Deserializer<'de>,
384384
{
385385
// This is a Visitor that forwards string types to T's `FromStr` impl and
@@ -391,7 +391,7 @@ where
391391

392392
impl<'de, T> Visitor<'de> for StringOrStruct<T>
393393
where
394-
T: Deserialize<'de> + FromStr<Err = Box<dyn std::error::Error>>,
394+
T: Deserialize<'de> + FromStr<Err = String>,
395395
{
396396
type Value = T;
397397

0 commit comments

Comments
 (0)