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