vrl/stdlib/
contains_all.rs

1use crate::compiler::prelude::*;
2use crate::stdlib::string_utils::convert_to_string;
3
4fn contains_all(value: &Value, substrings: Value, case_sensitive: Option<Value>) -> Resolved {
5    let case_sensitive = match case_sensitive {
6        Some(v) => v.try_boolean()?,
7        None => true,
8    };
9
10    let value_string = convert_to_string(value, !case_sensitive)?;
11    let substring_values = substrings.try_array()?;
12
13    for substring_value in substring_values {
14        let substring = convert_to_string(&substring_value, !case_sensitive)?;
15        if !value_string.contains(substring.as_ref()) {
16            return Ok(false.into());
17        }
18    }
19    Ok(true.into())
20}
21
22#[derive(Clone, Copy, Debug)]
23pub struct ContainsAll;
24
25impl Function for ContainsAll {
26    fn identifier(&self) -> &'static str {
27        "contains_all"
28    }
29
30    fn usage(&self) -> &'static str {
31        "Determines whether the `value` string contains all the specified `substrings`."
32    }
33
34    fn category(&self) -> &'static str {
35        Category::String.as_ref()
36    }
37
38    fn return_kind(&self) -> u16 {
39        kind::BOOLEAN
40    }
41
42    fn parameters(&self) -> &'static [Parameter] {
43        const PARAMETERS: &[Parameter] = &[
44            Parameter::required("value", kind::BYTES, "The text to search."),
45            Parameter::required(
46                "substrings",
47                kind::ARRAY,
48                "An array of substrings to search for in `value`.",
49            ),
50            Parameter::optional(
51                "case_sensitive",
52                kind::BOOLEAN,
53                "Whether the match should be case sensitive.",
54            ),
55        ];
56        PARAMETERS
57    }
58
59    fn compile(
60        &self,
61        _state: &state::TypeState,
62        _ctx: &mut FunctionCompileContext,
63        arguments: ArgumentList,
64    ) -> Compiled {
65        let value = arguments.required("value");
66        let substrings = arguments.required("substrings");
67        let case_sensitive = arguments.optional("case_sensitive");
68
69        Ok(ContainsAllFn {
70            value,
71            substrings,
72            case_sensitive,
73        }
74        .as_expr())
75    }
76
77    fn examples(&self) -> &'static [Example] {
78        &[
79            example! {
80                title: "String contains all with default parameters (case sensitive)",
81                source: r#"contains_all("The NEEDLE in the Haystack", ["NEEDLE", "Haystack"])"#,
82                result: Ok("true"),
83            },
84            example! {
85                title: "String doesn't contain all with default parameters (case sensitive)",
86                source: r#"contains_all("The NEEDLE in the Haystack", ["needle", "Haystack"])"#,
87                result: Ok("false"),
88            },
89            example! {
90                title: "String contains all (case insensitive)",
91                source: r#"contains_all("The NEEDLE in the HaYsTaCk", ["nEeDlE", "haystack"], case_sensitive: false)"#,
92                result: Ok("true"),
93            },
94        ]
95    }
96}
97
98#[derive(Clone, Debug)]
99struct ContainsAllFn {
100    value: Box<dyn Expression>,
101    substrings: Box<dyn Expression>,
102    case_sensitive: Option<Box<dyn Expression>>,
103}
104
105impl FunctionExpression for ContainsAllFn {
106    fn resolve(&self, ctx: &mut Context) -> Resolved {
107        let value = self.value.resolve(ctx)?;
108        let substrings = self.substrings.resolve(ctx)?;
109        let case_sensitive = self
110            .case_sensitive
111            .as_ref()
112            .map(|expr| expr.resolve(ctx))
113            .transpose()?;
114        contains_all(&value, substrings, case_sensitive)
115    }
116
117    fn type_def(&self, state: &TypeState) -> TypeDef {
118        let substring_type_def = self.substrings.type_def(state);
119        let collection = substring_type_def.as_array().expect("must be an array");
120        let bytes_collection = Collection::from_unknown(Kind::bytes());
121        TypeDef::boolean().maybe_fallible(bytes_collection.is_superset(collection).is_err())
122    }
123}
124
125#[cfg(test)]
126mod tests {
127    use crate::value;
128
129    use super::*;
130
131    test_function![
132        contains_all => ContainsAll;
133
134        no {
135            args: func_args![value: value!("The Needle In The Haystack"),
136                             substrings: value!(["the", "duck"])],
137            want: Ok(value!(false)),
138            tdef: TypeDef::boolean().infallible(),
139        }
140
141        substring_type {
142            args: func_args![value: value!("The Needle In The Haystack"),
143                             substrings: value!([1, 2])],
144            want: Err("expected string, got integer"),
145            tdef: TypeDef::boolean().fallible(),
146        }
147
148        yes {
149            args: func_args![value: value!("The Needle In The Haystack"),
150                             substrings: value!(["The Needle", "Needle In"])],
151            want: Ok(value!(true)),
152            tdef: TypeDef::boolean().infallible(),
153        }
154
155        case_sensitive_yes {
156            args: func_args![value: value!("The Needle In The Haystack"),
157                             substrings: value!(["Needle", "Haystack"])],
158            want: Ok(value!(true)),
159            tdef: TypeDef::boolean().infallible(),
160        }
161
162         case_sensitive_no {
163                        args: func_args![value: value!("The Needle In The Haystack"),
164                             substrings: value!(["needle", "haystack"])],
165            want: Ok(value!(false)),
166            tdef: TypeDef::boolean().infallible(),
167        }
168
169        case_insensitive_no {
170                        args: func_args![value: value!("The Needle In The Haystack"),
171                                        substrings: value!(["thread", "haystack"]),
172                                        case_sensitive: false],
173            want: Ok(value!(false)),
174            tdef: TypeDef::boolean().infallible(),
175        }
176
177        case_insensitive_yes {
178                       args: func_args![value: value!("The Needle In The Haystack"),
179                                        substrings: value!(["needle", "haystack"]),
180                                        case_sensitive: false],
181            want: Ok(value!(true)),
182            tdef: TypeDef::boolean().infallible(),
183        }
184    ];
185}