vrl/stdlib/
random_float.rs

1use crate::compiler::prelude::*;
2use rand::{Rng, thread_rng};
3use std::ops::Range;
4
5const INVALID_RANGE_ERR: &str = "max must be greater than min";
6
7fn random_float(min: Value, max: Value) -> Resolved {
8    let min = min.try_float()?;
9    let max = max.try_float()?;
10
11    if max <= min {
12        return Err("max must be greater than min".into());
13    }
14
15    let f: f64 = thread_rng().gen_range(min..max);
16
17    Ok(Value::Float(NotNan::new(f).expect("always a number")))
18}
19
20fn get_range(min: Value, max: Value) -> std::result::Result<Range<f64>, &'static str> {
21    let min = min.try_float().expect("min must be a float");
22    let max = max.try_float().expect("max must be a float");
23
24    if max <= min {
25        return Err(INVALID_RANGE_ERR);
26    }
27
28    Ok(min..max)
29}
30
31#[derive(Clone, Copy, Debug)]
32pub struct RandomFloat;
33
34impl Function for RandomFloat {
35    fn identifier(&self) -> &'static str {
36        "random_float"
37    }
38
39    fn usage(&self) -> &'static str {
40        "Returns a random float between [min, max)."
41    }
42
43    fn category(&self) -> &'static str {
44        Category::Random.as_ref()
45    }
46
47    fn internal_failure_reasons(&self) -> &'static [&'static str] {
48        &["`max` is not greater than `min`."]
49    }
50
51    fn return_kind(&self) -> u16 {
52        kind::FLOAT
53    }
54
55    fn parameters(&self) -> &'static [Parameter] {
56        const PARAMETERS: &[Parameter] = &[
57            Parameter::required("min", kind::FLOAT, "Minimum value (inclusive)."),
58            Parameter::required("max", kind::FLOAT, "Maximum value (exclusive)."),
59        ];
60        PARAMETERS
61    }
62
63    fn examples(&self) -> &'static [Example] {
64        &[example! {
65            title: "Random float from 0.0 to 10.0, not including 10.0",
66            source: indoc! {"
67                f = random_float(0.0, 10.0)
68                f >= 0 && f < 10
69            "},
70            result: Ok("true"),
71        }]
72    }
73
74    fn compile(
75        &self,
76        state: &state::TypeState,
77        _ctx: &mut FunctionCompileContext,
78        arguments: ArgumentList,
79    ) -> Compiled {
80        let min = arguments.required("min");
81        let max = arguments.required("max");
82
83        if let (Some(min), Some(max)) = (min.resolve_constant(state), max.resolve_constant(state)) {
84            // check if range is valid
85            let _: Range<f64> =
86                get_range(min, max.clone()).map_err(|err| function::Error::InvalidArgument {
87                    keyword: "max",
88                    value: max,
89                    error: err,
90                })?;
91        }
92
93        Ok(RandomFloatFn { min, max }.as_expr())
94    }
95}
96
97#[derive(Debug, Clone)]
98struct RandomFloatFn {
99    min: Box<dyn Expression>,
100    max: Box<dyn Expression>,
101}
102
103impl FunctionExpression for RandomFloatFn {
104    fn resolve(&self, ctx: &mut Context) -> Resolved {
105        let min = self.min.resolve(ctx)?;
106        let max = self.max.resolve(ctx)?;
107
108        random_float(min, max)
109    }
110
111    fn type_def(&self, state: &state::TypeState) -> TypeDef {
112        match (
113            self.min.resolve_constant(state),
114            self.max.resolve_constant(state),
115        ) {
116            (Some(min), Some(max)) => {
117                if get_range(min, max).is_ok() {
118                    TypeDef::float().infallible()
119                } else {
120                    TypeDef::float().fallible()
121                }
122            }
123            _ => TypeDef::float().fallible(),
124        }
125    }
126}
127
128#[cfg(test)]
129mod tests {
130    use super::*;
131    use crate::value;
132    // positive tests are handled by examples
133
134    test_function![
135        random_float => RandomFloat;
136
137        bad_range {
138            args: func_args![min: value!(1.0), max: value!(1.0)],
139            want: Err("invalid argument"),
140            tdef: TypeDef::float().fallible(),
141        }
142    ];
143}