Skip to main content

keelson_factory/
source.rs

1use std::fmt;
2use std::sync::Arc;
3
4use keelson_models::Set;
5
6use crate::Faker;
7
8/// One column of a factory template: where the value comes from.
9///
10/// A template field holds a `Source<T>`, and mods rewrite it —
11/// `fac::users::id(10)` sets [`Source::Value`], `fac::users::random_id()`
12/// sets a [`Source::Gen`]. At build time [`resolve`](Source::resolve) turns
13/// the source into the three-state [`Set`] the model's `Setter` wants, with
14/// the template's own default rule filling [`Auto`](Source::Auto).
15///
16/// Why five states where `Set` has three: a template needs to distinguish
17/// "let the template's default rule decide" ([`Auto`](Source::Auto)) from
18/// "leave the column out of the statement" ([`Omit`](Source::Omit)) — in a
19/// `Setter` those collapse into `Unset`, but in a factory the first means
20/// "sequence/random/whatever the spec says" and the second means "the
21/// database default, explicitly".
22pub enum Source<T> {
23    /// The template's default rule decides — a sequence value for a unique
24    /// column, a random value for a data column, omission for a column whose
25    /// database default is the point.
26    Auto,
27    /// Exactly this value.
28    Value(T),
29    /// SQL `NULL`, explicitly.
30    Null,
31    /// Leave the column out of the statement — the database default applies.
32    Omit,
33    /// A caller-supplied generator, drawing from the run's [`Faker`] — so a
34    /// custom random source is still covered by the determinism switch.
35    Gen(Arc<dyn Fn(&mut Faker) -> T + Send + Sync>),
36}
37
38impl<T> Source<T> {
39    /// A generated value: `Source::from_fn(|f| f.i64_in(1, 1000))`.
40    pub fn from_fn(g: impl Fn(&mut Faker) -> T + Send + Sync + 'static) -> Self {
41        Source::Gen(Arc::new(g))
42    }
43}
44
45impl<T: Clone> Source<T> {
46    /// The [`Set`] this source contributes, with `auto` — the template's
47    /// per-column default rule — deciding [`Auto`](Source::Auto). `auto`
48    /// returns a `Set` rather than a `T` so a rule can itself be "omit"
49    /// (a column whose database default is the right test value).
50    pub fn resolve(&self, f: &mut Faker, auto: impl FnOnce(&mut Faker) -> Set<T>) -> Set<T> {
51        match self {
52            Source::Auto => auto(f),
53            Source::Value(v) => Set::Value(v.clone()),
54            Source::Null => Set::Null,
55            Source::Omit => Set::Unset,
56            Source::Gen(g) => Set::Value(g(f)),
57        }
58    }
59}
60
61// Manual, not `#[derive(Default)]`: the derive would bound `T: Default`,
62// which `Auto` does not need.
63#[allow(clippy::derivable_impls)]
64impl<T> Default for Source<T> {
65    fn default() -> Self {
66        Source::Auto
67    }
68}
69
70impl<T: Clone> Clone for Source<T> {
71    fn clone(&self) -> Self {
72        match self {
73            Source::Auto => Source::Auto,
74            Source::Value(v) => Source::Value(v.clone()),
75            Source::Null => Source::Null,
76            Source::Omit => Source::Omit,
77            Source::Gen(g) => Source::Gen(Arc::clone(g)),
78        }
79    }
80}
81
82impl<T: fmt::Debug> fmt::Debug for Source<T> {
83    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84        match self {
85            Source::Auto => f.write_str("Auto"),
86            Source::Value(v) => f.debug_tuple("Value").field(v).finish(),
87            Source::Null => f.write_str("Null"),
88            Source::Omit => f.write_str("Omit"),
89            Source::Gen(_) => f.write_str("Gen(..)"),
90        }
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97
98    fn faker() -> Faker {
99        Faker::seeded(0)
100    }
101
102    #[test]
103    fn auto_defers_to_the_default_rule_in_both_directions() {
104        let s = Source::<i64>::Auto;
105        assert_eq!(s.resolve(&mut faker(), |_| Set::Value(7)), Set::Value(7));
106        assert_eq!(s.resolve(&mut faker(), |_| Set::Unset), Set::Unset);
107    }
108
109    #[test]
110    fn value_null_and_omit_map_onto_the_setter_states() {
111        assert_eq!(
112            Source::Value(3i64).resolve(&mut faker(), |_| Set::Unset),
113            Set::Value(3)
114        );
115        assert_eq!(
116            Source::<i64>::Null.resolve(&mut faker(), |_| Set::Value(1)),
117            Set::Null
118        );
119        assert_eq!(
120            Source::<i64>::Omit.resolve(&mut faker(), |_| Set::Value(1)),
121            Set::Unset
122        );
123    }
124
125    #[test]
126    fn gen_draws_from_the_faker_so_the_seed_covers_it() {
127        let s = Source::from_fn(|f: &mut Faker| f.i64_in(0, 1_000_000));
128        let a = s.resolve(&mut Faker::seeded(9), |_| Set::Unset);
129        let b = s.clone().resolve(&mut Faker::seeded(9), |_| Set::Unset);
130        assert_eq!(a, b, "same seed, same generated value");
131        assert!(matches!(a, Set::Value(_)));
132    }
133
134    #[test]
135    fn debug_names_the_variant_without_requiring_the_closure_to() {
136        assert_eq!(format!("{:?}", Source::<i64>::Auto), "Auto");
137        assert_eq!(
138            format!("{:?}", Source::from_fn(|f: &mut Faker| f.next_u64())),
139            "Gen(..)"
140        );
141    }
142}