#[macro_export]
macro_rules! id {
($name:ident) => {
$crate::id!($name, $crate::global::get_default_config());
};
($name:ident, $config:expr) => {
$crate::__paste! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct $name(u64);
pub struct [<$name Generator>] {
inner: $crate::generator::Generator,
}
impl [<$name Generator>] {
pub fn generate(&self) -> $name {
let id = self.inner.generate();
$name(id)
}
pub fn worker_id(&self) -> u64 {
self.inner.worker_id()
}
pub fn process_id(&self) -> u64 {
self.inner.process_id()
}
}
impl $name {
fn context() -> &'static $crate::context::IdContext {
static CONTEXT: std::sync::OnceLock<$crate::context::IdContext> =
std::sync::OnceLock::new();
CONTEXT.get_or_init(|| $crate::context::IdContext::new($config))
}
pub fn generate() -> Self {
let id = Self::context().default_generator().generate();
Self(id)
}
pub fn instance(worker_id: u64, process_id: u64) -> Result<[<$name Generator>], $crate::config::ValidationError> {
let inner = Self::context().create_generator(worker_id, process_id)?;
Ok([<$name Generator>] { inner })
}
pub fn worker(worker_id: u64) -> Result<[<$name Generator>], $crate::config::ValidationError> {
let inner = Self::context().create_worker(worker_id)?;
Ok([<$name Generator>] { inner })
}
pub fn process(process_id: u64) -> Result<[<$name Generator>], $crate::config::ValidationError> {
let inner = Self::context().create_process(process_id)?;
Ok([<$name Generator>] { inner })
}
pub fn decompose(self) -> (u64, u64, u64, u64) {
Self::context().default_generator().decompose(self.0)
}
pub fn components(self) -> $crate::generator::IdComponents {
Self::context().default_generator().components(self.0)
}
pub fn timestamp(self) -> u64 {
Self::context().default_generator().extract_timestamp(self.0)
}
pub fn worker_id(self) -> u64 {
Self::context().default_generator().extract_worker_id(self.0)
}
pub fn process_id(self) -> u64 {
Self::context().default_generator().extract_process_id(self.0)
}
pub fn sequence(self) -> u64 {
Self::context().default_generator().extract_sequence(self.0)
}
pub fn compose(timestamp: u64, sequence: u64) -> Result<Self, $crate::config::ValidationError> {
let id = Self::context().default_generator().compose(timestamp, sequence)?;
Ok(Self(id))
}
pub fn compose_unchecked(timestamp: u64, sequence: u64) -> Self {
let id = Self::context().default_generator().compose_unchecked(timestamp, sequence);
Self(id)
}
pub fn compose_custom(
timestamp: u64,
worker_id: u64,
process_id: u64,
sequence: u64,
) -> Result<Self, $crate::config::ValidationError> {
let id = Self::context().default_generator().compose_custom(timestamp, worker_id, process_id, sequence)?;
Ok(Self(id))
}
pub fn compose_custom_unchecked(timestamp: u64, worker_id: u64, process_id: u64, sequence: u64) -> Self {
let id = Self::context().default_generator().compose_custom_unchecked(timestamp, worker_id, process_id, sequence);
Self(id)
}
}
impl $name {
pub fn from_u64_unchecked(id: u64) -> Self {
Self(id)
}
pub fn try_from_u64(id: u64) -> Result<Self, $crate::config::ValidationError> {
Self::context().config().validate_id(id)?;
Ok(Self(id))
}
pub fn as_u64(self) -> u64 {
self.0
}
}
impl std::fmt::Display for $name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl std::convert::TryFrom<u64> for $name {
type Error = $crate::config::ValidationError;
fn try_from(id: u64) -> Result<Self, Self::Error> {
Self::try_from_u64(id)
}
}
impl From<$name> for u64 {
fn from(id: $name) -> u64 {
id.0
}
}
impl std::str::FromStr for $name {
type Err = $crate::config::ValidationError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let id = s.parse::<u64>()?;
Self::try_from_u64(id)
}
}
$crate::__impl_serde_for_id!($name);
}
};
}
#[cfg(feature = "serde")]
#[macro_export]
#[doc(hidden)]
macro_rules! __impl_serde_for_id {
($name:ident) => {
impl $crate::__serde::Serialize for $name {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: $crate::__serde::Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
impl<'de> $crate::__serde::Deserialize<'de> for $name {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: $crate::__serde::Deserializer<'de>,
{
String::deserialize(deserializer)?
.parse()
.map_err($crate::__serde::de::Error::custom)
}
}
};
}
#[cfg(not(feature = "serde"))]
#[macro_export]
#[doc(hidden)]
macro_rules! __impl_serde_for_id {
($name:ident) => {};
}
#[cfg(test)]
mod tests {
use crate::{BitLayout, Config, Epoch};
#[test]
fn macro_generates_default_config() {
crate::id!(TestId);
let id1 = TestId::generate();
let id2 = TestId::generate();
assert_ne!(id1, id2);
assert!(id1.as_u64() > 0);
assert!(id2.as_u64() > 0);
let components = id1.components();
assert_eq!(components.worker_id, 0);
assert_eq!(components.process_id, 0);
}
#[test]
fn macro_with_custom_config() {
const CUSTOM_CONFIG: Config =
Config::new_unchecked(BitLayout::new(42, 8, 4, 10), Epoch::new(1_600_000_000_000));
crate::id!(CustomId, CUSTOM_CONFIG);
let id = CustomId::generate();
let components = id.components();
assert_eq!(components.worker_id, 0);
assert_eq!(components.process_id, 0);
let instance = CustomId::instance(50, 5).unwrap();
let instance_id = instance.generate();
let instance_components = instance_id.components();
assert_eq!(instance_components.worker_id, 50);
assert_eq!(instance_components.process_id, 5);
}
#[test]
fn compose_decompose_roundtrip() {
crate::id!(ComposeId);
let timestamp = 123456789;
let sequence = 100;
let id = ComposeId::compose(timestamp, sequence).unwrap();
let (dec_timestamp, dec_worker_id, dec_process_id, dec_sequence) = id.decompose();
assert_eq!(dec_timestamp, timestamp);
assert_eq!(dec_worker_id, 0);
assert_eq!(dec_process_id, 0);
assert_eq!(dec_sequence, sequence);
let id_custom = ComposeId::compose_custom(timestamp, 5, 3, sequence).unwrap();
let (ts, w, p, s) = id_custom.decompose();
assert_eq!(ts, timestamp);
assert_eq!(w, 5);
assert_eq!(p, 3);
assert_eq!(s, sequence);
}
#[test]
fn extract_individual_components() {
crate::id!(ComponentId);
let id = ComponentId::generate();
let timestamp = id.timestamp();
let worker_id = id.worker_id();
let process_id = id.process_id();
let sequence = id.sequence();
let components = id.components();
assert_eq!(timestamp, components.timestamp);
assert_eq!(worker_id, components.worker_id);
assert_eq!(process_id, components.process_id);
assert_eq!(sequence, components.sequence);
}
#[test]
fn u64_conversions() {
crate::id!(ConversionId);
let id = ConversionId::generate();
let raw = id.as_u64();
let id2 = ConversionId::from_u64_unchecked(raw);
assert_eq!(id, id2);
let id3: ConversionId = raw.try_into().unwrap();
assert_eq!(id, id3);
let raw2: u64 = id.into();
assert_eq!(raw, raw2);
}
#[test]
fn string_display_and_parsing() {
crate::id!(ParseId);
let id = ParseId::generate();
let id_str = id.to_string();
let parsed_id: ParseId = id_str.parse().unwrap();
assert_eq!(id, parsed_id);
}
#[test]
fn multiple_types_independent() {
crate::id!(UserId);
crate::id!(OrderId);
let user_id1 = UserId::generate();
let user_id2 = UserId::generate();
let order_id1 = OrderId::generate();
let order_id2 = OrderId::generate();
assert_ne!(user_id1, user_id2);
assert_ne!(order_id1, order_id2);
assert!(user_id1.as_u64() > 0);
assert!(order_id1.as_u64() > 0);
assert!(user_id1.as_u64() != user_id2.as_u64());
assert!(order_id1.as_u64() != order_id2.as_u64());
}
#[test]
fn instance_convenience_methods() {
const CUSTOM_ALGORITHM: Config =
Config::new_unchecked(BitLayout::new(42, 8, 4, 10), Epoch::new(1_600_000_000_000));
crate::id!(InstanceMethodId, CUSTOM_ALGORITHM);
let worker_instance = InstanceMethodId::worker(50).unwrap();
let worker_id = worker_instance.generate();
let worker_components = worker_id.components();
assert_eq!(worker_components.worker_id, 50);
assert_eq!(worker_components.process_id, 0);
let process_instance = InstanceMethodId::process(5).unwrap();
let process_id = process_instance.generate();
let process_components = process_id.components();
assert_eq!(process_components.worker_id, 0);
assert_eq!(process_components.process_id, 5);
let full_instance = InstanceMethodId::instance(50, 5).unwrap();
let full_id = full_instance.generate();
let full_components = full_id.components();
assert_eq!(full_components.worker_id, 50);
assert_eq!(full_components.process_id, 5);
}
#[test]
fn multiple_instances_independent_state() {
crate::id!(MultiInstanceId);
let instance1 = MultiInstanceId::instance(10, 5).unwrap();
let instance2 = MultiInstanceId::instance(1, 2).unwrap();
let id1 = instance1.generate();
let id2 = instance2.generate();
let components1 = id1.components();
let components2 = id2.components();
assert_eq!(components1.worker_id, 10);
assert_eq!(components1.process_id, 5);
assert_eq!(components2.worker_id, 1);
assert_eq!(components2.process_id, 2);
}
#[test]
fn generator_with_bound_ids() {
crate::id!(FactoryTestId);
let stateful_gen = FactoryTestId::instance(15, 7).unwrap();
assert_eq!(stateful_gen.worker_id(), 15);
assert_eq!(stateful_gen.process_id(), 7);
let id1 = stateful_gen.generate();
let id2 = stateful_gen.generate();
let components1 = id1.components();
let components2 = id2.components();
assert_eq!(components1.worker_id, 15);
assert_eq!(components1.process_id, 7);
assert_eq!(components2.worker_id, 15);
assert_eq!(components2.process_id, 7);
assert_ne!(id1, id2);
}
#[test]
fn try_from_u64_validates_components() {
const CUSTOM_CONFIG: Config = Config::new_unchecked(
BitLayout::new(42, 8, 4, 10), Epoch::new(1_600_000_000_000),
);
crate::id!(ValidationTestId, CUSTOM_CONFIG);
let valid_id = ValidationTestId::generate();
let raw = valid_id.as_u64();
assert!(ValidationTestId::try_from_u64(raw).is_ok());
}
#[test]
fn try_from_trait_validates() {
crate::id!(TryFromTestId);
let valid_id = TryFromTestId::generate();
let raw = valid_id.as_u64();
let converted: Result<TryFromTestId, _> = raw.try_into();
assert!(converted.is_ok());
assert_eq!(converted.unwrap(), valid_id);
}
#[test]
fn compose_validates_components() {
const CUSTOM_CONFIG: Config =
Config::new_unchecked(BitLayout::new(42, 8, 4, 10), Epoch::new(1_600_000_000_000));
crate::id!(ComposeValidationId, CUSTOM_CONFIG);
let result = ComposeValidationId::compose_custom(1000, 100, 5, 500);
assert!(result.is_ok());
let result = ComposeValidationId::compose_custom(1000, 256, 5, 500);
assert!(result.is_err());
assert!(matches!(
result,
Err(crate::config::ValidationError::WorkerIdOutOfRange { .. })
));
let result = ComposeValidationId::compose_custom(1000, 100, 16, 500);
assert!(result.is_err());
assert!(matches!(
result,
Err(crate::config::ValidationError::ProcessIdOutOfRange { .. })
));
let result = ComposeValidationId::compose_custom(1000, 100, 5, 1024);
assert!(result.is_err());
assert!(matches!(
result,
Err(crate::config::ValidationError::SequenceOutOfRange { .. })
));
let result = ComposeValidationId::compose_custom(1u64 << 42, 100, 5, 500);
assert!(result.is_err());
assert!(matches!(
result,
Err(crate::config::ValidationError::TimestampOutOfRange { .. })
));
let result = ComposeValidationId::compose(1000, 500);
assert!(result.is_ok());
let result = ComposeValidationId::compose(1u64 << 42, 500);
assert!(result.is_err());
let result = ComposeValidationId::compose(1000, 1024);
assert!(result.is_err());
}
#[test]
fn compose_unchecked_masks_overflow() {
const CUSTOM_CONFIG: Config =
Config::new_unchecked(BitLayout::new(42, 8, 4, 10), Epoch::new(1_600_000_000_000));
crate::id!(UncheckedComposeId, CUSTOM_CONFIG);
let id = UncheckedComposeId::compose_custom_unchecked(1u64 << 42, 256, 16, 1024);
let components = id.components();
assert_eq!(components.timestamp, 0); assert_eq!(components.worker_id, 0); assert_eq!(components.process_id, 0); assert_eq!(components.sequence, 0);
let id2 = UncheckedComposeId::compose_unchecked(1u64 << 42, 1024);
let comp2 = id2.components();
assert_eq!(comp2.timestamp, 0);
assert_eq!(comp2.sequence, 0);
}
#[test]
fn from_str_validates() {
const CUSTOM_CONFIG: Config =
Config::new_unchecked(BitLayout::new(42, 8, 4, 10), Epoch::new(1_600_000_000_000));
crate::id!(ParseValidationId, CUSTOM_CONFIG);
let valid_id = ParseValidationId::generate();
let id_str = valid_id.to_string();
let parsed: Result<ParseValidationId, _> = id_str.parse();
assert!(parsed.is_ok());
assert_eq!(parsed.unwrap(), valid_id);
let result: Result<ParseValidationId, _> = "not_a_number".parse();
assert!(result.is_err());
assert!(matches!(
result,
Err(crate::config::ValidationError::StringParseError(_))
));
}
#[test]
fn from_u64_unchecked_no_validation() {
const CUSTOM_CONFIG: Config =
Config::new_unchecked(BitLayout::new(42, 8, 4, 10), Epoch::new(1_600_000_000_000));
crate::id!(UncheckedFromId, CUSTOM_CONFIG);
let layout = CUSTOM_CONFIG.layout();
let invalid_value = 256u64 << layout.worker_shift();
let id = UncheckedFromId::from_u64_unchecked(invalid_value);
assert_eq!(id.as_u64(), invalid_value);
}
}