use parking_lot::Mutex;
use std::sync::atomic::{AtomicU64, Ordering};
use uuid::Uuid;
pub trait UuidProvider: Send + Sync {
fn new_v4(&self) -> Uuid;
fn is_mock(&self) -> bool;
}
pub struct RealUuid;
impl RealUuid {
pub fn new() -> Self {
Self
}
}
impl Default for RealUuid {
fn default() -> Self {
Self::new()
}
}
impl UuidProvider for RealUuid {
fn new_v4(&self) -> Uuid {
Uuid::new_v4()
}
fn is_mock(&self) -> bool {
false
}
}
pub struct MockUuid {
mode: MockUuidMode,
}
enum MockUuidMode {
Sequential(AtomicU64),
Predetermined(Mutex<Vec<Uuid>>),
}
impl MockUuid {
pub fn sequential() -> Self {
Self {
mode: MockUuidMode::Sequential(AtomicU64::new(1)),
}
}
pub fn sequential_from(start: u64) -> Self {
Self {
mode: MockUuidMode::Sequential(AtomicU64::new(start)),
}
}
pub fn predetermined(uuids: Vec<Uuid>) -> Self {
let mut reversed = uuids;
reversed.reverse();
Self {
mode: MockUuidMode::Predetermined(Mutex::new(reversed)),
}
}
pub fn from_strings(uuids: &[&str]) -> Self {
let parsed: Vec<Uuid> = uuids
.iter()
.map(|s| Uuid::parse_str(s).expect("Invalid UUID string"))
.collect();
Self::predetermined(parsed)
}
pub fn current_counter(&self) -> Option<u64> {
match &self.mode {
MockUuidMode::Sequential(counter) => Some(counter.load(Ordering::SeqCst)),
MockUuidMode::Predetermined(_) => None,
}
}
}
impl UuidProvider for MockUuid {
fn new_v4(&self) -> Uuid {
match &self.mode {
MockUuidMode::Sequential(counter) => {
let n = counter.fetch_add(1, Ordering::SeqCst);
let bytes: [u8; 16] = [
0,
0,
0,
0,
0,
0,
0,
0,
(n >> 56) as u8,
(n >> 48) as u8,
(n >> 40) as u8,
(n >> 32) as u8,
(n >> 24) as u8,
(n >> 16) as u8,
(n >> 8) as u8,
n as u8,
];
Uuid::from_bytes(bytes)
}
MockUuidMode::Predetermined(uuids) => {
let mut guard = uuids.lock();
if let Some(uuid) = guard.pop() {
uuid
} else {
let n = guard.len() as u64 + 1;
drop(guard);
let bytes: [u8; 16] = [
0,
0,
0,
0,
0,
0,
0,
0,
(n >> 56) as u8,
(n >> 48) as u8,
(n >> 40) as u8,
(n >> 32) as u8,
(n >> 24) as u8,
(n >> 16) as u8,
(n >> 8) as u8,
n as u8,
];
Uuid::from_bytes(bytes)
}
}
}
}
fn is_mock(&self) -> bool {
true
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sequential_uuids() {
let provider = MockUuid::sequential();
let id1 = provider.new_v4();
let id2 = provider.new_v4();
let id3 = provider.new_v4();
assert_eq!(id1.to_string(), "00000000-0000-0000-0000-000000000001");
assert_eq!(id2.to_string(), "00000000-0000-0000-0000-000000000002");
assert_eq!(id3.to_string(), "00000000-0000-0000-0000-000000000003");
}
#[test]
fn sequential_from() {
let provider = MockUuid::sequential_from(100);
let id = provider.new_v4();
assert_eq!(id.to_string(), "00000000-0000-0000-0000-000000000064"); }
#[test]
fn predetermined_uuids() {
let id1 = Uuid::parse_str("12345678-1234-1234-1234-123456789abc").unwrap();
let id2 = Uuid::parse_str("abcdefab-cdef-abcd-efab-cdefabcdefab").unwrap();
let provider = MockUuid::predetermined(vec![id1, id2]);
assert_eq!(provider.new_v4(), id1);
assert_eq!(provider.new_v4(), id2);
}
#[test]
fn from_strings() {
let provider = MockUuid::from_strings(&[
"11111111-1111-1111-1111-111111111111",
"22222222-2222-2222-2222-222222222222",
]);
assert_eq!(
provider.new_v4().to_string(),
"11111111-1111-1111-1111-111111111111"
);
assert_eq!(
provider.new_v4().to_string(),
"22222222-2222-2222-2222-222222222222"
);
}
#[test]
fn real_uuid_is_random() {
let provider = RealUuid::new();
let id1 = provider.new_v4();
let id2 = provider.new_v4();
assert_ne!(id1, id2);
}
}