1use crate::generator::{Generator, GeneratorKind, RngCore, SeedValue, string};
37use core::range::RangeInclusive;
38use rand::Rng;
39
40pub trait IntoRange<T>: sealed::Sealed<T> {
46 fn into_range(self) -> Option<RangeInclusive<T>>;
48}
49
50mod sealed {
51 pub trait Sealed<T> {}
52}
53
54macro_rules! integer_ranges {
55 ($($ty:ty),*) => {$(
56 impl sealed::Sealed<$ty> for core::ops::Range<$ty> {}
57 impl IntoRange<$ty> for core::ops::Range<$ty> {
58 fn into_range(self) -> Option<RangeInclusive<$ty>> {
59 (self.start < self.end).then(|| RangeInclusive {
60 start: self.start,
61 last: self.end - 1,
62 })
63 }
64 }
65 impl sealed::Sealed<$ty> for core::range::Range<$ty> {}
66 impl IntoRange<$ty> for core::range::Range<$ty> {
67 fn into_range(self) -> Option<RangeInclusive<$ty>> {
68 core::ops::Range::from(self).into_range()
69 }
70 }
71 impl sealed::Sealed<$ty> for core::ops::RangeInclusive<$ty> {}
72 impl IntoRange<$ty> for core::ops::RangeInclusive<$ty> {
73 fn into_range(self) -> Option<RangeInclusive<$ty>> {
74 (!self.is_empty()).then(|| self.into())
75 }
76 }
77 impl sealed::Sealed<$ty> for RangeInclusive<$ty> {}
78 impl IntoRange<$ty> for RangeInclusive<$ty> {
79 fn into_range(self) -> Option<RangeInclusive<$ty>> {
80 (self.start <= self.last).then_some(self)
81 }
82 }
83 )*};
84}
85
86integer_ranges!(i64, usize);
87
88impl sealed::Sealed<f64> for core::ops::RangeInclusive<f64> {}
89impl IntoRange<f64> for core::ops::RangeInclusive<f64> {
90 fn into_range(self) -> Option<RangeInclusive<f64>> {
91 RangeInclusive::from(self).into_range()
92 }
93}
94impl sealed::Sealed<f64> for RangeInclusive<f64> {}
95impl IntoRange<f64> for RangeInclusive<f64> {
96 fn into_range(self) -> Option<RangeInclusive<f64>> {
97 (self.start.is_finite() && self.last.is_finite() && self.start <= self.last).then_some(self)
98 }
99}
100
101pub trait GeneratorExt: Generator + Sized {
103 fn nullable(self, probability: f64) -> Nullable<Self> {
110 assert!(
111 (0.0..=1.0).contains(&probability),
112 "nullable probability must be between 0.0 and 1.0, got {probability}"
113 );
114 Nullable {
115 inner: self,
116 probability,
117 }
118 }
119
120 fn map<F, V>(self, f: F) -> Map<Self, F>
122 where
123 F: Fn(SeedValue) -> V + Send + Sync,
124 V: Into<SeedValue>,
125 {
126 Map { inner: self, f }
127 }
128}
129
130impl<G: Generator> GeneratorExt for G {}
131
132#[derive(Debug, Clone, Copy)]
134pub struct Int {
135 range: RangeInclusive<i64>,
136}
137
138#[must_use]
144pub fn int(range: impl IntoRange<i64>) -> Int {
145 Int {
146 range: range.into_range().expect("int range is empty"),
147 }
148}
149
150impl Generator for Int {
151 fn generate(&self, rng: &mut dyn RngCore, _index: usize, _sql_type: &str) -> SeedValue {
152 SeedValue::Integer(rng.random_range(self.range.start..=self.range.last))
153 }
154
155 fn name(&self) -> &'static str {
156 "int"
157 }
158}
159
160#[derive(Debug, Clone, Copy)]
162pub struct Float {
163 range: RangeInclusive<f64>,
164 decimals: Option<u32>,
165}
166
167#[must_use]
173pub fn float(range: impl IntoRange<f64>) -> Float {
174 Float {
175 range: range
176 .into_range()
177 .expect("float range must be finite and non-empty"),
178 decimals: None,
179 }
180}
181
182impl Float {
183 #[must_use]
186 pub const fn decimals(mut self, decimals: u32) -> Self {
187 self.decimals = Some(decimals);
188 self
189 }
190}
191
192impl Generator for Float {
193 fn generate(&self, rng: &mut dyn RngCore, _index: usize, _sql_type: &str) -> SeedValue {
194 let value = rng.random_range(self.range.start..=self.range.last);
195 let value = match self.decimals {
196 Some(decimals) => {
197 let factor = 10f64.powi(i32::try_from(decimals).unwrap_or(i32::MAX));
198 ((value * factor).round() / factor).clamp(self.range.start, self.range.last)
199 }
200 None => value,
201 };
202 SeedValue::Float(value)
203 }
204
205 fn name(&self) -> &'static str {
206 "float"
207 }
208}
209
210#[derive(Debug, Clone, Copy)]
212pub struct Boolean {
213 probability_true: f64,
214}
215
216#[must_use]
222pub fn boolean(probability_true: f64) -> Boolean {
223 assert!(
224 (0.0..=1.0).contains(&probability_true),
225 "boolean probability must be between 0.0 and 1.0, got {probability_true}"
226 );
227 Boolean { probability_true }
228}
229
230impl Generator for Boolean {
231 fn generate(&self, rng: &mut dyn RngCore, _index: usize, _sql_type: &str) -> SeedValue {
232 SeedValue::Bool(rng.random_bool(self.probability_true))
233 }
234
235 fn name(&self) -> &'static str {
236 "boolean"
237 }
238}
239
240#[derive(Debug, Clone)]
242pub struct OneOf {
243 values: Vec<SeedValue>,
244}
245
246#[must_use]
253pub fn one_of<T: Into<SeedValue>>(values: impl IntoIterator<Item = T>) -> OneOf {
254 let values: Vec<SeedValue> = values.into_iter().map(Into::into).collect();
255 assert!(!values.is_empty(), "one_of needs at least one value");
256 OneOf { values }
257}
258
259impl Generator for OneOf {
260 fn generate(&self, rng: &mut dyn RngCore, _index: usize, _sql_type: &str) -> SeedValue {
261 self.values[rng.random_range(0..self.values.len())].clone()
262 }
263
264 fn name(&self) -> &'static str {
265 "one_of"
266 }
267}
268
269#[derive(Debug, Clone)]
271pub struct Weighted {
272 values: Vec<SeedValue>,
273 cumulative: Vec<u64>,
274}
275
276#[must_use]
283pub fn weighted<T: Into<SeedValue>>(values: impl IntoIterator<Item = (T, u32)>) -> Weighted {
284 let mut total = 0u64;
285 let (values, cumulative) = values
286 .into_iter()
287 .map(|(value, weight)| {
288 total += u64::from(weight);
289 (value.into(), total)
290 })
291 .unzip();
292 assert!(total > 0, "weighted needs at least one non-zero weight");
293 Weighted { values, cumulative }
294}
295
296impl Generator for Weighted {
297 fn generate(&self, rng: &mut dyn RngCore, _index: usize, _sql_type: &str) -> SeedValue {
298 let total = *self.cumulative.last().unwrap_or(&1);
299 let pick = rng.random_range(0..total);
300 let index = self.cumulative.partition_point(|&end| end <= pick);
301 self.values[index].clone()
302 }
303
304 fn name(&self) -> &'static str {
305 "weighted"
306 }
307}
308
309#[derive(Debug, Clone)]
311pub struct Constant {
312 value: SeedValue,
313}
314
315#[must_use]
317pub fn constant(value: impl Into<SeedValue>) -> Constant {
318 Constant {
319 value: value.into(),
320 }
321}
322
323impl Generator for Constant {
324 fn generate(&self, _rng: &mut dyn RngCore, _index: usize, _sql_type: &str) -> SeedValue {
325 self.value.clone()
326 }
327
328 fn name(&self) -> &'static str {
329 "constant"
330 }
331}
332
333#[derive(Debug, Clone, Copy)]
335pub struct Sequence {
336 start: i64,
337 step: i64,
338}
339
340#[must_use]
343pub const fn sequence(start: i64) -> Sequence {
344 Sequence { start, step: 1 }
345}
346
347impl Sequence {
348 #[must_use]
350 pub const fn step(mut self, step: i64) -> Self {
351 self.step = step;
352 self
353 }
354}
355
356impl Generator for Sequence {
357 fn generate(&self, _rng: &mut dyn RngCore, index: usize, _sql_type: &str) -> SeedValue {
358 let index = i64::try_from(index).unwrap_or(i64::MAX);
359 SeedValue::Integer(self.start.saturating_add(index.saturating_mul(self.step)))
360 }
361
362 fn name(&self) -> &'static str {
363 "sequence"
364 }
365}
366
367#[derive(Debug, Clone, Copy)]
369pub struct Text {
370 length: RangeInclusive<usize>,
371}
372
373#[must_use]
380pub fn text(length: impl IntoRange<usize>) -> Text {
381 Text {
382 length: length.into_range().expect("text length range is empty"),
383 }
384}
385
386impl Generator for Text {
387 fn generate(&self, rng: &mut dyn RngCore, index: usize, sql_type: &str) -> SeedValue {
388 string::TextGen {
389 min_len: self.length.start,
390 max_len: self.length.last,
391 }
392 .generate(rng, index, sql_type)
393 }
394
395 fn name(&self) -> &'static str {
396 "text"
397 }
398}
399
400#[derive(Debug, Clone, Copy)]
402pub struct Words {
403 count: RangeInclusive<usize>,
404}
405
406#[must_use]
413pub fn words(count: impl IntoRange<usize>) -> Words {
414 Words {
415 count: count.into_range().expect("words count range is empty"),
416 }
417}
418
419impl Generator for Words {
420 fn generate(&self, rng: &mut dyn RngCore, index: usize, sql_type: &str) -> SeedValue {
421 let words = rng.random_range(self.count.start..=self.count.last);
422 string::LoremGen { words }.generate(rng, index, sql_type)
423 }
424
425 fn name(&self) -> &'static str {
426 "words"
427 }
428}
429
430pub struct FromFn<F> {
432 f: F,
433}
434
435#[must_use]
442pub fn from_fn<F, V>(f: F) -> FromFn<F>
443where
444 F: Fn(&mut dyn RngCore, usize) -> V + Send + Sync,
445 V: Into<SeedValue>,
446{
447 FromFn { f }
448}
449
450impl<F, V> Generator for FromFn<F>
451where
452 F: Fn(&mut dyn RngCore, usize) -> V + Send + Sync,
453 V: Into<SeedValue>,
454{
455 fn generate(&self, rng: &mut dyn RngCore, index: usize, _sql_type: &str) -> SeedValue {
456 (self.f)(rng, index).into()
457 }
458
459 fn name(&self) -> &'static str {
460 "from_fn"
461 }
462}
463
464pub struct Nullable<G> {
467 inner: G,
468 probability: f64,
469}
470
471impl<G: Generator> Generator for Nullable<G> {
472 fn generate(&self, rng: &mut dyn RngCore, index: usize, sql_type: &str) -> SeedValue {
473 if rng.random_bool(self.probability) {
474 SeedValue::Null
475 } else {
476 self.inner.generate(rng, index, sql_type)
477 }
478 }
479
480 fn name(&self) -> &'static str {
481 self.inner.name()
482 }
483}
484
485pub struct Map<G, F> {
487 inner: G,
488 f: F,
489}
490
491impl<G, F, V> Generator for Map<G, F>
492where
493 G: Generator,
494 F: Fn(SeedValue) -> V + Send + Sync,
495 V: Into<SeedValue>,
496{
497 fn generate(&self, rng: &mut dyn RngCore, index: usize, sql_type: &str) -> SeedValue {
498 (self.f)(self.inner.generate(rng, index, sql_type)).into()
499 }
500
501 fn name(&self) -> &'static str {
502 self.inner.name()
503 }
504}
505
506macro_rules! kind_constructors {
507 ($($(#[$doc:meta])* $name:ident => $kind:ident,)*) => {$(
508 $(#[$doc])*
509 #[must_use]
510 pub const fn $name() -> GeneratorKind {
511 GeneratorKind::$kind
512 }
513 )*};
514}
515
516kind_constructors! {
517 email => Email,
520 first_name => FirstName,
522 last_name => LastName,
524 full_name => FullName,
526 phone => Phone,
528 city => City,
530 country => Country,
532 address => Address,
534 company => Company,
536 job_title => JobTitle,
538 title => Title,
540 url => Url,
542 username => Username,
544 slug => Slug,
546 uuid => Uuid,
548 json => Json,
550 date => Date,
553 timestamp => Timestamp,
556 time => Time,
558}
559
560#[cfg(test)]
561mod tests {
562 use super::*;
563 use rand::SeedableRng;
564 use rand::rngs::StdRng;
565
566 fn values(generator: &impl Generator, rows: usize) -> Vec<SeedValue> {
567 let mut rng = StdRng::seed_from_u64(7);
568 (0..rows)
569 .map(|index| generator.generate(&mut rng, index, "TEXT"))
570 .collect()
571 }
572
573 #[test]
574 fn ranges_are_respected() {
575 assert!(
576 values(&int(-3..=3), 200)
577 .iter()
578 .all(|value| matches!(value, SeedValue::Integer(-3..=3)))
579 );
580 assert!(
581 values(&float(1.0..=2.0).decimals(1), 200)
582 .iter()
583 .all(|value| {
584 matches!(value, SeedValue::Float(number) if (1.0..=2.0).contains(number)
585 && (number * 10.0).fract() == 0.0)
586 })
587 );
588 assert!(values(&text(2..=4), 200).iter().all(|value| {
589 matches!(value, SeedValue::Text(text) if (2..=4).contains(&text.len()))
590 }));
591 }
592
593 #[test]
594 fn exclusive_inclusive_and_core_range_forms_agree() {
595 let exclusive = values(&int(0..3), 200);
596 assert_eq!(exclusive, values(&int(0..=2), 200));
597 assert_eq!(
598 exclusive,
599 values(&int(core::range::Range { start: 0, end: 3 }), 200)
600 );
601 assert_eq!(
602 exclusive,
603 values(&int(core::range::RangeInclusive { start: 0, last: 2 }), 200)
604 );
605 assert_eq!(values(&text(3..4), 20), values(&text(3..=3), 20));
606 }
607
608 #[test]
609 #[should_panic(expected = "int range is empty")]
610 fn empty_ranges_are_rejected() {
611 let _ = int(5..5);
612 }
613
614 #[test]
615 fn choices_and_weights() {
616 let picked = values(&one_of(["a", "b"]), 200);
617 assert!(picked.contains(&SeedValue::from("a")) && picked.contains(&SeedValue::from("b")));
618
619 let picked = values(&weighted([("never", 0), ("always", 3)]), 100);
620 assert!(
621 picked
622 .iter()
623 .all(|value| *value == SeedValue::from("always"))
624 );
625 }
626
627 #[test]
628 fn sequence_constant_and_closures() {
629 assert_eq!(
630 values(&sequence(10).step(5), 3),
631 [10, 15, 20].map(SeedValue::from)
632 );
633 assert_eq!(
634 values(&constant(true), 2),
635 [true, true].map(SeedValue::from)
636 );
637 assert_eq!(
638 values(&from_fn(|_rng, index| index as i64 * 2), 3),
639 [0, 2, 4].map(SeedValue::from)
640 );
641 }
642
643 #[test]
644 fn combinators() {
645 let mixed = values(&int(1..=1).nullable(0.5), 200);
646 assert!(mixed.contains(&SeedValue::Null) && mixed.contains(&SeedValue::Integer(1)));
647 assert!(
648 values(&int(1..=1).nullable(0.0), 50)
649 .iter()
650 .all(|value| *value == SeedValue::Integer(1))
651 );
652 let mapped = values(
653 &first_name().map(|value| match value {
654 SeedValue::Text(name) => name.to_uppercase(),
655 _ => unreachable!(),
656 }),
657 5,
658 );
659 assert!(mapped.iter().all(|value| matches!(
660 value,
661 SeedValue::Text(name) if name.chars().all(|c| !c.is_lowercase())
662 )));
663 }
664}