#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(docsrs, feature(doc_cfg))]
mod global_gen;
#[cfg(feature = "global_gen")]
pub use global_gen::{new, new_string};
pub mod id;
pub use id::Id;
#[doc(hidden)]
pub use id::{Id as Scru128Id, ParseError};
pub mod generator;
pub use generator::Generator;
#[doc(hidden)]
pub use generator::{self as r#gen, Generator as Scru128Generator};
const MAX_TIMESTAMP: u64 = 0xffff_ffff_ffff;
const MAX_COUNTER_HI: u32 = 0xff_ffff;
const MAX_COUNTER_LO: u32 = 0xff_ffff;
#[cfg(all(test, feature = "std"))]
mod tests {
use std::{collections, sync, time};
use crate::{Generator, Id};
static SAMPLES: sync::LazyLock<Vec<String>> = sync::LazyLock::new(|| {
Generator::for_testing()
.iter()
.map(String::from)
.take(100_000)
.collect()
});
#[test]
fn generates_25_digit_canonical_string() {
let re = regex::Regex::new(r"^[0-9a-z]{25}$").unwrap();
for e in &SAMPLES[..] {
assert!(re.is_match(e));
}
}
#[test]
fn generates_100k_identifiers_without_collision() {
let s: collections::HashSet<&String> = SAMPLES.iter().collect();
assert_eq!(s.len(), SAMPLES.len());
}
#[test]
fn generates_sortable_string_representation_by_creation_time() {
for i in 1..SAMPLES.len() {
assert!(SAMPLES[i - 1] < SAMPLES[i]);
}
}
#[test]
fn encodes_up_to_date_timestamp() {
let mut g = Generator::for_testing();
for _ in 0..10_000 {
let ts_now = (time::SystemTime::now()
.duration_since(time::UNIX_EPOCH)
.expect("clock may have gone backwards")
.as_millis()) as i64;
let timestamp = g.generate().timestamp() as i64;
assert!((ts_now - timestamp).abs() < 16);
}
}
#[test]
fn encodes_unique_sortable_tuple_of_timestamp_and_counters() {
let mut prev = SAMPLES[0].parse::<Id>().unwrap();
for e in &SAMPLES[1..] {
let curr = e.parse::<Id>().unwrap();
assert!(
prev.timestamp() < curr.timestamp()
|| (prev.timestamp() == curr.timestamp()
&& prev.counter_hi() < curr.counter_hi())
|| (prev.timestamp() == curr.timestamp()
&& prev.counter_hi() == curr.counter_hi()
&& prev.counter_lo() < curr.counter_lo())
);
prev = curr;
}
}
}