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 string to search."),
10 Parameter::required(
11 "substring",
12 kind::BYTES,
13 "The substring with which `value` must end.",
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 ends_with(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.ends_with(substring.as_ref()).into())
29}
30
31#[derive(Clone, Copy, Debug)]
32pub struct EndsWith;
33
34impl Function for EndsWith {
35 fn identifier(&self) -> &'static str {
36 "ends_with"
37 }
38
39 fn usage(&self) -> &'static str {
40 "Determines whether the `value` string ends with 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(EndsWithFn {
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 ends with (case sensitive)",
77 source: r#"ends_with("The Needle In The Haystack", "The Haystack")"#,
78 result: Ok("true"),
79 },
80 example! {
81 title: "String ends with (case insensitive)",
82 source: r#"ends_with("The Needle In The Haystack", "the haystack", case_sensitive: false)"#,
83 result: Ok("true"),
84 },
85 example! {
86 title: "String ends with (case sensitive failure)",
87 source: r#"ends_with("foobar", "R")"#,
88 result: Ok("false"),
89 },
90 ]
91 }
92}
93
94#[derive(Clone, Debug)]
95struct EndsWithFn {
96 value: Box<dyn Expression>,
97 substring: Box<dyn Expression>,
98 case_sensitive: Option<Box<dyn Expression>>,
99}
100
101impl FunctionExpression for EndsWithFn {
102 fn resolve(&self, ctx: &mut Context) -> Resolved {
103 let case_sensitive = self
104 .case_sensitive
105 .map_resolve_with_default(ctx, || DEFAULT_CASE_SENSITIVE.clone())?;
106 let substring = self.substring.resolve(ctx)?;
107 let value = self.value.resolve(ctx)?;
108
109 ends_with(&value, &substring, case_sensitive)
110 }
111
112 fn type_def(&self, _: &state::TypeState) -> TypeDef {
113 TypeDef::boolean().infallible()
114 }
115}
116
117#[cfg(test)]
118mod tests {
119 use crate::value;
120
121 use super::*;
122
123 test_function![
124 ends_with => EndsWith;
125
126 no {
127 args: func_args![value: "bar",
128 substring: "foo"],
129 want: Ok(value!(false)),
130 tdef: TypeDef::boolean().infallible(),
131 }
132
133 opposite {
134 args: func_args![value: "bar",
135 substring: "foobar"],
136 want: Ok(value!(false)),
137 tdef: TypeDef::boolean().infallible(),
138 }
139
140 subset {
141 args: func_args![value: "foobar",
142 substring: "oba"],
143 want: Ok(value!(false)),
144 tdef: TypeDef::boolean().infallible(),
145 }
146
147 yes {
148 args: func_args![value: "foobar",
149 substring: "bar"],
150 want: Ok(value!(true)),
151 tdef: TypeDef::boolean().infallible(),
152 }
153
154 starts_with {
155 args: func_args![value: "foobar",
156 substring: "foo"],
157 want: Ok(value!(false)),
158 tdef: TypeDef::boolean().infallible(),
159 }
160
161 uppercase {
162 args: func_args![value: "fooBAR",
163 substring: "BAR"
164 ],
165 want: Ok(value!(true)),
166 tdef: TypeDef::boolean().infallible(),
167 }
168
169 case_sensitive {
170 args: func_args![value: "foobar",
171 substring: "BAR"
172 ],
173 want: Ok(value!(false)),
174 tdef: TypeDef::boolean().infallible(),
175 }
176
177 case_insensitive {
178 args: func_args![value: "foobar",
179 substring: "BAR",
180 case_sensitive: false],
181 want: Ok(value!(true)),
182 tdef: TypeDef::boolean().infallible(),
183 }
184 ];
185}