use crate::cnf::ID_CHARS;
use crate::err::Error;
use crate::sql::uuid::Uuid;
use crate::sql::value::Value;
use crate::sql::{Datetime, Number};
use chrono::{TimeZone, Utc};
use nanoid::nanoid;
use rand::distributions::{Alphanumeric, DistString};
use rand::prelude::IteratorRandom;
use rand::Rng;
use ulid::Ulid;
pub fn rand(_: ()) -> Result<Value, Error> {
Ok(rand::random::<f64>().into())
}
pub fn bool(_: ()) -> Result<Value, Error> {
Ok(rand::random::<bool>().into())
}
pub fn r#enum(mut args: Vec<Value>) -> Result<Value, Error> {
Ok(match args.len() {
0 => Value::None,
1 => match args.remove(0) {
Value::Array(v) => v.into_iter().choose(&mut rand::thread_rng()).unwrap_or(Value::None),
v => v,
},
_ => args.into_iter().choose(&mut rand::thread_rng()).unwrap(),
})
}
pub fn float((range,): (Option<(f64, f64)>,)) -> Result<Value, Error> {
Ok(if let Some((min, max)) = range {
if max < min {
rand::thread_rng().gen_range(max..=min)
} else {
rand::thread_rng().gen_range(min..=max)
}
} else {
rand::random::<f64>()
}
.into())
}
pub fn guid((arg1, arg2): (Option<i64>, Option<i64>)) -> Result<Value, Error> {
const LIMIT: i64 = 64;
let val = if let Some((min, max)) = arg1.zip(arg2) {
match min {
min if (1..=LIMIT).contains(&min) => match max {
max if min <= max && max <= LIMIT => rand::thread_rng().gen_range(min as usize..=max as usize),
max if max >= 1 && max <= min => rand::thread_rng().gen_range(max as usize..=min as usize),
_ => return Err(Error::InvalidArguments {
name: String::from("rand::guid"),
message: format!("To generate a guid of between X and Y characters in length, the 2 arguments must be positive numbers and no higher than {LIMIT}."),
}),
},
_ => return Err(Error::InvalidArguments {
name: String::from("rand::guid"),
message: format!("To generate a string of between X and Y characters in length, the 2 arguments must be positive numbers and no higher than {LIMIT}."),
}),
}
} else if let Some(len) = arg1 {
if (1..=LIMIT).contains(&len) {
len as usize
} else {
return Err(Error::InvalidArguments {
name: String::from("rand::guid"),
message: format!("To generate a string of X characters in length, the argument must be a positive number and no higher than {LIMIT}."),
});
}
} else {
20
};
Ok(nanoid!(val, &ID_CHARS).into())
}
pub fn int((range,): (Option<(i64, i64)>,)) -> Result<Value, Error> {
Ok(if let Some((min, max)) = range {
if max < min {
rand::thread_rng().gen_range(max..=min)
} else {
rand::thread_rng().gen_range(min..=max)
}
} else {
rand::random::<i64>()
}
.into())
}
pub fn string((arg1, arg2): (Option<i64>, Option<i64>)) -> Result<Value, Error> {
const LIMIT: i64 = 65536;
let val = if let Some((min, max)) = arg1.zip(arg2) {
match min {
min if (1..=LIMIT).contains(&min) => match max {
max if min <= max && max <= LIMIT => rand::thread_rng().gen_range(min as usize..=max as usize),
max if max >= 1 && max <= min => rand::thread_rng().gen_range(max as usize..=min as usize),
_ => return Err(Error::InvalidArguments {
name: String::from("rand::string"),
message: format!("To generate a string of between X and Y characters in length, the 2 arguments must be positive numbers and no higher than {LIMIT}."),
}),
},
_ => return Err(Error::InvalidArguments {
name: String::from("rand::string"),
message: format!("To generate a string of between X and Y characters in length, the 2 arguments must be positive numbers and no higher than {LIMIT}."),
}),
}
} else if let Some(len) = arg1 {
if (1..=LIMIT).contains(&len) {
len as usize
} else {
return Err(Error::InvalidArguments {
name: String::from("rand::string"),
message: format!("To generate a string of X characters in length, the argument must be a positive number and no higher than {LIMIT}."),
});
}
} else {
32
};
Ok(Alphanumeric.sample_string(&mut rand::thread_rng(), val).into())
}
pub fn time((range,): (Option<(Value, Value)>,)) -> Result<Value, Error> {
let range = match range {
None => None,
Some((Value::Number(Number::Int(min)), Value::Number(Number::Int(max)))) => {
Some((min, max))
}
Some((Value::Datetime(min), Value::Datetime(max))) => match (min.to_i64(), max.to_i64()) {
(Some(min), Some(max)) => Some((min, max)),
_ => {
return Err(Error::InvalidArguments {
name: String::from("rand::time"),
message: String::from("Failed to convert datetime arguments to i64 timestamps"),
})
}
},
_ => {
return Err(Error::InvalidArguments {
name: String::from("rand::time"),
message: String::from(
"Expected an optional pair of datetimes or pair of i64 numbers to be passed",
),
})
}
};
const LIMIT: i64 = 8210298412799;
let val = if let Some((min, max)) = range {
match min {
min if (1..=LIMIT).contains(&min) => match max {
max if min <= max && max <= LIMIT => rand::thread_rng().gen_range(min..=max),
max if max >= 1 && max <= min => rand::thread_rng().gen_range(max..=min),
_ => return Err(Error::InvalidArguments {
name: String::from("rand::time"),
message: format!("To generate a time between X and Y seconds, the 2 arguments must be positive numbers and no higher than {LIMIT}."),
}),
},
_ => return Err(Error::InvalidArguments {
name: String::from("rand::time"),
message: format!("To generate a time between X and Y seconds, the 2 arguments must be positive numbers and no higher than {LIMIT}."),
}),
}
} else {
rand::thread_rng().gen_range(0..=LIMIT)
};
for _ in 0..5 {
if let Some(v) = Utc.timestamp_opt(val, 0).earliest() {
return Ok(v.into());
}
}
Err(fail!("Expected a valid datetime, but were unable to generate one"))
}
pub fn ulid((timestamp,): (Option<Datetime>,)) -> Result<Value, Error> {
let ulid = match timestamp {
Some(timestamp) => {
#[cfg(target_family = "wasm")]
if timestamp.0 < chrono::DateTime::UNIX_EPOCH {
return Err(Error::InvalidArguments {
name: String::from("rand::ulid"),
message: format!(
"To generate a ULID from a datetime, it must be a time beyond UNIX epoch."
),
});
}
Ulid::from_datetime(timestamp.0.into())
}
None => Ulid::new(),
};
Ok(ulid.to_string().into())
}
pub fn uuid((timestamp,): (Option<Datetime>,)) -> Result<Value, Error> {
let uuid = match timestamp {
Some(timestamp) => {
#[cfg(target_family = "wasm")]
if timestamp.0 < chrono::DateTime::UNIX_EPOCH {
return Err(Error::InvalidArguments {
name: String::from("rand::ulid"),
message: format!(
"To generate a ULID from a datetime, it must be a time beyond UNIX epoch."
),
});
}
Uuid::new_v7_from_datetime(timestamp)
}
None => Uuid::new(),
};
Ok(uuid.into())
}
pub mod uuid {
use crate::err::Error;
use crate::sql::uuid::Uuid;
use crate::sql::value::Value;
use crate::sql::Datetime;
pub fn v4(_: ()) -> Result<Value, Error> {
Ok(Uuid::new_v4().into())
}
pub fn v7((timestamp,): (Option<Datetime>,)) -> Result<Value, Error> {
let uuid = match timestamp {
Some(timestamp) => {
#[cfg(target_family = "wasm")]
if timestamp.0 < chrono::DateTime::UNIX_EPOCH {
return Err(Error::InvalidArguments {
name: String::from("rand::ulid"),
message: format!(
"To generate a ULID from a datetime, it must be a time beyond UNIX epoch."
),
});
}
Uuid::new_v7_from_datetime(timestamp)
}
None => Uuid::new(),
};
Ok(uuid.into())
}
}