use std::fmt;
use std::sync::LazyLock;
use crate::desired_state::ids::{InvalidId, Uuid7, Uuid7Generator};
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct RequestId(Uuid7);
impl RequestId {
pub const PREFIX: &'static str = "req_";
pub const fn new(uuid: Uuid7) -> Self {
Self(uuid)
}
#[cfg(test)]
pub(crate) const fn uuid(&self) -> Uuid7 {
self.0
}
pub fn parse(text: &str) -> Result<Self, InvalidId> {
let uuid = text
.strip_prefix(Self::PREFIX)
.ok_or_else(|| InvalidId::Prefix {
expected: Self::PREFIX,
found: text.to_owned(),
})?;
Ok(Self(Uuid7::parse(uuid)?))
}
}
impl fmt::Debug for RequestId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
impl fmt::Display for RequestId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}{}", Self::PREFIX, self.0)
}
}
static REQUEST_IDS: LazyLock<Uuid7Generator> = LazyLock::new(Uuid7Generator::new);
pub fn next_request_id() -> RequestId {
RequestId::new(REQUEST_IDS.next())
}
#[derive(Debug, Clone)]
pub struct EventIdentity {
pub request_id: RequestId,
pub trace_id: Option<String>,
}
impl EventIdentity {
pub fn capture() -> Self {
Self {
request_id: next_request_id(),
trace_id: crate::telemetry::trace_id(),
}
}
}
#[cfg(test)]
mod tests {
use std::collections::BTreeSet;
use super::*;
#[test]
fn a_minted_id_is_a_uuid7_behind_the_req_prefix() {
let id = next_request_id();
let text = id.to_string();
assert!(text.starts_with("req_"), "{text}");
assert_eq!(text.len(), 4 + 36, "{text}");
assert_eq!(id.uuid().as_bytes()[6] >> 4, 7, "version bits");
assert_eq!(id.uuid().as_bytes()[8] >> 6, 0b10, "variant bits");
assert_eq!(RequestId::parse(&text).expect("round trip"), id);
}
#[test]
fn ids_are_unique_and_sort_in_mint_order() {
let minted: Vec<RequestId> = (0..10_000).map(|_| next_request_id()).collect();
let distinct: BTreeSet<RequestId> = minted.iter().copied().collect();
assert_eq!(distinct.len(), minted.len(), "ids must not repeat");
assert!(
minted.windows(2).all(|pair| pair[0] < pair[1]),
"ids must sort in mint order"
);
let text: Vec<String> = minted.iter().map(RequestId::to_string).collect();
assert!(text.windows(2).all(|pair| pair[0] < pair[1]));
}
#[test]
fn an_id_carries_the_millisecond_it_was_minted_in() {
let before = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.expect("after the epoch")
.as_millis() as u64;
assert!(next_request_id().uuid().timestamp_millis() >= before.saturating_sub(1));
}
#[test]
fn a_counter_era_id_is_not_an_event_identity() {
assert!(RequestId::parse("req_0000000000000001").is_err());
assert!(RequestId::parse("0192f5e1-2b3c-7def-8123-456789abcdef").is_err());
assert!(
RequestId::parse("req_0192f5e1-2b3c-4def-8123-456789abcdef").is_err(),
"a v4 uuid is not time-ordered and must not pass as one"
);
}
}