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)] 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}