promql_parser/label/
matcher.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::fmt;
16use std::hash::{Hash, Hasher};
17
18use regex::Regex;
19
20use crate::parser::lex::is_label;
21use crate::parser::token::{token_display, TokenId, T_EQL, T_EQL_REGEX, T_NEQ, T_NEQ_REGEX};
22use crate::util::join_vector;
23
24const LABEL_METRIC_NAME: &str = "__name__";
25
26#[derive(Debug, Clone)]
27pub enum MatchOp {
28    Equal,
29    NotEqual,
30    // TODO: do we need regex here?
31    Re(Regex),
32    NotRe(Regex),
33}
34
35impl fmt::Display for MatchOp {
36    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37        match self {
38            MatchOp::Equal => write!(f, "="),
39            MatchOp::NotEqual => write!(f, "!="),
40            MatchOp::Re(_reg) => write!(f, "=~"),
41            MatchOp::NotRe(_reg) => write!(f, "!~"),
42        }
43    }
44}
45
46impl PartialEq for MatchOp {
47    fn eq(&self, other: &Self) -> bool {
48        match (self, other) {
49            (MatchOp::Equal, MatchOp::Equal) => true,
50            (MatchOp::NotEqual, MatchOp::NotEqual) => true,
51            (MatchOp::Re(s), MatchOp::Re(o)) => s.as_str().eq(o.as_str()),
52            (MatchOp::NotRe(s), MatchOp::NotRe(o)) => s.as_str().eq(o.as_str()),
53            _ => false,
54        }
55    }
56}
57
58impl Eq for MatchOp {}
59
60impl Hash for MatchOp {
61    fn hash<H: Hasher>(&self, state: &mut H) {
62        match self {
63            MatchOp::Equal => "eq".hash(state),
64            MatchOp::NotEqual => "ne".hash(state),
65            MatchOp::Re(s) => format!("re:{}", s.as_str()).hash(state),
66            MatchOp::NotRe(s) => format!("nre:{}", s.as_str()).hash(state),
67        }
68    }
69}
70
71#[cfg(feature = "ser")]
72impl serde::Serialize for MatchOp {
73    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
74    where
75        S: serde::Serializer,
76    {
77        serializer.serialize_str(&self.to_string())
78    }
79}
80
81// Matcher models the matching of a label.
82#[derive(Debug, Clone, PartialEq, Eq, Hash)]
83#[cfg_attr(feature = "ser", derive(serde::Serialize))]
84pub struct Matcher {
85    #[cfg_attr(feature = "ser", serde(rename = "type"))]
86    pub op: MatchOp,
87    pub name: String,
88    pub value: String,
89}
90
91impl Matcher {
92    pub fn new(op: MatchOp, name: &str, value: &str) -> Self {
93        Self {
94            op,
95            name: name.into(),
96            value: value.into(),
97        }
98    }
99
100    /// matches returns whether the matcher matches the given string value.
101    pub fn is_match(&self, s: &str) -> bool {
102        match &self.op {
103            MatchOp::Equal => self.value.eq(s),
104            MatchOp::NotEqual => self.value.ne(s),
105            MatchOp::Re(r) => r.is_match(s),
106            MatchOp::NotRe(r) => !r.is_match(s),
107        }
108    }
109
110    /// Parse and potentially transform the regex.
111    ///
112    /// Go and Rust handle the repeat pattern differently,
113    /// in Go the following is valid: `aaa{bbb}ccc` but
114    /// in Rust {bbb} is seen as an invalid repeat and must be escaped as \{bbb}.
115    /// This escapes the opening { if its not followed by valid repeat pattern (e.g. 4,6).
116    ///
117    /// Regex used in PromQL are fully anchored.
118    fn try_parse_re(original_re: &str) -> Result<Regex, String> {
119        let re = format!("^(?:{})$", original_re);
120        Regex::new(&re)
121            .or_else(|_| Regex::new(&try_escape_for_repeat_re(&re)))
122            .map_err(|_| format!("illegal regex for {original_re}",))
123    }
124
125    pub fn new_matcher(id: TokenId, name: String, value: String) -> Result<Matcher, String> {
126        let op = Self::find_matcher_op(id, &value)?;
127        op.map(|op| Matcher::new(op, name.as_str(), value.as_str()))
128    }
129
130    pub fn new_metric_name_matcher(name: String) -> Result<Matcher, String> {
131        Ok(Matcher::new(
132            MatchOp::Equal,
133            LABEL_METRIC_NAME,
134            name.as_str(),
135        ))
136    }
137
138    fn find_matcher_op(id: TokenId, value: &str) -> Result<Result<MatchOp, String>, String> {
139        let op = match id {
140            T_EQL => Ok(MatchOp::Equal),
141            T_NEQ => Ok(MatchOp::NotEqual),
142            T_EQL_REGEX => Ok(MatchOp::Re(Matcher::try_parse_re(value)?)),
143            T_NEQ_REGEX => Ok(MatchOp::NotRe(Matcher::try_parse_re(value)?)),
144            _ => Err(format!("invalid match op {}", token_display(id))),
145        };
146        Ok(op)
147    }
148}
149
150impl fmt::Display for Matcher {
151    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
152        let name = if is_label(&self.name) {
153            self.name.clone()
154        } else {
155            format!("\"{}\"", self.name)
156        };
157        write!(f, "{}{}\"{}\"", name, self.op, self.value)
158    }
159}
160
161// Go and Rust handle the repeat pattern differently
162// in Go the following is valid: `aaa{bbb}ccc`
163// in Rust {bbb} is seen as an invalid repeat and must be ecaped \{bbb}
164// This escapes the opening { if its not followed by valid repeat pattern (e.g. 4,6).
165fn try_escape_for_repeat_re(re: &str) -> String {
166    fn is_repeat(chars: &mut std::str::Chars<'_>) -> (bool, String) {
167        let mut buf = String::new();
168        let mut comma_seen = false;
169        for c in chars.by_ref() {
170            buf.push(c);
171            match c {
172                ',' if comma_seen => {
173                    return (false, buf); // ,, is invalid
174                }
175                ',' if buf == "," => {
176                    return (false, buf); // {, is invalid
177                }
178                ',' if !comma_seen => comma_seen = true,
179                '}' if buf == "}" => {
180                    return (false, buf); // {} is invalid
181                }
182                '}' => {
183                    return (true, buf);
184                }
185                _ if c.is_ascii_digit() => continue,
186                _ => {
187                    return (false, buf); // false if visit non-digit char
188                }
189            }
190        }
191        (false, buf) // not ended with }
192    }
193
194    let mut result = String::with_capacity(re.len() + 1);
195    let mut chars = re.chars();
196
197    while let Some(c) = chars.next() {
198        match c {
199            '\\' => {
200                if let Some(cc) = chars.next() {
201                    result.push(c);
202                    result.push(cc);
203                }
204            }
205            '{' => {
206                let (is, s) = is_repeat(&mut chars);
207                if !is {
208                    result.push('\\');
209                }
210                result.push(c);
211                result.push_str(&s);
212            }
213            _ => result.push(c),
214        }
215    }
216    result
217}
218
219#[derive(Debug, Clone, PartialEq, Eq)]
220#[cfg_attr(feature = "ser", derive(serde::Serialize))]
221pub struct Matchers {
222    pub matchers: Vec<Matcher>,
223    #[cfg_attr(feature = "ser", serde(skip_serializing_if = "<[_]>::is_empty"))]
224    pub or_matchers: Vec<Vec<Matcher>>,
225}
226
227impl Matchers {
228    pub fn empty() -> Self {
229        Self {
230            matchers: vec![],
231            or_matchers: vec![],
232        }
233    }
234
235    pub fn one(matcher: Matcher) -> Self {
236        let matchers = vec![matcher];
237        Self {
238            matchers,
239            or_matchers: vec![],
240        }
241    }
242
243    pub fn new(matchers: Vec<Matcher>) -> Self {
244        Self {
245            matchers,
246            or_matchers: vec![],
247        }
248    }
249
250    pub fn with_or_matchers(mut self, or_matchers: Vec<Vec<Matcher>>) -> Self {
251        self.or_matchers = or_matchers;
252        self
253    }
254
255    pub fn append(mut self, matcher: Matcher) -> Self {
256        // Check the latest or_matcher group. If it is not empty,
257        // we need to add the current matcher to this group.
258        let last_or_matcher = self.or_matchers.last_mut();
259        if let Some(last_or_matcher) = last_or_matcher {
260            last_or_matcher.push(matcher);
261        } else {
262            self.matchers.push(matcher);
263        }
264        self
265    }
266
267    pub fn append_or(mut self, matcher: Matcher) -> Self {
268        if !self.matchers.is_empty() {
269            // Be careful not to move ownership here, because it
270            // will be used by the subsequent append method.
271            let last_matchers = std::mem::take(&mut self.matchers);
272            self.or_matchers.push(last_matchers);
273        }
274        let new_or_matchers = vec![matcher];
275        self.or_matchers.push(new_or_matchers);
276        self
277    }
278
279    /// Vector selectors must either specify a name or at least one label
280    /// matcher that does not match the empty string.
281    ///
282    /// The following expression is illegal:
283    /// {job=~".*"} # Bad!
284    pub fn is_empty_matchers(&self) -> bool {
285        (self.matchers.is_empty() && self.or_matchers.is_empty())
286            || self
287                .matchers
288                .iter()
289                .chain(self.or_matchers.iter().flatten())
290                .all(|m| m.is_match(""))
291    }
292
293    /// find the matcher's value whose name equals the specified name. This function
294    /// is designed to prepare error message of invalid promql expression.
295    pub(crate) fn find_matcher_value(&self, name: &str) -> Option<String> {
296        self.matchers
297            .iter()
298            .chain(self.or_matchers.iter().flatten())
299            .find(|m| m.name.eq(name))
300            .map(|m| m.value.clone())
301    }
302
303    /// find matchers whose name equals the specified name
304    pub fn find_matchers(&self, name: &str) -> Vec<Matcher> {
305        self.matchers
306            .iter()
307            .chain(self.or_matchers.iter().flatten())
308            .filter(|m| m.name.eq(name))
309            .cloned()
310            .collect()
311    }
312}
313
314impl fmt::Display for Matchers {
315    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
316        let simple_matchers = &self.matchers;
317        let or_matchers = &self.or_matchers;
318        if or_matchers.is_empty() {
319            write!(f, "{}", join_vector(simple_matchers, ",", true))
320        } else {
321            let or_matchers_string =
322                self.or_matchers
323                    .iter()
324                    .fold(String::new(), |or_matchers_str, pair| {
325                        format!("{} or {}", or_matchers_str, join_vector(pair, ", ", false))
326                    });
327            let or_matchers_string = or_matchers_string.trim_start_matches(" or").trim();
328            write!(f, "{or_matchers_string}")
329        }
330    }
331}
332
333#[cfg(test)]
334mod tests {
335    use super::*;
336    use crate::parser::token;
337    use std::collections::hash_map::DefaultHasher;
338
339    fn hash<H>(op: H) -> u64
340    where
341        H: Hash,
342    {
343        let mut hasher = DefaultHasher::new();
344        op.hash(&mut hasher);
345        hasher.finish()
346    }
347
348    #[test]
349    fn test_new_matcher() {
350        assert_eq!(
351            Matcher::new_matcher(token::T_ADD, "".into(), "".into()),
352            Err(format!("invalid match op {}", token_display(token::T_ADD)))
353        )
354    }
355
356    #[test]
357    fn test_matcher_op_eq() {
358        assert_eq!(MatchOp::Equal, MatchOp::Equal);
359        assert_eq!(MatchOp::NotEqual, MatchOp::NotEqual);
360        assert_eq!(
361            MatchOp::Re(Regex::new("\\s+").unwrap()),
362            MatchOp::Re(Regex::new("\\s+").unwrap())
363        );
364        assert_eq!(
365            MatchOp::NotRe(Regex::new("\\s+").unwrap()),
366            MatchOp::NotRe(Regex::new("\\s+").unwrap())
367        );
368
369        assert_ne!(MatchOp::Equal, MatchOp::NotEqual);
370        assert_ne!(
371            MatchOp::NotEqual,
372            MatchOp::NotRe(Regex::new("\\s+").unwrap())
373        );
374        assert_ne!(
375            MatchOp::Re(Regex::new("\\s+").unwrap()),
376            MatchOp::NotRe(Regex::new("\\s+").unwrap())
377        );
378    }
379
380    #[test]
381    fn test_matchop_hash() {
382        assert_eq!(hash(MatchOp::Equal), hash(MatchOp::Equal));
383        assert_eq!(hash(MatchOp::NotEqual), hash(MatchOp::NotEqual));
384        assert_eq!(
385            hash(MatchOp::Re(Regex::new("\\s+").unwrap())),
386            hash(MatchOp::Re(Regex::new("\\s+").unwrap()))
387        );
388        assert_eq!(
389            hash(MatchOp::NotRe(Regex::new("\\s+").unwrap())),
390            hash(MatchOp::NotRe(Regex::new("\\s+").unwrap()))
391        );
392
393        assert_ne!(hash(MatchOp::Equal), hash(MatchOp::NotEqual));
394        assert_ne!(
395            hash(MatchOp::NotEqual),
396            hash(MatchOp::NotRe(Regex::new("\\s+").unwrap()))
397        );
398        assert_ne!(
399            hash(MatchOp::Re(Regex::new("\\s+").unwrap())),
400            hash(MatchOp::NotRe(Regex::new("\\s+").unwrap()))
401        );
402    }
403
404    #[test]
405    fn test_matcher_hash() {
406        assert_eq!(
407            hash(Matcher::new(MatchOp::Equal, "name", "value")),
408            hash(Matcher::new(MatchOp::Equal, "name", "value")),
409        );
410
411        assert_eq!(
412            hash(Matcher::new(MatchOp::NotEqual, "name", "value")),
413            hash(Matcher::new(MatchOp::NotEqual, "name", "value")),
414        );
415
416        assert_eq!(
417            hash(Matcher::new(
418                MatchOp::Re(Regex::new("\\s+").unwrap()),
419                "name",
420                "\\s+"
421            )),
422            hash(Matcher::new(
423                MatchOp::Re(Regex::new("\\s+").unwrap()),
424                "name",
425                "\\s+"
426            )),
427        );
428
429        assert_eq!(
430            hash(Matcher::new(
431                MatchOp::NotRe(Regex::new("\\s+").unwrap()),
432                "name",
433                "\\s+"
434            )),
435            hash(Matcher::new(
436                MatchOp::NotRe(Regex::new("\\s+").unwrap()),
437                "name",
438                "\\s+"
439            )),
440        );
441
442        assert_ne!(
443            hash(Matcher::new(MatchOp::Equal, "name", "value")),
444            hash(Matcher::new(MatchOp::NotEqual, "name", "value")),
445        );
446
447        assert_ne!(
448            hash(Matcher::new(
449                MatchOp::Re(Regex::new("\\s+").unwrap()),
450                "name",
451                "\\s+"
452            )),
453            hash(Matcher::new(
454                MatchOp::NotRe(Regex::new("\\s+").unwrap()),
455                "name",
456                "\\s+"
457            )),
458        );
459    }
460
461    #[test]
462    fn test_matcher_eq_ne() {
463        let op = MatchOp::Equal;
464        let matcher = Matcher::new(op, "name", "up");
465        assert!(matcher.is_match("up"));
466        assert!(!matcher.is_match("down"));
467
468        let op = MatchOp::NotEqual;
469        let matcher = Matcher::new(op, "name", "up");
470        assert!(matcher.is_match("foo"));
471        assert!(matcher.is_match("bar"));
472        assert!(!matcher.is_match("up"));
473    }
474
475    #[test]
476    fn test_matcher_re() {
477        let value = "api/v1/.*";
478        let re = Regex::new(value).unwrap();
479        let op = MatchOp::Re(re);
480        let matcher = Matcher::new(op, "name", value);
481        assert!(matcher.is_match("api/v1/query"));
482        assert!(matcher.is_match("api/v1/range_query"));
483        assert!(!matcher.is_match("api/v2"));
484    }
485
486    #[test]
487    fn test_eq_matcher_equality() {
488        assert_eq!(
489            Matcher::new(MatchOp::Equal, "code", "200"),
490            Matcher::new(MatchOp::Equal, "code", "200")
491        );
492
493        assert_ne!(
494            Matcher::new(MatchOp::Equal, "code", "200"),
495            Matcher::new(MatchOp::Equal, "code", "201")
496        );
497
498        assert_ne!(
499            Matcher::new(MatchOp::Equal, "code", "200"),
500            Matcher::new(MatchOp::NotEqual, "code", "200")
501        );
502    }
503
504    #[test]
505    fn test_ne_matcher_equality() {
506        assert_eq!(
507            Matcher::new(MatchOp::NotEqual, "code", "200"),
508            Matcher::new(MatchOp::NotEqual, "code", "200")
509        );
510
511        assert_ne!(
512            Matcher::new(MatchOp::NotEqual, "code", "200"),
513            Matcher::new(MatchOp::NotEqual, "code", "201")
514        );
515
516        assert_ne!(
517            Matcher::new(MatchOp::NotEqual, "code", "200"),
518            Matcher::new(MatchOp::Equal, "code", "200")
519        );
520    }
521
522    #[test]
523    fn test_re_matcher_equality() {
524        assert_eq!(
525            Matcher::new(MatchOp::Re(Regex::new("2??").unwrap()), "code", "2??",),
526            Matcher::new(MatchOp::Re(Regex::new("2??").unwrap()), "code", "2??",)
527        );
528
529        assert_ne!(
530            Matcher::new(MatchOp::Re(Regex::new("2??").unwrap()), "code", "2??",),
531            Matcher::new(MatchOp::Re(Regex::new("2??").unwrap()), "code", "2*?",)
532        );
533
534        assert_ne!(
535            Matcher::new(MatchOp::Re(Regex::new("2??").unwrap()), "code", "2??",),
536            Matcher::new(MatchOp::Equal, "code", "2??")
537        );
538
539        // Test anchoring behavior - should match full string only
540        let matcher = Matcher::new(
541            MatchOp::Re(Matcher::try_parse_re("abc.*").unwrap()),
542            "code",
543            "abc.*",
544        );
545        assert!(matcher.is_match("abc123"));
546        assert!(!matcher.is_match("xabc123"));
547
548        let matcher = Matcher::new(
549            MatchOp::Re(Matcher::try_parse_re(".*xyz$").unwrap()),
550            "code",
551            ".*xyz",
552        );
553        assert!(matcher.is_match("123xyz"));
554        assert!(!matcher.is_match("123xyzx"));
555
556        let matcher = Matcher::new(
557            MatchOp::Re(Matcher::try_parse_re("abc").unwrap()),
558            "code",
559            "abc",
560        );
561        assert!(matcher.is_match("abc"));
562        assert!(!matcher.is_match("xabc"));
563        assert!(!matcher.is_match("abcx"));
564
565        let matcher = Matcher::new(
566            MatchOp::Re(Matcher::try_parse_re("127.0.0.1").unwrap()),
567            "code",
568            "127.0.0.1",
569        );
570        assert!(matcher.is_match("127.0.0.1"));
571        assert!(!matcher.is_match("x127.0.0.1"));
572        assert!(!matcher.is_match("127.0.0.2"));
573
574        let raw_input = r#"127\.0\.0\.1"#;
575        let matcher = Matcher::new(
576            MatchOp::Re(Matcher::try_parse_re(raw_input).unwrap()),
577            "code",
578            raw_input,
579        );
580        assert!(matcher.is_match("127.0.0.1"));
581        assert!(!matcher.is_match("x127.0.0.1"));
582        assert!(!matcher.is_match("127.0.0.2"));
583        // regex round trip
584        let re = Matcher::try_parse_re(raw_input).unwrap();
585        let new_re = Regex::new(re.as_str()).unwrap();
586        assert_eq!(re.as_str(), new_re.as_str());
587    }
588
589    #[test]
590    fn test_not_re_matcher_equality() {
591        assert_eq!(
592            Matcher::new(MatchOp::NotRe(Regex::new("2??").unwrap()), "code", "2??",),
593            Matcher::new(MatchOp::NotRe(Regex::new("2??").unwrap()), "code", "2??",)
594        );
595
596        assert_ne!(
597            Matcher::new(MatchOp::NotRe(Regex::new("2??").unwrap()), "code", "2??",),
598            Matcher::new(MatchOp::NotRe(Regex::new("2?*").unwrap()), "code", "2*?",)
599        );
600
601        assert_ne!(
602            Matcher::new(MatchOp::NotRe(Regex::new("2??").unwrap()), "code", "2??",),
603            Matcher::new(MatchOp::Equal, "code", "2??")
604        );
605
606        // Test anchoring behavior - should NOT match full string only
607        let matcher = Matcher::new(
608            MatchOp::NotRe(Matcher::try_parse_re("abc.*").unwrap()),
609            "code",
610            "abc.*",
611        );
612        assert!(!matcher.is_match("abc123"));
613        assert!(matcher.is_match("xabc123")); // Does not match at start, so NotRe returns true
614
615        let matcher = Matcher::new(
616            MatchOp::NotRe(Matcher::try_parse_re(".*xyz$").unwrap()),
617            "code",
618            ".*xyz",
619        );
620        assert!(!matcher.is_match("123xyz"));
621        assert!(matcher.is_match("123xyzx")); // Does not match at end, so NotRe returns true
622    }
623
624    #[test]
625    fn test_matchers_equality() {
626        assert_eq!(
627            Matchers::empty()
628                .append(Matcher::new(MatchOp::Equal, "name1", "val1"))
629                .append(Matcher::new(MatchOp::Equal, "name2", "val2")),
630            Matchers::empty()
631                .append(Matcher::new(MatchOp::Equal, "name1", "val1"))
632                .append(Matcher::new(MatchOp::Equal, "name2", "val2"))
633        );
634
635        assert_ne!(
636            Matchers::empty().append(Matcher::new(MatchOp::Equal, "name1", "val1")),
637            Matchers::empty().append(Matcher::new(MatchOp::Equal, "name2", "val2"))
638        );
639
640        assert_ne!(
641            Matchers::empty().append(Matcher::new(MatchOp::Equal, "name1", "val1")),
642            Matchers::empty().append(Matcher::new(MatchOp::NotEqual, "name1", "val1"))
643        );
644
645        assert_eq!(
646            Matchers::empty()
647                .append(Matcher::new(MatchOp::Equal, "name1", "val1"))
648                .append(Matcher::new(MatchOp::NotEqual, "name2", "val2"))
649                .append(Matcher::new(
650                    MatchOp::Re(Regex::new("\\d+").unwrap()),
651                    "name2",
652                    "\\d+"
653                ))
654                .append(Matcher::new(
655                    MatchOp::NotRe(Regex::new("\\d+").unwrap()),
656                    "name2",
657                    "\\d+"
658                )),
659            Matchers::empty()
660                .append(Matcher::new(MatchOp::Equal, "name1", "val1"))
661                .append(Matcher::new(MatchOp::NotEqual, "name2", "val2"))
662                .append(Matcher::new(
663                    MatchOp::Re(Regex::new("\\d+").unwrap()),
664                    "name2",
665                    "\\d+"
666                ))
667                .append(Matcher::new(
668                    MatchOp::NotRe(Regex::new("\\d+").unwrap()),
669                    "name2",
670                    "\\d+"
671                ))
672        );
673    }
674
675    #[test]
676    fn test_find_matchers() {
677        let matchers = Matchers::empty()
678            .append(Matcher::new(MatchOp::Equal, "foo", "bar"))
679            .append(Matcher::new(MatchOp::NotEqual, "foo", "bar"))
680            .append(Matcher::new_matcher(T_EQL_REGEX, "foo".into(), "bar".into()).unwrap())
681            .append(Matcher::new_matcher(T_NEQ_REGEX, "foo".into(), "bar".into()).unwrap())
682            .append(Matcher::new(MatchOp::Equal, "FOO", "bar"))
683            .append(Matcher::new(MatchOp::NotEqual, "bar", "bar"));
684
685        let ms = matchers.find_matchers("foo");
686        assert_eq!(4, ms.len());
687    }
688
689    #[test]
690    fn test_convert_re() {
691        assert_eq!(try_escape_for_repeat_re("abc{}"), r"abc\{}");
692        assert_eq!(try_escape_for_repeat_re("abc{def}"), r"abc\{def}");
693        assert_eq!(try_escape_for_repeat_re("abc{def"), r"abc\{def");
694        assert_eq!(try_escape_for_repeat_re("abc{1}"), "abc{1}");
695        assert_eq!(try_escape_for_repeat_re("abc{1,}"), "abc{1,}");
696        assert_eq!(try_escape_for_repeat_re("abc{1,2}"), "abc{1,2}");
697        assert_eq!(try_escape_for_repeat_re("abc{,2}"), r"abc\{,2}");
698        assert_eq!(try_escape_for_repeat_re("abc{{1,2}}"), r"abc\{{1,2}}");
699        assert_eq!(try_escape_for_repeat_re(r"abc\{abc"), r"abc\{abc");
700        assert_eq!(try_escape_for_repeat_re("abc{1a}"), r"abc\{1a}");
701        assert_eq!(try_escape_for_repeat_re("abc{1,a}"), r"abc\{1,a}");
702        assert_eq!(try_escape_for_repeat_re("abc{1,2a}"), r"abc\{1,2a}");
703        assert_eq!(try_escape_for_repeat_re("abc{1,2,3}"), r"abc\{1,2,3}");
704        assert_eq!(try_escape_for_repeat_re("abc{1,,2}"), r"abc\{1,,2}");
705    }
706}