vrl/stdlib/
match_any.rs

1use crate::compiler::prelude::*;
2use regex::bytes::RegexSet;
3
4fn match_any(value: Value, pattern: &RegexSet) -> Resolved {
5    let bytes = value.try_bytes()?;
6    Ok(pattern.is_match(&bytes).into())
7}
8
9#[derive(Clone, Copy, Debug)]
10pub struct MatchAny;
11
12impl Function for MatchAny {
13    fn identifier(&self) -> &'static str {
14        "match_any"
15    }
16
17    fn usage(&self) -> &'static str {
18        "Determines whether `value` matches any of the given `patterns`. All patterns are checked in a single pass over the target string, giving this function a potential performance advantage over the multiple calls in the `match` function."
19    }
20
21    fn category(&self) -> &'static str {
22        Category::String.as_ref()
23    }
24
25    fn return_kind(&self) -> u16 {
26        kind::BOOLEAN
27    }
28
29    fn parameters(&self) -> &'static [Parameter] {
30        const PARAMETERS: &[Parameter] = &[
31            Parameter::required("value", kind::BYTES, "The value to match."),
32            Parameter::required(
33                "patterns",
34                kind::ARRAY,
35                "The array of regular expression patterns to match against.",
36            ),
37        ];
38        PARAMETERS
39    }
40
41    fn examples(&self) -> &'static [Example] {
42        &[
43            example! {
44                title: "Regex match on a string",
45                source: r#"match_any("I'm a little teapot", [r'frying pan', r'teapot'])"#,
46                result: Ok("true"),
47            },
48            example! {
49                title: "No match",
50                source: r#"match_any("My name is John Doe", patterns: [r'\d+', r'Jane'])"#,
51                result: Ok("false"),
52            },
53        ]
54    }
55
56    fn compile(
57        &self,
58        state: &state::TypeState,
59        _ctx: &mut FunctionCompileContext,
60        arguments: ArgumentList,
61    ) -> Compiled {
62        let value = arguments.required("value");
63        let patterns = arguments.required_array("patterns")?;
64
65        let mut re_strings = Vec::with_capacity(patterns.len());
66        for expr in patterns {
67            let value =
68                expr.resolve_constant(state)
69                    .ok_or(function::Error::ExpectedStaticExpression {
70                        keyword: "patterns",
71                        expr,
72                    })?;
73
74            let re = value
75                .try_regex()
76                .map_err(|e| Box::new(e) as Box<dyn DiagnosticMessage>)?;
77            re_strings.push(re.to_string());
78        }
79
80        let regex_set = RegexSet::new(re_strings).expect("regex were already valid");
81
82        Ok(MatchAnyFn { value, regex_set }.as_expr())
83    }
84}
85
86#[derive(Clone, Debug)]
87struct MatchAnyFn {
88    value: Box<dyn Expression>,
89    regex_set: RegexSet,
90}
91
92impl FunctionExpression for MatchAnyFn {
93    fn resolve(&self, ctx: &mut Context) -> Resolved {
94        let value = self.value.resolve(ctx)?;
95        match_any(value, &self.regex_set)
96    }
97
98    fn type_def(&self, _: &state::TypeState) -> TypeDef {
99        TypeDef::boolean().infallible()
100    }
101}
102
103#[cfg(test)]
104#[allow(clippy::trivial_regex)]
105mod tests {
106    use super::*;
107    use crate::value;
108    use regex::Regex;
109
110    test_function![
111        r#match_any => MatchAny;
112
113        yes {
114            args: func_args![value: "foobar",
115                             patterns: Value::Array(vec![
116                                 Value::Regex(Regex::new("foo").unwrap().into()),
117                                 Value::Regex(Regex::new("bar").unwrap().into()),
118                                 Value::Regex(Regex::new("baz").unwrap().into()),
119                             ])],
120            want: Ok(value!(true)),
121            tdef: TypeDef::boolean().infallible(),
122        }
123
124        no {
125            args: func_args![value: "foo 2 bar",
126                             patterns: Value::Array(vec![
127                                 Value::Regex(Regex::new("baz|quux").unwrap().into()),
128                                 Value::Regex(Regex::new("foobar").unwrap().into()),
129                             ])],
130            want: Ok(value!(false)),
131            tdef: TypeDef::boolean().infallible(),
132        }
133    ];
134}