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}