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}