omni_dev/voice/det.rs
1//! Pluggable RNG for [`EventId`](super::EventId) (ULID) generation.
2//!
3//! Lives at `voice/` scope rather than under `backends/mock.rs` because
4//! the same trait is consumed by [`voice reflect`](super::reflect) for
5//! deterministic event minting in snapshot tests. Production code uses
6//! [`SystemUlidRng`]; tests use [`CountingUlidRng`].
7
8use ulid::Ulid;
9
10/// Source of [`Ulid`]s. Pluggable so tests can pin ULIDs while production
11/// uses real entropy.
12pub trait UlidRng: Send + Sync {
13 /// Returns the next ULID. Each call must produce a value strictly
14 /// greater than the previous one to preserve event-id monotonicity.
15 fn next_ulid(&mut self) -> Ulid;
16}
17
18/// Production RNG: defers to `Ulid::new()`.
19#[derive(Debug, Default)]
20pub struct SystemUlidRng;
21
22impl UlidRng for SystemUlidRng {
23 fn next_ulid(&mut self) -> Ulid {
24 Ulid::new()
25 }
26}
27
28/// Deterministic RNG for tests.
29///
30/// Returns `Ulid::from_parts(0, counter)` for an increasing counter
31/// starting at 1. The encoded form is lexicographically ordered, so a
32/// sequence of ULIDs from this RNG is monotonically increasing — exactly
33/// the property snapshot tests rely on.
34#[derive(Debug, Default)]
35pub struct CountingUlidRng {
36 counter: u128,
37}
38
39impl CountingUlidRng {
40 /// Builds a new counting RNG starting at zero — the first ULID it
41 /// returns has random bits `1`, the second `2`, and so on.
42 pub fn new() -> Self {
43 Self { counter: 0 }
44 }
45}
46
47impl UlidRng for CountingUlidRng {
48 fn next_ulid(&mut self) -> Ulid {
49 self.counter += 1;
50 Ulid::from_parts(0, self.counter)
51 }
52}
53
54#[cfg(test)]
55mod tests {
56 use super::*;
57
58 #[test]
59 fn system_ulid_rng_produces_unique_values() {
60 let mut rng = SystemUlidRng;
61 let a = rng.next_ulid();
62 let b = rng.next_ulid();
63 assert_ne!(a, b);
64 }
65
66 #[test]
67 fn counting_ulid_rng_starts_at_one() {
68 let mut rng = CountingUlidRng::new();
69 let first = rng.next_ulid();
70 assert_eq!(first, Ulid::from_parts(0, 1));
71 }
72
73 #[test]
74 fn counting_ulid_rng_is_monotonic() {
75 let mut rng = CountingUlidRng::new();
76 let a = rng.next_ulid();
77 let b = rng.next_ulid();
78 let c = rng.next_ulid();
79 assert!(a < b);
80 assert!(b < c);
81 }
82}