vrl/stdlib/
contains.rs

1use crate::compiler::prelude::*;
2use crate::stdlib::string_utils::convert_to_string;
3use std::sync::LazyLock;
4
5static DEFAULT_CASE_SENSITIVE: LazyLock<Value> = LazyLock::new(|| Value::Boolean(true));
6
7static PARAMETERS: LazyLock<Vec<Parameter>> = LazyLock::new(|| {
8    vec![
9        Parameter::required("value", kind::BYTES, "The text to search."),
10        Parameter::required(
11            "substring",
12            kind::BYTES,
13            "The substring to search for in `value`.",
14        ),
15        Parameter::optional(
16            "case_sensitive",
17            kind::BOOLEAN,
18            "Whether the match should be case sensitive.",
19        )
20        .default(&DEFAULT_CASE_SENSITIVE),
21    ]
22});
23
24fn contains(value: &Value, substring: &Value, case_sensitive: Value) -> Resolved {
25    let case_sensitive = case_sensitive.try_boolean()?;
26    let value = convert_to_string(value, !case_sensitive)?;
27    let substring = convert_to_string(substring, !case_sensitive)?;
28    Ok(value.contains(substring.as_ref()).into())
29}
30
31#[derive(Clone, Copy, Debug)]
32pub struct Contains;
33
34impl Function for Contains {
35    fn identifier(&self) -> &'static str {
36        "contains"
37    }
38
39    fn usage(&self) -> &'static str {
40        "Determines whether the `value` string contains the specified `substring`."
41    }
42
43    fn category(&self) -> &'static str {
44        Category::String.as_ref()
45    }
46
47    fn return_kind(&self) -> u16 {
48        kind::BOOLEAN
49    }
50
51    fn parameters(&self) -> &'static [Parameter] {
52        PARAMETERS.as_slice()
53    }
54
55    fn compile(
56        &self,
57        _state: &state::TypeState,
58        _ctx: &mut FunctionCompileContext,
59        arguments: ArgumentList,
60    ) -> Compiled {
61        let value = arguments.required("value");
62        let substring = arguments.required("substring");
63        let case_sensitive = arguments.optional("case_sensitive");
64
65        Ok(ContainsFn {
66            value,
67            substring,
68            case_sensitive,
69        }
70        .as_expr())
71    }
72
73    fn examples(&self) -> &'static [Example] {
74        &[
75            example! {
76                title: "String contains with default parameters (case sensitive)",
77                source: r#"contains("banana", "AnA")"#,
78                result: Ok("false"),
79            },
80            example! {
81                title: "String contains (case insensitive)",
82                source: r#"contains("banana", "AnA", case_sensitive: false)"#,
83                result: Ok("true"),
84            },
85        ]
86    }
87}
88
89#[derive(Clone, Debug)]
90struct ContainsFn {
91    value: Box<dyn Expression>,
92    substring: Box<dyn Expression>,
93    case_sensitive: Option<Box<dyn Expression>>,
94}
95
96impl FunctionExpression for ContainsFn {
97    fn resolve(&self, ctx: &mut Context) -> Resolved {
98        let value = self.value.resolve(ctx)?;
99        let substring = self.substring.resolve(ctx)?;
100        let case_sensitive = self
101            .case_sensitive
102            .map_resolve_with_default(ctx, || DEFAULT_CASE_SENSITIVE.clone())?;
103
104        contains(&value, &substring, case_sensitive)
105    }
106
107    fn type_def(&self, _: &state::TypeState) -> TypeDef {
108        TypeDef::boolean().infallible()
109    }
110}
111
112#[cfg(test)]
113mod tests {
114    use crate::value;
115
116    use super::*;
117
118    test_function![
119        contains => Contains;
120
121        no {
122            args: func_args![value: value!("foo"),
123                             substring: value!("bar")],
124            want: Ok(value!(false)),
125            tdef: TypeDef::boolean().infallible(),
126        }
127
128        yes {
129            args: func_args![value: value!("foobar"),
130                             substring: value!("foo")],
131            want: Ok(value!(true)),
132            tdef: TypeDef::boolean().infallible(),
133        }
134
135        entirely {
136            args: func_args![value: value!("foo"),
137                             substring: value!("foo")],
138            want: Ok(value!(true)),
139            tdef: TypeDef::boolean().infallible(),
140        }
141
142        middle {
143            args: func_args![value: value!("foobar"),
144                             substring: value!("oba")],
145            want: Ok(value!(true)),
146            tdef: TypeDef::boolean().infallible(),
147        }
148
149        start {
150            args: func_args![value: value!("foobar"),
151                             substring: value!("foo")],
152            want: Ok(value!(true)),
153            tdef: TypeDef::boolean().infallible(),
154        }
155
156        end {
157            args: func_args![value: value!("foobar"),
158                             substring: value!("bar")],
159            want: Ok(value!(true)),
160            tdef: TypeDef::boolean().infallible(),
161        }
162
163        case_sensitive_yes {
164            args: func_args![value: value!("fooBAR"),
165                             substring: value!("BAR"),
166            ],
167            want: Ok(value!(true)),
168            tdef: TypeDef::boolean().infallible(),
169        }
170
171         case_sensitive_yes_lowercase {
172            args: func_args![value: value!("fooBAR"),
173                             substring: value!("bar"),
174                             case_sensitive: true
175            ],
176            want: Ok(value!(false)),
177            tdef: TypeDef::boolean().infallible(),
178        }
179
180        case_sensitive_no_uppercase {
181            args: func_args![value: value!("foobar"),
182                             substring: value!("BAR"),
183            ],
184            want: Ok(value!(false)),
185            tdef: TypeDef::boolean().infallible(),
186        }
187
188        case_insensitive_yes_uppercase {
189            args: func_args![value: value!("foobar"),
190                             substring: value!("BAR"),
191                             case_sensitive: false
192            ],
193            want: Ok(value!(true)),
194            tdef: TypeDef::boolean().infallible(),
195        }
196    ];
197}