vrl/stdlib/
match.rs

1use regex::Regex;
2
3use crate::compiler::prelude::*;
4
5fn match_(value: &Value, pattern: Value) -> Resolved {
6    let string = value.try_bytes_utf8_lossy()?;
7    let pattern = pattern.try_regex()?;
8    Ok(pattern.is_match(&string).into())
9}
10
11fn match_static(value: &Value, pattern: &Regex) -> Resolved {
12    let string = value.try_bytes_utf8_lossy()?;
13    Ok(pattern.is_match(&string).into())
14}
15
16#[derive(Clone, Copy, Debug)]
17pub struct Match;
18
19impl Function for Match {
20    fn identifier(&self) -> &'static str {
21        "match"
22    }
23
24    fn usage(&self) -> &'static str {
25        "Determines whether the `value` matches the `pattern`."
26    }
27
28    fn category(&self) -> &'static str {
29        Category::String.as_ref()
30    }
31
32    fn return_kind(&self) -> u16 {
33        kind::BOOLEAN
34    }
35
36    fn parameters(&self) -> &'static [Parameter] {
37        const PARAMETERS: &[Parameter] = &[
38            Parameter::required("value", kind::BYTES, "The value to match."),
39            Parameter::required(
40                "pattern",
41                kind::REGEX,
42                "The regular expression pattern to match against.",
43            ),
44        ];
45        PARAMETERS
46    }
47
48    fn examples(&self) -> &'static [Example] {
49        &[
50            example! {
51                title: "Regex match on a string",
52                source: r#"match("I'm a little teapot", r'teapot')"#,
53                result: Ok("true"),
54            },
55            example! {
56                title: "String does not match the regular expression",
57                source: r#"match("I'm a little teapot", r'.*balloon')"#,
58                result: Ok("false"),
59            },
60        ]
61    }
62
63    fn compile(
64        &self,
65        state: &state::TypeState,
66        _ctx: &mut FunctionCompileContext,
67        arguments: ArgumentList,
68    ) -> Compiled {
69        let value = arguments.required("value");
70        let pattern = arguments.required("pattern");
71
72        match pattern.resolve_constant(state) {
73            Some(pattern) => {
74                let pattern = pattern
75                    .try_regex()
76                    .map_err(|e| Box::new(e) as Box<dyn DiagnosticMessage>)?;
77
78                let pattern = Regex::new(pattern.as_str()).map_err(|e| {
79                    Box::new(ExpressionError::from(e.to_string())) as Box<dyn DiagnosticMessage>
80                })?;
81
82                Ok(MatchStaticFn { value, pattern }.as_expr())
83            }
84            None => Ok(MatchFn { value, pattern }.as_expr()),
85        }
86    }
87}
88
89#[derive(Debug, Clone)]
90pub(crate) struct MatchFn {
91    value: Box<dyn Expression>,
92    pattern: Box<dyn Expression>,
93}
94
95impl FunctionExpression for MatchFn {
96    fn resolve(&self, ctx: &mut Context) -> Resolved {
97        let value = self.value.resolve(ctx)?;
98        let pattern = self.pattern.resolve(ctx)?;
99
100        match_(&value, pattern)
101    }
102
103    fn type_def(&self, _: &state::TypeState) -> TypeDef {
104        TypeDef::boolean().infallible()
105    }
106}
107
108#[derive(Debug, Clone)]
109pub(crate) struct MatchStaticFn {
110    value: Box<dyn Expression>,
111    pattern: Regex,
112}
113
114impl FunctionExpression for MatchStaticFn {
115    fn resolve(&self, ctx: &mut Context) -> Resolved {
116        let value = self.value.resolve(ctx)?;
117
118        match_static(&value, &self.pattern)
119    }
120
121    fn type_def(&self, _: &state::TypeState) -> TypeDef {
122        TypeDef::boolean().infallible()
123    }
124}
125
126#[cfg(test)]
127#[allow(clippy::trivial_regex)]
128mod tests {
129    use crate::value;
130
131    use super::*;
132
133    test_function![
134        r#match => Match;
135
136        yes {
137            args: func_args![value: "foobar",
138                             pattern: Value::Regex(Regex::new("\\s\\w+").unwrap().into())],
139            want: Ok(value!(false)),
140            tdef: TypeDef::boolean().infallible(),
141        }
142
143        no {
144            args: func_args![value: "foo 2 bar",
145                             pattern: Value::Regex(Regex::new("foo \\d bar").unwrap().into())],
146            want: Ok(value!(true)),
147            tdef: TypeDef::boolean().infallible(),
148        }
149    ];
150}