vrl/stdlib/
to_int.rs

1use crate::compiler::conversion::Conversion;
2use crate::compiler::prelude::*;
3
4fn to_int(value: Value) -> Resolved {
5    use Value::{Boolean, Bytes, Float, Integer, Null, Timestamp};
6
7    match value {
8        Integer(_) => Ok(value),
9        #[allow(clippy::cast_possible_truncation)] //TODO evaluate removal options
10        Float(v) => Ok(Integer(v.into_inner() as i64)),
11        Boolean(v) => Ok(Integer(i64::from(v))),
12        Null => Ok(0.into()),
13        Bytes(v) => Conversion::Integer
14            .convert(v)
15            .map_err(|e| e.to_string().into()),
16        Timestamp(v) => Ok(v.timestamp().into()),
17        v => Err(format!("unable to coerce {} into integer", v.kind()).into()),
18    }
19}
20
21#[derive(Clone, Copy, Debug)]
22pub struct ToInt;
23
24impl Function for ToInt {
25    fn identifier(&self) -> &'static str {
26        "to_int"
27    }
28
29    fn usage(&self) -> &'static str {
30        "Coerces the `value` into an integer."
31    }
32
33    fn category(&self) -> &'static str {
34        Category::Coerce.as_ref()
35    }
36
37    fn internal_failure_reasons(&self) -> &'static [&'static str] {
38        &[
39            "`value` is a string but the text is not an integer.",
40            "`value` is not a string, int, or timestamp.",
41        ]
42    }
43
44    fn return_kind(&self) -> u16 {
45        kind::INTEGER
46    }
47
48    fn return_rules(&self) -> &'static [&'static str] {
49        &[
50            "If `value` is an integer, it will be returned as-is.",
51            "If `value` is a float, it will be truncated to its integer portion.",
52            "If `value` is a string, it must be the string representation of an integer or else an error is raised.",
53            "If `value` is a boolean, `0` is returned for `false` and `1` is returned for `true`.",
54            "If `value` is a timestamp, a [Unix timestamp](https://en.wikipedia.org/wiki/Unix_time) (in seconds) is returned.",
55            "If `value` is null, `0` is returned.",
56        ]
57    }
58
59    fn parameters(&self) -> &'static [Parameter] {
60        const PARAMETERS: &[Parameter] = &[Parameter::required(
61            "value",
62            kind::ANY,
63            "The value to convert to an integer.",
64        )];
65        PARAMETERS
66    }
67
68    fn examples(&self) -> &'static [Example] {
69        &[
70            example! {
71                title: "Coerce to an int (string)",
72                source: "to_int!(\"2\")",
73                result: Ok("2"),
74            },
75            example! {
76                title: "Coerce to an int (timestamp)",
77                source: "to_int(t'2020-12-30T22:20:53.824727Z')",
78                result: Ok("1609366853"),
79            },
80            example! {
81                title: "Integer",
82                source: "to_int(5)",
83                result: Ok("5"),
84            },
85            example! {
86                title: "Float",
87                source: "to_int(5.6)",
88                result: Ok("5"),
89            },
90            example! {
91                title: "True",
92                source: "to_int(true)",
93                result: Ok("1"),
94            },
95            example! {
96                title: "False",
97                source: "to_int(false)",
98                result: Ok("0"),
99            },
100            example! {
101                title: "Null",
102                source: "to_int(null)",
103                result: Ok("0"),
104            },
105            example! {
106                title: "Invalid string",
107                source: "to_int!(s'foobar')",
108                result: Err(
109                    r#"function call error for "to_int" at (0:18): Invalid integer "foobar": invalid digit found in string"#,
110                ),
111            },
112            example! {
113                title: "Array",
114                source: "to_int!([])",
115                result: Err(
116                    r#"function call error for "to_int" at (0:11): unable to coerce array into integer"#,
117                ),
118            },
119            example! {
120                title: "Object",
121                source: "to_int!({})",
122                result: Err(
123                    r#"function call error for "to_int" at (0:11): unable to coerce object into integer"#,
124                ),
125            },
126            example! {
127                title: "Regex",
128                source: "to_int!(r'foo')",
129                result: Err(
130                    r#"function call error for "to_int" at (0:15): unable to coerce regex into integer"#,
131                ),
132            },
133        ]
134    }
135
136    fn compile(
137        &self,
138        _state: &state::TypeState,
139        _ctx: &mut FunctionCompileContext,
140        arguments: ArgumentList,
141    ) -> Compiled {
142        let value = arguments.required("value");
143
144        Ok(ToIntFn { value }.as_expr())
145    }
146}
147
148#[derive(Debug, Clone)]
149struct ToIntFn {
150    value: Box<dyn Expression>,
151}
152
153impl FunctionExpression for ToIntFn {
154    fn resolve(&self, ctx: &mut Context) -> Resolved {
155        let value = self.value.resolve(ctx)?;
156
157        to_int(value)
158    }
159
160    fn type_def(&self, state: &state::TypeState) -> TypeDef {
161        let td = self.value.type_def(state);
162
163        TypeDef::integer().maybe_fallible(
164            td.contains_bytes()
165                || td.contains_array()
166                || td.contains_object()
167                || td.contains_regex(),
168        )
169    }
170}
171
172#[cfg(test)]
173mod tests {
174    use chrono::{DateTime, Utc};
175
176    use super::*;
177
178    test_function![
179        to_int => ToInt;
180
181        string {
182             args: func_args![value: "20"],
183             want: Ok(20),
184             tdef: TypeDef::integer().fallible(),
185        }
186
187        float {
188             args: func_args![value: 20.5],
189             want: Ok(20),
190             tdef: TypeDef::integer().infallible(),
191        }
192
193        timezone {
194             args: func_args![value: DateTime::parse_from_rfc2822("Wed, 16 Oct 2019 12:00:00 +0000")
195                            .unwrap()
196                            .with_timezone(&Utc)],
197             want: Ok(1_571_227_200),
198             tdef: TypeDef::integer().infallible(),
199         }
200    ];
201}