use std::fmt;
#[derive(
Debug,
Clone,
Copy,
Default,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
serde::Deserialize,
serde::Serialize,
)]
pub struct EpochId(u64);
impl EpochId {
pub const INITIAL: Self = Self(0);
#[must_use]
pub const fn new(value: u64) -> Self {
Self(value)
}
#[must_use]
pub const fn get(self) -> u64 {
self.0
}
#[must_use]
pub const fn next(self) -> Option<Self> {
match self.0.checked_add(1) {
Some(next) => Some(Self(next)),
None => None,
}
}
}
impl From<u64> for EpochId {
fn from(value: u64) -> Self {
Self::new(value)
}
}
impl From<EpochId> for u64 {
fn from(epoch: EpochId) -> Self {
epoch.get()
}
}
impl fmt::Display for EpochId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn epoch_id_initial_starts_at_zero() {
assert_eq!(EpochId::INITIAL.get(), 0);
assert_eq!(EpochId::default(), EpochId::INITIAL);
}
#[test]
fn epoch_id_next_is_monotonic() {
let first = EpochId::INITIAL;
let second = first.next().unwrap();
let third = second.next().unwrap();
assert!(first < second);
assert!(second < third);
assert_eq!(u64::from(third), 2);
}
#[test]
fn epoch_id_next_returns_none_on_overflow() {
assert!(EpochId::new(u64::MAX).next().is_none());
}
#[test]
fn epoch_id_display_uses_raw_value() {
assert_eq!(EpochId::new(42).to_string(), "42");
}
}