use std::fmt;
use std::hash::Hash;
use std::str::FromStr;
pub trait EntityIdType:
Copy + Clone + Eq + Hash + fmt::Debug + fmt::Display + FromStr + Send + Sync + 'static
{
const ENTITY_NAME: &'static str;
fn new(id: u128) -> Self;
fn as_u128(&self) -> u128;
fn now_v7() -> Self;
fn now_v7_with_clock(clock: &dyn crate::store::Clock) -> Self {
Self::new(generate_v7_id_with_clock(clock))
}
fn nil() -> Self;
}
pub fn generate_v7_id() -> u128 {
uuid::Uuid::now_v7().as_u128()
}
pub fn generate_v7_id_with_clock(clock: &dyn crate::store::Clock) -> u128 {
let timestamp_us = clock.now_us().max(0);
let seconds = timestamp_us / 1_000_000;
let subsec_nanos = (timestamp_us % 1_000_000) * 1_000;
let seconds = u64::try_from(seconds).unwrap_or(u64::MAX);
let subsec_nanos = u32::try_from(subsec_nanos).unwrap_or(u32::MAX);
uuid::Uuid::new_v7(uuid::Timestamp::from_unix(
uuid::NoContext,
seconds,
subsec_nanos,
))
.as_u128()
}
#[macro_export]
macro_rules! define_entity_id {
($name:ident, $entity:literal, serde) => {
$crate::define_entity_id!($name, $entity);
impl ::serde::Serialize for $name {
fn serialize<S: ::serde::Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
$crate::wire::u128_bytes::serialize(&self.0, ser)
}
}
impl<'de> ::serde::Deserialize<'de> for $name {
fn deserialize<D: ::serde::Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
$crate::wire::u128_bytes::deserialize(de).map(Self)
}
}
};
($name:ident, $entity:literal) => {
#[doc = concat!("Typed entity ID for `", $entity, "` entities. Wraps a `u128` UUIDv7.")]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct $name(u128);
impl $name {
#[doc = concat!("Construct a `", stringify!($name), "` from a raw `u128` in a const context.")]
#[must_use]
pub const fn from_u128(id: u128) -> Self {
Self(id)
}
}
impl $crate::id::EntityIdType for $name {
const ENTITY_NAME: &'static str = $entity;
fn new(id: u128) -> Self {
Self(id)
}
fn as_u128(&self) -> u128 {
self.0
}
fn now_v7() -> Self {
Self($crate::id::generate_v7_id())
}
fn now_v7_with_clock(clock: &dyn $crate::store::Clock) -> Self {
Self($crate::id::generate_v7_id_with_clock(clock))
}
fn nil() -> Self {
Self(0)
}
}
impl ::std::fmt::Display for $name {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
write!(f, "{}:{:032x}", $entity, self.0)
}
}
impl ::std::str::FromStr for $name {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let hex = s.strip_prefix(concat!($entity, ":")).ok_or_else(|| {
format!(
"invalid {}: missing entity prefix '{}:' in {s:?}",
$entity, $entity
)
})?;
u128::from_str_radix(hex, 16)
.map(Self)
.map_err(|e| format!("invalid {}: {e}", $entity))
}
}
impl ::core::convert::From<u128> for $name {
fn from(id: u128) -> Self {
<Self as $crate::id::EntityIdType>::new(id)
}
}
impl ::core::convert::From<$name> for u128 {
fn from(id: $name) -> Self {
<$name as $crate::id::EntityIdType>::as_u128(&id)
}
}
};
}
define_entity_id!(EventId, "event", serde);
define_entity_id!(CorrelationId, "correlation", serde);
define_entity_id!(CausationId, "causation", serde);
define_entity_id!(IdempotencyKey, "idempotency", serde);
impl IdempotencyKey {
#[must_use]
pub fn for_operation(domain: &str, components: &[&str]) -> Self {
let mut hasher = blake3::Hasher::new();
hasher.update(&(domain.len() as u64).to_le_bytes());
hasher.update(domain.as_bytes());
hasher.update(&(components.len() as u64).to_le_bytes());
for component in components {
hasher.update(&(component.len() as u64).to_le_bytes());
hasher.update(component.as_bytes());
}
let digest = hasher.finalize();
let mut bytes = [0u8; 16];
bytes.copy_from_slice(&digest.as_bytes()[..16]);
<Self as EntityIdType>::new(u128::from_be_bytes(bytes))
}
}