vrl/stdlib/
to_float.rs

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