use rand::{RngExt, distr::Alphanumeric, prelude::IndexedRandom};
use wae_types::{WaeError, WaeErrorKind, WaeResult as TestingResult};
pub trait Fixture: Sized {
fn generate() -> TestingResult<Self>;
}
pub trait FixtureBuilder<T>: Sized {
fn build(self) -> TestingResult<T>;
}
#[derive(Debug, Clone)]
pub struct RandomString {
pub length: usize,
pub prefix: Option<String>,
pub suffix: Option<String>,
}
impl Default for RandomString {
fn default() -> Self {
Self { length: 10, prefix: None, suffix: None }
}
}
impl RandomString {
pub fn new() -> Self {
Self::default()
}
pub fn length(mut self, len: usize) -> Self {
self.length = len;
self
}
pub fn prefix(mut self, prefix: impl Into<String>) -> Self {
self.prefix = Some(prefix.into());
self
}
pub fn suffix(mut self, suffix: impl Into<String>) -> Self {
self.suffix = Some(suffix.into());
self
}
pub fn generate(&self) -> TestingResult<String> {
let mut rng = rand::rng();
let random_part: String = (0..self.length).map(|_| rng.sample(Alphanumeric) as char).collect();
let mut result = String::new();
if let Some(ref prefix) = self.prefix {
result.push_str(prefix);
}
result.push_str(&random_part);
if let Some(ref suffix) = self.suffix {
result.push_str(suffix);
}
Ok(result)
}
}
impl Fixture for String {
fn generate() -> TestingResult<Self> {
RandomString::new().generate()
}
}
#[derive(Debug, Clone)]
pub struct RandomNumber<T> {
pub min: T,
pub max: T,
_marker: std::marker::PhantomData<T>,
}
impl<T> RandomNumber<T> {
pub fn new(min: T, max: T) -> Self {
Self { min, max, _marker: std::marker::PhantomData }
}
}
impl RandomNumber<i32> {
pub fn generate(&self) -> TestingResult<i32> {
let mut rng = rand::rng();
Ok(rng.random_range(self.min..=self.max))
}
}
impl RandomNumber<i64> {
pub fn generate(&self) -> TestingResult<i64> {
let mut rng = rand::rng();
Ok(rng.random_range(self.min..=self.max))
}
}
impl RandomNumber<u32> {
pub fn generate(&self) -> TestingResult<u32> {
let mut rng = rand::rng();
Ok(rng.random_range(self.min..=self.max))
}
}
impl RandomNumber<u64> {
pub fn generate(&self) -> TestingResult<u64> {
let mut rng = rand::rng();
Ok(rng.random_range(self.min..=self.max))
}
}
impl RandomNumber<f64> {
pub fn generate(&self) -> TestingResult<f64> {
let mut rng = rand::rng();
Ok(rng.random_range(self.min..=self.max))
}
}
impl Fixture for i32 {
fn generate() -> TestingResult<Self> {
let generator: RandomNumber<i32> = RandomNumber::new(0, 1000000);
generator.generate()
}
}
impl Fixture for u64 {
fn generate() -> TestingResult<Self> {
RandomNumber::new(0u64, 1000000u64).generate()
}
}
#[derive(Debug, Clone)]
pub struct RandomEmail {
pub domain: String,
}
impl Default for RandomEmail {
fn default() -> Self {
Self { domain: "test.example.com".to_string() }
}
}
impl RandomEmail {
pub fn new() -> Self {
Self::default()
}
pub fn domain(mut self, domain: impl Into<String>) -> Self {
self.domain = domain.into();
self
}
pub fn generate(&self) -> TestingResult<String> {
let username = RandomString::new().length(8).generate()?;
Ok(format!("{}@{}", username, self.domain))
}
}
#[derive(Debug, Clone, Default)]
pub struct RandomUuid {
pub version4: bool,
}
impl RandomUuid {
pub fn new() -> Self {
Self { version4: true }
}
pub fn version7(mut self) -> Self {
self.version4 = false;
self
}
pub fn generate(&self) -> TestingResult<uuid::Uuid> {
if self.version4 { Ok(uuid::Uuid::new_v4()) } else { Ok(uuid::Uuid::now_v7()) }
}
pub fn generate_string(&self) -> TestingResult<String> {
Ok(self.generate()?.to_string())
}
}
impl Fixture for uuid::Uuid {
fn generate() -> TestingResult<Self> {
RandomUuid::new().generate()
}
}
#[derive(Debug, Clone, Default)]
pub struct RandomBool {
pub true_probability: f64,
}
impl RandomBool {
pub fn new() -> Self {
Self { true_probability: 0.5 }
}
pub fn probability(mut self, prob: f64) -> Self {
self.true_probability = prob.clamp(0.0, 1.0);
self
}
pub fn generate(&self) -> TestingResult<bool> {
let mut rng = rand::rng();
Ok(rng.random_bool(self.true_probability))
}
}
impl Fixture for bool {
fn generate() -> TestingResult<Self> {
RandomBool::new().generate()
}
}
#[derive(Debug, Clone)]
pub struct RandomChoice<T> {
pub items: Vec<T>,
}
impl<T: Clone> RandomChoice<T> {
pub fn new(items: Vec<T>) -> Self {
Self { items }
}
pub fn generate(&self) -> TestingResult<T> {
let mut rng = rand::rng();
self.items
.choose(&mut rng)
.cloned()
.ok_or_else(|| WaeError::new(WaeErrorKind::FixtureError { reason: "No items to choose from".to_string() }))
}
}
#[derive(Debug, Clone)]
pub struct RandomDateTime {
pub start_timestamp: i64,
pub end_timestamp: i64,
}
impl Default for RandomDateTime {
fn default() -> Self {
Self { start_timestamp: 0, end_timestamp: 4102444800 }
}
}
impl RandomDateTime {
pub fn new() -> Self {
Self::default()
}
pub fn range(mut self, start: i64, end: i64) -> Self {
self.start_timestamp = start;
self.end_timestamp = end;
self
}
pub fn generate_timestamp(&self) -> TestingResult<i64> {
let mut rng = rand::rng();
Ok(rng.random_range(self.start_timestamp..=self.end_timestamp))
}
}
pub struct FixtureGenerator;
impl FixtureGenerator {
pub fn strings(count: usize, length: usize) -> TestingResult<Vec<String>> {
(0..count).map(|_| RandomString::new().length(length).generate()).collect()
}
pub fn emails(count: usize, domain: &str) -> TestingResult<Vec<String>> {
(0..count).map(|_| RandomEmail::new().domain(domain).generate()).collect()
}
pub fn uuids(count: usize) -> TestingResult<Vec<uuid::Uuid>> {
(0..count).map(|_| RandomUuid::new().generate()).collect()
}
pub fn i32_numbers(count: usize, min: i32, max: i32) -> TestingResult<Vec<i32>> {
let mut rng = rand::rng();
(0..count).map(|_| Ok(rng.random_range(min..=max))).collect()
}
pub fn u64_numbers(count: usize, min: u64, max: u64) -> TestingResult<Vec<u64>> {
let mut rng = rand::rng();
(0..count).map(|_| Ok(rng.random_range(min..=max))).collect()
}
}