vrl/stdlib/
match_array.rs

1use crate::compiler::prelude::*;
2use std::sync::LazyLock;
3
4static DEFAULT_ALL: LazyLock<Value> = LazyLock::new(|| Value::Boolean(false));
5
6static PARAMETERS: LazyLock<Vec<Parameter>> = LazyLock::new(|| {
7    vec![
8        Parameter::required("value", kind::ARRAY, "The array."),
9        Parameter::required(
10            "pattern",
11            kind::REGEX,
12            "The regular expression pattern to match against.",
13        ),
14        Parameter::optional(
15            "all",
16            kind::BOOLEAN,
17            "Whether to match on all elements of `value`.",
18        )
19        .default(&DEFAULT_ALL),
20    ]
21});
22
23fn match_array(list: Value, pattern: Value, all: Value) -> Resolved {
24    let pattern = pattern.try_regex()?;
25    let list = list.try_array()?;
26    let all = all.try_boolean()?;
27    let matcher = |i: &Value| match i.try_bytes_utf8_lossy() {
28        Ok(v) => pattern.is_match(&v),
29        _ => false,
30    };
31    let included = if all {
32        list.iter().all(matcher)
33    } else {
34        list.iter().any(matcher)
35    };
36    Ok(included.into())
37}
38
39#[derive(Clone, Copy, Debug)]
40pub struct MatchArray;
41
42impl Function for MatchArray {
43    fn identifier(&self) -> &'static str {
44        "match_array"
45    }
46
47    fn usage(&self) -> &'static str {
48        "Determines whether the elements in the `value` array matches the `pattern`. By default, it checks that at least one element matches, but can be set to determine if all the elements match."
49    }
50
51    fn category(&self) -> &'static str {
52        Category::Enumerate.as_ref()
53    }
54
55    fn return_kind(&self) -> u16 {
56        kind::BOOLEAN
57    }
58
59    fn examples(&self) -> &'static [Example] {
60        &[
61            example! {
62                title: "Match at least one element",
63                source: r#"match_array(["foobar", "bazqux"], r'foo')"#,
64                result: Ok("true"),
65            },
66            example! {
67                title: "Match all elements",
68                source: r#"match_array(["foo", "foobar", "barfoo"], r'foo', all: true)"#,
69                result: Ok("true"),
70            },
71            example! {
72                title: "No matches",
73                source: r#"match_array(["bazqux", "xyz"], r'foo')"#,
74                result: Ok("false"),
75            },
76            example! {
77                title: "Not all elements match",
78                source: r#"match_array(["foo", "foobar", "baz"], r'foo', all: true)"#,
79                result: Ok("false"),
80            },
81        ]
82    }
83
84    fn compile(
85        &self,
86        _state: &state::TypeState,
87        _ctx: &mut FunctionCompileContext,
88        arguments: ArgumentList,
89    ) -> Compiled {
90        let value = arguments.required("value");
91        let pattern = arguments.required("pattern");
92        let all = arguments.optional("all");
93
94        Ok(MatchArrayFn {
95            value,
96            pattern,
97            all,
98        }
99        .as_expr())
100    }
101
102    fn parameters(&self) -> &'static [Parameter] {
103        PARAMETERS.as_slice()
104    }
105}
106
107#[derive(Debug, Clone)]
108pub(crate) struct MatchArrayFn {
109    value: Box<dyn Expression>,
110    pattern: Box<dyn Expression>,
111    all: Option<Box<dyn Expression>>,
112}
113
114impl FunctionExpression for MatchArrayFn {
115    fn resolve(&self, ctx: &mut Context) -> Resolved {
116        let list = self.value.resolve(ctx)?;
117        let pattern = self.pattern.resolve(ctx)?;
118        let all = self
119            .all
120            .map_resolve_with_default(ctx, || DEFAULT_ALL.clone())?;
121
122        match_array(list, pattern, all)
123    }
124
125    fn type_def(&self, _: &state::TypeState) -> TypeDef {
126        TypeDef::boolean().infallible()
127    }
128}
129
130#[cfg(test)]
131#[allow(clippy::trivial_regex)]
132mod tests {
133    use super::*;
134    use crate::value;
135    use regex::Regex;
136
137    test_function![
138        match_array => MatchArray;
139
140        default {
141            args: func_args![
142                value: value!(["foo", "foobar", "barfoo"]),
143                pattern: Value::Regex(Regex::new("foo").unwrap().into())
144            ],
145            want: Ok(value!(true)),
146            tdef: TypeDef::boolean().infallible(),
147        }
148
149        all {
150            args: func_args![
151                value: value!(["foo", "foobar", "barfoo"]),
152                pattern: Value::Regex(Regex::new("foo").unwrap().into()),
153                all: value!(true),
154            ],
155            want: Ok(value!(true)),
156            tdef: TypeDef::boolean().infallible(),
157        }
158
159        not_all {
160            args: func_args![
161                value: value!(["foo", "foobar", "baz"]),
162                pattern: Value::Regex(Regex::new("foo").unwrap().into()),
163                all: value!(true),
164            ],
165            want: Ok(value!(false)),
166            tdef: TypeDef::boolean().infallible(),
167        }
168
169        mixed_values {
170            args: func_args![
171                value: value!(["foo", "123abc", 1, true, [1,2,3]]),
172                pattern: Value::Regex(Regex::new("abc").unwrap().into())
173            ],
174            want: Ok(value!(true)),
175            tdef: TypeDef::boolean().infallible(),
176        }
177
178        mixed_values_no_match {
179            args: func_args![
180                value: value!(["foo", "123abc", 1, true, [1,2,3]]),
181                pattern: Value::Regex(Regex::new("xyz").unwrap().into()),
182            ],
183            want: Ok(value!(false)),
184            tdef: TypeDef::boolean().infallible(),
185        }
186
187        mixed_values_no_match_all {
188            args: func_args![
189                value: value!(["foo", "123abc", 1, true, [1,2,3]]),
190                pattern: Value::Regex(Regex::new("abc`").unwrap().into()),
191                all: value!(true),
192            ],
193            want: Ok(value!(false)),
194            tdef: TypeDef::boolean().infallible(),
195        }
196    ];
197}