use crate::prelude::*;
use crate::registry::{mfa, FResult, FunctionError, Registry, TremorFn, TremorFnWrapper};
use crate::tremor_fn;
use crate::EventContext;
use crate::Value;
use rand::distributions::Alphanumeric;
use rand::{rngs::SmallRng, Rng, SeedableRng};
#[derive(Clone, Debug, Default)]
struct RandomInteger {}
impl TremorFn for RandomInteger {
fn invoke<'event, 'c>(
&self,
ctx: &'c EventContext,
args: &[&Value<'event>],
) -> FResult<Value<'event>> {
let this_mfa = || mfa("random", "integer", args.len());
let mut rng = SmallRng::seed_from_u64(ctx.ingest_ns());
match args {
[low, high] => {
if let (Some(low), Some(high)) = (low.as_i64(), high.as_i64()) {
if low < high {
Ok(Value::from(rng.gen_range(low..high)))
} else {
Err(FunctionError::RuntimeError {
mfa: this_mfa(),
error:
"Invalid arguments. First argument must be lower than second argument".to_string(),
})
}
} else {
Err(FunctionError::BadType { mfa: this_mfa() })
}
}
[input] => {
input.as_u64().map_or_else(
|| Err(FunctionError::BadType { mfa: this_mfa() }),
|input| {
if input == 0 {
Err(FunctionError::RuntimeError {
mfa: this_mfa(),
error: "Invalid arguments. Single argument value must be > 0."
.to_string(),
})
} else {
Ok(Value::from(rng.gen_range(0..input)))
}
},
)
}
[] => Ok(Value::from(
rng.gen::<i64>(), )),
_ => Err(FunctionError::BadArity {
mfa: this_mfa(),
calling_a: args.len(),
}),
}
}
fn boxed_clone(&self) -> Box<dyn TremorFn> {
Box::new(self.clone())
}
fn arity(&self) -> std::ops::RangeInclusive<usize> {
0..=2
}
fn is_const(&self) -> bool {
false
}
}
#[derive(Clone, Debug, Default)]
struct RandomFloat {}
impl TremorFn for RandomFloat {
fn invoke<'event, 'c>(
&self,
ctx: &'c EventContext,
args: &[&Value<'event>],
) -> FResult<Value<'event>> {
let this_mfa = || mfa("random", "float", args.len());
let mut rng = SmallRng::seed_from_u64(ctx.ingest_ns());
match args {
[low, high] => {
if let (Some(low), Some(high)) = (low.cast_f64(), high.cast_f64()) {
if low < high {
Ok(Value::from(rng.gen_range(low..high)))
} else {
Err(FunctionError::RuntimeError {
mfa: this_mfa(),
error:
"Invalid arguments. First argument must be lower than second argument".to_string(),
})
}
} else {
Err(FunctionError::BadType { mfa: this_mfa() })
}
}
[input] => {
input.cast_f64().map_or_else(
|| Err(FunctionError::BadType { mfa: this_mfa() }),
|input| {
if input <= 0.0 {
Err(FunctionError::RuntimeError {
mfa: this_mfa(),
error: "Invalid arguments. Single argument value must be > 0.0."
.to_string(),
})
} else {
Ok(Value::from(rng.gen_range(0.0..input)))
}
},
)
}
[] => Ok(Value::from(
rng.gen::<f64>(), )),
_ => Err(FunctionError::BadArity {
mfa: this_mfa(),
calling_a: args.len(),
}),
}
}
fn boxed_clone(&self) -> Box<dyn TremorFn> {
Box::new(self.clone())
}
fn arity(&self) -> std::ops::RangeInclusive<usize> {
0..=2
}
fn is_const(&self) -> bool {
false
}
}
pub fn load(registry: &mut Registry) {
registry
.insert(tremor_fn! (random|bool(_context) {
Ok(Value::from(
SmallRng::seed_from_u64(_context.ingest_ns())
.gen::<bool>()
))
}))
.insert(tremor_fn! (random|string(_context, _length) {
_length.as_usize().map_or_else(
||Err(FunctionError::BadType{mfa: this_mfa()}),
|n| {
Ok(Value::from(
SmallRng::seed_from_u64(_context.ingest_ns())
.sample_iter(&Alphanumeric).map(char::from).take(n).collect::<String>()
))
}
)
}))
.insert(TremorFnWrapper::new(
"random".to_string(),
"integer".to_string(),
Box::<RandomInteger>::default(),
))
.insert(TremorFnWrapper::new(
"random".to_string(),
"float".to_string(),
Box::<RandomFloat>::default(),
));
}
#[cfg(test)]
mod test {
use crate::registry::fun;
use crate::Value;
use proptest::prelude::*;
use simd_json::prelude::*;
#[test]
fn bool() {
let f = fun("random", "bool");
assert!(f(&[]).ok().map(|v| v.is_bool()).unwrap_or_default());
}
#[test]
fn string() {
let f = fun("random", "string");
let n = 0;
assert_val!(f(&[&Value::from(n)]), "");
let n = 16;
assert!(match f(&[&Value::from(n)]) {
Ok(Value::String(s)) => s.len() as i64 == n,
_ => false,
});
}
#[test]
fn integer() {
let f = fun("random", "integer");
let v1 = Value::from(0);
let v2 = Value::from(1);
assert_val!(f(&[&v1, &v2]), 0);
let v1 = Value::from(-42);
let v2 = Value::from(-41);
assert_val!(f(&[&v1, &v2]), -42);
let v = Value::from(1);
assert_val!(f(&[&v]), 0);
assert!(f(&[]).ok().map(|v| v.is_i64()).unwrap_or_default());
let null = Value::from(0);
assert!(f(&[&null]).is_err());
}
proptest! {
#[test]
fn integer_single_arg_no_error(x in 1_u64..u64::MAX) { let f = fun("random", "integer");
let v = Value::from(x);
assert!(f(&[&v]).is_ok());
}
}
#[test]
fn float() {
let f = fun("random", "float");
let v1 = 0.0;
let v2 = 100.0;
assert!(f(&[&Value::from(v1), &Value::from(v2)])
.ok()
.as_f64()
.map(|a| a >= v1 && a < v2)
.unwrap_or_default());
let v = 100.0;
assert!(f(&[&Value::from(v)])
.ok()
.as_f64()
.map(|a| a >= 0.0 && a < v)
.unwrap_or_default());
let null = Value::from(0.0);
assert!(f(&[&null]).is_err());
assert!(f(&[])
.ok()
.as_f64()
.map(|a| (0.0..1.0).contains(&a))
.unwrap_or_default());
}
proptest! {
#[test]
fn float_single_arg_no_error(x in (0.0_f64..f64::MAX).prop_filter("Values must be > 0.0", |x| *x > 0.0)) {
let f = fun("random", "float");
let v = Value::from(x);
assert!(f(&[&v]).is_ok());
}
}
}