vrl/stdlib/
ends_with.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 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}