Skip to content

Commit 6d752e7

Browse files
authored
fix: Convert Go regex to Rust regex (#76)
Convert Go regex to Rust regex Go and Rust have some differences in their regex implementation. In Go the following is valid: ``` uri=~"/v[1-9]/.*/{gid}/{uid}" ``` Rust sees the `{gid}` as a bad repetition, it expects a range like {1,4} ``` regex parse error: /v[1-9]/.*/{gid}/{uid} ^ error: repetition quantifier expects a valid decimal ``` For it to be a valid Rust regex the opening { has to be escaped. This change adds that escaping. The implementation is simple, iteration + writing in a result buffer. It is probably not the best of nicest way of doing it, but it straight forward
1 parent 6cf0736 commit 6d752e7

File tree

2 files changed

+100
-0
lines changed

2 files changed

+100
-0
lines changed

src/label/matcher.rs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,15 +91,80 @@ impl Matcher {
9191
}
9292
}
9393

94+
// Go and Rust handle the repeat pattern differently
95+
// in Go the following is valid: `aaa{bbb}ccc`
96+
// in Rust {bbb} is seen as an invalid repeat and must be ecaped \{bbb}
97+
// This escapes the opening { if its not followed by valid repeat pattern (e.g. 4,6).
98+
fn convert_re(re: &str) -> String {
99+
// (true, string) if its a valid repeat pattern (e.g. 1,2 or 2,)
100+
fn is_repeat(chars: &mut std::str::Chars<'_>) -> (bool, String) {
101+
let mut buf = String::new();
102+
let mut comma = false;
103+
for c in chars.by_ref() {
104+
buf.push(c);
105+
106+
if c == ',' {
107+
// two commas or {, are both invalid
108+
if comma || buf == "," {
109+
return (false, buf);
110+
} else {
111+
comma = true;
112+
}
113+
} else if c.is_ascii_digit() {
114+
continue;
115+
} else if c == '}' {
116+
if buf == "}" {
117+
return (false, buf);
118+
} else {
119+
return (true, buf);
120+
}
121+
} else {
122+
return (false, buf);
123+
}
124+
}
125+
(false, buf)
126+
}
127+
128+
let mut result = String::new();
129+
let mut chars = re.chars();
130+
131+
while let Some(c) = chars.next() {
132+
if c != '{' {
133+
result.push(c);
134+
}
135+
136+
// if escaping, just push the next char as well
137+
if c == '\\' {
138+
if let Some(c) = chars.next() {
139+
result.push(c);
140+
}
141+
} else if c == '{' {
142+
match is_repeat(&mut chars) {
143+
(true, s) => {
144+
result.push('{');
145+
result.push_str(&s);
146+
}
147+
(false, s) => {
148+
result.push_str(r"\{");
149+
result.push_str(&s);
150+
}
151+
}
152+
}
153+
}
154+
result
155+
}
156+
94157
pub fn new_matcher(id: TokenId, name: String, value: String) -> Result<Matcher, String> {
95158
let op = match id {
96159
T_EQL => Ok(MatchOp::Equal),
97160
T_NEQ => Ok(MatchOp::NotEqual),
98161
T_EQL_REGEX => {
162+
let value = Matcher::convert_re(&value);
99163
let re = Regex::new(&value).map_err(|_| format!("illegal regex for {}", &value))?;
100164
Ok(MatchOp::Re(re))
101165
}
102166
T_NEQ_REGEX => {
167+
let value = Matcher::convert_re(&value);
103168
let re = Regex::new(&value).map_err(|_| format!("illegal regex for {}", &value))?;
104169
Ok(MatchOp::NotRe(re))
105170
}
@@ -465,4 +530,23 @@ mod tests {
465530
let ms = matchers.find_matchers("foo");
466531
assert_eq!(4, ms.len());
467532
}
533+
534+
#[test]
535+
fn test_convert_re() {
536+
let convert = |s: &str| Matcher::convert_re(s);
537+
assert_eq!(convert("abc{}"), r#"abc\{}"#);
538+
assert_eq!(convert("abc{def}"), r#"abc\{def}"#);
539+
assert_eq!(convert("abc{def"), r#"abc\{def"#);
540+
assert_eq!(convert("abc{1}"), "abc{1}");
541+
assert_eq!(convert("abc{1,}"), "abc{1,}");
542+
assert_eq!(convert("abc{1,2}"), "abc{1,2}");
543+
assert_eq!(convert("abc{,2}"), r#"abc\{,2}"#);
544+
assert_eq!(convert("abc{{1,2}}"), r#"abc\{{1,2}}"#);
545+
assert_eq!(convert(r#"abc\{abc"#), r#"abc\{abc"#);
546+
assert_eq!(convert("abc{1a}"), r#"abc\{1a}"#);
547+
assert_eq!(convert("abc{1,a}"), r#"abc\{1,a}"#);
548+
assert_eq!(convert("abc{1,2a}"), r#"abc\{1,2a}"#);
549+
assert_eq!(convert("abc{1,2,3}"), r#"abc\{1,2,3}"#);
550+
assert_eq!(convert("abc{1,,2}"), r#"abc\{1,,2}"#);
551+
}
468552
}

src/parser/parse.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -896,6 +896,22 @@ mod tests {
896896
]);
897897
Expr::new_vector_selector(None, matchers)
898898
}),
899+
(r#"foo:bar{a=~"bc{9}"}"#, {
900+
let matchers = Matchers::one(Matcher::new(
901+
MatchOp::Re(Regex::new("bc{9}").unwrap()),
902+
"a",
903+
"bc{9}",
904+
));
905+
Expr::new_vector_selector(Some(String::from("foo:bar")), matchers)
906+
}),
907+
(r#"foo:bar{a=~"bc{abc}"}"#, {
908+
let matchers = Matchers::one(Matcher::new(
909+
MatchOp::Re(Regex::new("bc\\{abc}").unwrap()),
910+
"a",
911+
"bc{abc}",
912+
));
913+
Expr::new_vector_selector(Some(String::from("foo:bar")), matchers)
914+
}),
899915
];
900916
assert_cases(Case::new_result_cases(cases));
901917

0 commit comments

Comments
 (0)