coven/
id_provider.rs

1//! Identifier source, injected so tests get a deterministic — but still
2//! unique — id sequence.
3//!
4//! Production wires [`UuidProvider`] (`Uuid::new_v4().to_string()`); tests
5//! construct a [`SequentialIdProvider`] and pass it to the unit under test.
6//!
7//! Each `new_id()` call yields a fresh, distinct id. Per-entity uniqueness is
8//! preserved: a loop minting one id per row keeps producing distinct ids under
9//! both the production and the test provider.
10
11use std::sync::Arc;
12use uuid::Uuid;
13
14/// Identifier source. Yields a fresh unique id per call.
15pub trait IdProvider: Send + Sync {
16    /// A fresh unique identifier as a string (matches every call site, which
17    /// does `Uuid::new_v4().to_string()` today).
18    fn new_id(&self) -> String;
19}
20
21/// Shared handle to an id provider. Held by `Clone` types (`LibraryManager`)
22/// so they clone the handle, not the implementation.
23pub type IdRef = Arc<dyn IdProvider>;
24
25/// Production provider: random v4 UUIDs.
26pub struct UuidProvider;
27
28impl IdProvider for UuidProvider {
29    fn new_id(&self) -> String {
30        Uuid::new_v4().to_string()
31    }
32}
33
34// The deterministic id fake is exposed to downstream crates' tests via the
35// `test-utils` feature, so any crate that consumes `IdProvider` tests against
36// the same fake instead of mirroring it.
37#[cfg(any(test, feature = "test-utils"))]
38pub use fakes::SequentialIdProvider;
39
40#[cfg(any(test, feature = "test-utils"))]
41mod fakes {
42    use super::*;
43    use std::sync::atomic::{AtomicU64, Ordering};
44
45    /// Deterministic but unique: `"{prefix}-0"`, `"{prefix}-1"`, ... Preserves
46    /// the per-entity uniqueness invariant while being reproducible across runs.
47    pub struct SequentialIdProvider {
48        prefix: String,
49        next: AtomicU64,
50    }
51
52    impl SequentialIdProvider {
53        pub fn new(prefix: &str) -> Self {
54            Self {
55                prefix: prefix.to_string(),
56                next: AtomicU64::new(0),
57            }
58        }
59    }
60
61    impl IdProvider for SequentialIdProvider {
62        fn new_id(&self) -> String {
63            let n = self.next.fetch_add(1, Ordering::SeqCst);
64            format!("{}-{}", self.prefix, n)
65        }
66    }
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72
73    #[test]
74    fn sequential_ids_are_deterministic_and_unique() {
75        let ids = SequentialIdProvider::new("id");
76        assert_eq!(ids.new_id(), "id-0");
77        assert_eq!(ids.new_id(), "id-1");
78        assert_eq!(ids.new_id(), "id-2");
79    }
80
81    #[test]
82    fn provider_is_usable_behind_the_shared_handle() {
83        let ids: IdRef = Arc::new(SequentialIdProvider::new("row"));
84        assert_eq!(ids.new_id(), "row-0");
85        assert_ne!(ids.new_id(), ids.new_id());
86    }
87}