use crate::{Epoch, LOGICAL_MAX, PHYSICAL_MS_MAX, Timestamp};
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct PhysicalMs(u64);
impl PhysicalMs {
pub const MAX: PhysicalMs = PhysicalMs(PHYSICAL_MS_MAX);
pub const ZERO: PhysicalMs = PhysicalMs(0);
pub const fn try_new(value: u64) -> Result<Self, CoreError> {
if value > PHYSICAL_MS_MAX {
return Err(CoreError::PhysicalMsOutOfRange(value));
}
Ok(PhysicalMs(value))
}
pub const fn get(self) -> u64 {
self.0
}
}
impl TryFrom<u64> for PhysicalMs {
type Error = CoreError;
fn try_from(value: u64) -> Result<Self, Self::Error> {
Self::try_new(value)
}
}
impl core::fmt::Display for PhysicalMs {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
core::fmt::Display::fmt(&self.0, f)
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct WindowGrant {
physical_ms: u64,
logical_start: u32,
count: u32,
epoch: Epoch,
}
impl WindowGrant {
pub fn try_new(
physical_ms: u64,
logical_start: u32,
count: u32,
epoch: Epoch,
) -> Result<Self, CoreError> {
if count == 0 {
return Err(CoreError::InvalidCount(0));
}
let last_logical =
logical_start
.checked_add(count - 1)
.ok_or(CoreError::LogicalRangeOutOfRange {
logical_start,
count,
})?;
Timestamp::try_pack(physical_ms, last_logical).map_err(|err| match err {
crate::TimestampError::PhysicalMsOutOfRange { physical_ms, .. } => {
CoreError::PhysicalMsOutOfRange(physical_ms)
}
crate::TimestampError::LogicalOutOfRange { .. } => CoreError::LogicalRangeOutOfRange {
logical_start,
count,
},
})?;
Ok(WindowGrant {
physical_ms,
logical_start,
count,
epoch,
})
}
pub(crate) fn new_unchecked(
physical_ms: u64,
logical_start: u32,
count: u32,
epoch: Epoch,
) -> Self {
debug_assert!(count != 0);
debug_assert!(physical_ms <= PHYSICAL_MS_MAX);
debug_assert!((logical_start as u64) + (count as u64) <= (LOGICAL_MAX as u64) + 1);
WindowGrant {
physical_ms,
logical_start,
count,
epoch,
}
}
pub fn physical_ms(&self) -> u64 {
self.physical_ms
}
pub fn logical_start(&self) -> u32 {
self.logical_start
}
pub fn count(&self) -> u32 {
self.count
}
pub fn epoch(&self) -> Epoch {
self.epoch
}
pub fn first(&self) -> Timestamp {
Timestamp::pack(self.physical_ms, self.logical_start)
}
pub fn last(&self) -> Timestamp {
Timestamp::pack(self.physical_ms, self.logical_start + self.count - 1)
}
}
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum CoreError {
#[error("not leader")]
NotLeader,
#[error("window exhausted; caller must extend before retrying")]
WindowExhausted,
#[error("invalid count: {0}")]
InvalidCount(u32),
#[error("physical_ms {0} exceeds 46-bit maximum")]
PhysicalMsOutOfRange(u64),
#[error("logical range [{logical_start}, +{count}) exceeds the 18-bit logical field")]
LogicalRangeOutOfRange { logical_start: u32, count: u32 },
#[error(
"invalid leadership window: fence_floor {fence_floor} exceeds committed_ceiling {committed_ceiling}"
)]
InvalidLeadershipWindow {
fence_floor: u64,
committed_ceiling: u64,
},
#[error(
"window extension overflow: max(floor {floor}, now_ms {now_ms}) + ahead_ms {ahead_ms} exceeds u64::MAX"
)]
WindowExtensionOverflow {
floor: u64,
now_ms: u64,
ahead_ms: u64,
},
#[error("sequence key must not be empty")]
SeqKeyEmpty,
#[error("sequence key length {len} exceeds maximum {max} bytes")]
SeqKeyTooLong { len: usize, max: usize },
#[error("sequence count must be >= 1")]
SeqCountZero,
#[error("sequence count {count} exceeds maximum {max}")]
SeqCountTooLarge { count: u32, max: u32 },
#[error("sequence block [{start}, {start}+{count}) overflows u64")]
SeqBlockOverflow { start: u64, count: u32 },
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum CommitOutcome {
Applied { high_water: u64 },
Ignored(IgnoreReason),
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum IgnoreReason {
NotLeader,
EpochMismatch { expected: Epoch, current: Epoch },
NotAdvanced { persisted: u64, committed: u64 },
}
#[derive(Debug)]
enum State {
NotLeader,
Leader {
epoch: Epoch,
committed_high_water: u64,
next_physical_ms: u64,
next_logical: u32,
},
}
pub struct Allocator {
state: State,
}
impl Allocator {
pub fn new() -> Self {
Allocator {
state: State::NotLeader,
}
}
pub fn become_leader(
&mut self,
fence_floor: PhysicalMs,
committed_ceiling: PhysicalMs,
epoch: Epoch,
) -> Result<(), CoreError> {
if committed_ceiling < fence_floor {
return Err(CoreError::InvalidLeadershipWindow {
fence_floor: fence_floor.get(),
committed_ceiling: committed_ceiling.get(),
});
}
self.state = State::Leader {
epoch,
committed_high_water: committed_ceiling.get(),
next_physical_ms: fence_floor.get(),
next_logical: 0,
};
Ok(())
}
pub fn step_down(&mut self) {
self.state = State::NotLeader;
}
pub fn is_leader(&self) -> bool {
matches!(self.state, State::Leader { .. })
}
pub fn epoch(&self) -> Option<Epoch> {
match self.state {
State::Leader { epoch, .. } => Some(epoch),
State::NotLeader => None,
}
}
pub fn committed_high_water(&self) -> Option<u64> {
match self.state {
State::Leader {
committed_high_water,
..
} => Some(committed_high_water),
State::NotLeader => None,
}
}
fn validate_count(count: u32) -> Result<(), CoreError> {
if count == 0 || count > LOGICAL_MAX + 1 {
return Err(CoreError::InvalidCount(count));
}
Ok(())
}
fn project_grant(
next_physical_ms: u64,
next_logical: u32,
committed_high_water: u64,
now_ms: u64,
count: u32,
) -> Result<(u64, u32), CoreError> {
let mut physical_ms = next_physical_ms;
let mut logical = next_logical;
if now_ms > physical_ms {
physical_ms = now_ms;
logical = 0;
}
if logical as u64 + count as u64 > LOGICAL_MAX as u64 + 1 {
physical_ms += 1;
logical = 0;
}
if physical_ms > PHYSICAL_MS_MAX {
return Err(CoreError::PhysicalMsOutOfRange(physical_ms));
}
if physical_ms > committed_high_water {
return Err(CoreError::WindowExhausted);
}
Ok((physical_ms, logical))
}
fn advance_cursor(physical_ms: u64, logical_start: u32, count: u32) -> (u64, u32) {
let next_logical = logical_start + count;
if next_logical > LOGICAL_MAX {
(physical_ms + 1, 0)
} else {
(physical_ms, next_logical)
}
}
pub fn try_grant(&mut self, now_ms: u64, count: u32) -> Result<WindowGrant, CoreError> {
Self::validate_count(count)?;
let State::Leader {
epoch,
committed_high_water,
next_physical_ms,
next_logical,
} = &mut self.state
else {
return Err(CoreError::NotLeader);
};
let (physical_ms, logical_start) = Self::project_grant(
*next_physical_ms,
*next_logical,
*committed_high_water,
now_ms,
count,
)?;
let grant = WindowGrant::new_unchecked(physical_ms, logical_start, count, *epoch);
(*next_physical_ms, *next_logical) =
Self::advance_cursor(physical_ms, logical_start, count);
Ok(grant)
}
pub fn would_grant(&self, now_ms: u64, count: u32) -> bool {
if Self::validate_count(count).is_err() {
return false;
}
let State::Leader {
committed_high_water,
next_physical_ms,
next_logical,
..
} = &self.state
else {
return false;
};
Self::project_grant(
*next_physical_ms,
*next_logical,
*committed_high_water,
now_ms,
count,
)
.is_ok()
}
pub fn try_prepare_window_extension(
&self,
now_ms: PhysicalMs,
ahead_ms: u64,
) -> Result<PhysicalMs, CoreError> {
match &self.state {
State::NotLeader => Err(CoreError::NotLeader),
State::Leader {
committed_high_water,
..
} => {
debug_assert!(
*committed_high_water <= PHYSICAL_MS_MAX,
"committed_high_water > PHYSICAL_MS_MAX: \
try_on_leadership_gained / try_commit_window_extension invariant",
);
let floor = *committed_high_water + 1;
let now_ms = now_ms.get();
let requested = core::cmp::max(floor, now_ms).checked_add(ahead_ms).ok_or(
CoreError::WindowExtensionOverflow {
floor,
now_ms,
ahead_ms,
},
)?;
PhysicalMs::try_new(requested)
}
}
}
pub fn try_commit_window_extension(
&mut self,
persisted_high_water: PhysicalMs,
expected_epoch: Epoch,
) -> Result<CommitOutcome, CoreError> {
let persisted_high_water = persisted_high_water.get();
let State::Leader {
epoch,
committed_high_water,
..
} = &mut self.state
else {
return Ok(CommitOutcome::Ignored(IgnoreReason::NotLeader));
};
if *epoch != expected_epoch {
return Ok(CommitOutcome::Ignored(IgnoreReason::EpochMismatch {
expected: expected_epoch,
current: *epoch,
}));
}
if persisted_high_water <= *committed_high_water {
return Ok(CommitOutcome::Ignored(IgnoreReason::NotAdvanced {
persisted: persisted_high_water,
committed: *committed_high_water,
}));
}
*committed_high_water = persisted_high_water;
Ok(CommitOutcome::Applied {
high_water: persisted_high_water,
})
}
}
impl Default for Allocator {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn pms(value: u64) -> PhysicalMs {
PhysicalMs::try_new(value).expect("test literal exceeds PHYSICAL_MS_MAX")
}
#[test]
fn new_allocator_is_not_leader() {
let allocator = Allocator::new();
assert!(!allocator.is_leader());
assert_eq!(allocator.epoch(), None);
}
#[test]
fn become_leader_sets_epoch() {
let mut allocator = Allocator::new();
allocator
.become_leader(pms(1000), pms(5000), Epoch(5))
.unwrap();
assert!(allocator.is_leader());
assert_eq!(allocator.epoch(), Some(Epoch(5)));
}
#[test]
fn become_leader_rejects_inverted_window() {
let mut allocator = Allocator::new();
assert_eq!(
allocator.become_leader(pms(5000), pms(4000), Epoch(5)),
Err(CoreError::InvalidLeadershipWindow {
fence_floor: 5000,
committed_ceiling: 4000
})
);
}
#[test]
fn step_down_clears_state() {
let mut allocator = Allocator::new();
allocator
.become_leader(pms(1000), pms(5000), Epoch(5))
.unwrap();
allocator.step_down();
assert!(!allocator.is_leader());
assert_eq!(allocator.epoch(), None);
}
#[test]
fn committed_high_water_tracks_leader_state_and_extensions() {
let mut allocator = Allocator::new();
assert_eq!(allocator.committed_high_water(), None);
allocator
.become_leader(pms(1_000), pms(5_000), Epoch(1))
.unwrap();
assert_eq!(allocator.committed_high_water(), Some(5_000));
let target = allocator
.try_prepare_window_extension(pms(2_000), 3_000)
.unwrap();
allocator
.try_commit_window_extension(target, Epoch(1))
.unwrap();
assert_eq!(allocator.committed_high_water(), Some(target.get()));
allocator.step_down();
assert_eq!(allocator.committed_high_water(), None);
}
#[test]
fn try_grant_not_leader() {
let mut allocator = Allocator::new();
assert_eq!(allocator.try_grant(1000, 1), Err(CoreError::NotLeader));
}
#[test]
fn try_grant_zero_count() {
let mut allocator = Allocator::new();
allocator
.become_leader(pms(1000), pms(5000), Epoch(1))
.unwrap();
assert_eq!(
allocator.try_grant(1000, 0),
Err(CoreError::InvalidCount(0))
);
}
#[test]
fn try_grant_oversized_count() {
let mut allocator = Allocator::new();
allocator
.become_leader(pms(1000), pms(5000), Epoch(1))
.unwrap();
let oversized = LOGICAL_MAX + 2;
assert_eq!(
allocator.try_grant(1000, oversized),
Err(CoreError::InvalidCount(oversized))
);
}
#[test]
fn try_grant_above_committed_is_window_exhausted() {
let mut allocator = Allocator::new();
allocator
.become_leader(pms(5_000), pms(5_000), Epoch(1))
.unwrap();
allocator.try_grant(4_999, 1).unwrap();
assert_eq!(
allocator.try_grant(5_001, 1),
Err(CoreError::WindowExhausted)
);
}
#[test]
fn failed_try_grant_does_not_advance_state() {
let mut allocator = Allocator::new();
allocator
.become_leader(pms(1_000), pms(1_000), Epoch(1))
.unwrap();
assert_eq!(
allocator.try_grant(5_000, 1),
Err(CoreError::WindowExhausted)
);
let target = allocator
.try_prepare_window_extension(pms(2_000), 0)
.unwrap();
assert_eq!(target, pms(2_000)); allocator
.try_commit_window_extension(target, Epoch(1))
.unwrap();
let grant = allocator.try_grant(2_000, 1).unwrap();
assert_eq!(grant.physical_ms, 2_000);
assert_eq!(grant.logical_start, 0);
}
#[test]
fn try_grant_after_gain_serves_immediately() {
let mut allocator = Allocator::new();
allocator
.become_leader(pms(5_000), pms(10_000), Epoch(1))
.unwrap();
let grant = allocator.try_grant(1_000, 1).unwrap();
assert_eq!(grant.physical_ms, 5_000);
assert_eq!(grant.logical_start, 0);
assert_eq!(grant.epoch, Epoch(1));
}
#[test]
fn prepare_window_extension_not_leader() {
let allocator = Allocator::new();
assert_eq!(
allocator.try_prepare_window_extension(pms(1000), 3000),
Err(CoreError::NotLeader)
);
}
#[test]
fn prepare_window_extension_uses_now_ms_when_ahead_of_high_water() {
let mut allocator = Allocator::new();
allocator
.become_leader(pms(1000), pms(1000), Epoch(1))
.unwrap();
let target = allocator
.try_prepare_window_extension(pms(2000), 3000)
.unwrap();
assert_eq!(target, pms(5000)); }
#[test]
fn prepare_window_extension_uses_high_water_floor_when_clock_behind() {
let mut allocator = Allocator::new();
allocator
.become_leader(pms(10_000), pms(10_000), Epoch(1))
.unwrap();
let target = allocator
.try_prepare_window_extension(pms(500), 3000)
.unwrap();
assert_eq!(target, pms(13_001));
}
#[test]
fn prepare_window_extension_rejects_out_of_range_target() {
let mut allocator = Allocator::new();
allocator
.become_leader(PhysicalMs::MAX, PhysicalMs::MAX, Epoch(1))
.unwrap();
assert_eq!(
allocator.try_prepare_window_extension(PhysicalMs::MAX, 1),
Err(CoreError::PhysicalMsOutOfRange(PHYSICAL_MS_MAX + 2))
);
}
#[test]
fn prepare_window_extension_overflow_names_all_operands() {
let mut allocator = Allocator::new();
allocator
.become_leader(pms(1_000), pms(1_000), Epoch(1))
.unwrap();
assert_eq!(
allocator.try_prepare_window_extension(pms(1_000), u64::MAX),
Err(CoreError::WindowExtensionOverflow {
floor: 1_001,
now_ms: 1_000,
ahead_ms: u64::MAX,
})
);
}
#[test]
fn commit_then_try_grant_succeeds() {
let mut allocator = Allocator::new();
allocator
.become_leader(pms(1000), pms(1000), Epoch(7))
.unwrap();
let target = allocator
.try_prepare_window_extension(pms(1000), 3000)
.unwrap();
assert_eq!(
allocator.try_commit_window_extension(target, Epoch(7)),
Ok(CommitOutcome::Applied {
high_water: target.get()
})
);
let grant = allocator.try_grant(1000, 5).unwrap();
assert_eq!(grant.count, 5);
assert_eq!(grant.logical_start, 0);
assert_eq!(grant.epoch, Epoch(7));
assert!(grant.physical_ms <= target.get());
}
#[test]
fn commit_with_lower_value_is_ignored() {
let mut allocator = Allocator::new();
allocator
.become_leader(pms(1000), pms(1000), Epoch(1))
.unwrap();
assert_eq!(
allocator.try_commit_window_extension(pms(5000), Epoch(1)),
Ok(CommitOutcome::Applied { high_water: 5000 })
);
assert_eq!(
allocator.try_commit_window_extension(pms(3000), Epoch(1)),
Ok(CommitOutcome::Ignored(IgnoreReason::NotAdvanced {
persisted: 3000,
committed: 5000,
}))
);
let grant = allocator.try_grant(4500, 1).unwrap();
assert_eq!(grant.physical_ms, 4500);
}
#[test]
fn commit_with_equal_value_is_ignored_not_applied() {
let mut allocator = Allocator::new();
allocator
.become_leader(pms(1000), pms(5000), Epoch(1))
.unwrap();
assert_eq!(
allocator.try_commit_window_extension(pms(5000), Epoch(1)),
Ok(CommitOutcome::Ignored(IgnoreReason::NotAdvanced {
persisted: 5000,
committed: 5000,
}))
);
}
#[test]
fn try_grant_rejects_out_of_range_clock() {
let mut allocator = Allocator::new();
allocator
.become_leader(pms(1000), PhysicalMs::MAX, Epoch(1))
.unwrap();
assert_eq!(
allocator.try_grant(PHYSICAL_MS_MAX + 1, 1),
Err(CoreError::PhysicalMsOutOfRange(PHYSICAL_MS_MAX + 1))
);
}
#[test]
fn commit_at_wrong_epoch_is_silently_dropped() {
let mut allocator = Allocator::new();
allocator
.become_leader(pms(1000), pms(1000), Epoch(5))
.unwrap();
assert_eq!(
allocator.try_commit_window_extension(pms(9_999), Epoch(4)),
Ok(CommitOutcome::Ignored(IgnoreReason::EpochMismatch {
expected: Epoch(4),
current: Epoch(5),
}))
);
allocator.try_grant(900, 1).unwrap();
assert_eq!(
allocator.try_grant(1_100, 1),
Err(CoreError::WindowExhausted)
);
}
#[test]
fn commit_after_leadership_lost_is_ignored() {
let mut allocator = Allocator::new();
allocator
.become_leader(pms(1000), pms(5000), Epoch(1))
.unwrap();
allocator.step_down();
assert_eq!(
allocator.try_commit_window_extension(pms(9_999), Epoch(1)),
Ok(CommitOutcome::Ignored(IgnoreReason::NotLeader))
);
assert!(!allocator.is_leader());
}
#[test]
fn would_grant_matches_try_grant_outcome() {
let mut allocator = Allocator::new();
assert!(!allocator.would_grant(1_000, 1));
allocator
.become_leader(pms(1_000), pms(5_000), Epoch(1))
.unwrap();
assert!(!allocator.would_grant(1_000, 0));
assert!(!allocator.would_grant(1_000, LOGICAL_MAX + 2));
assert!(allocator.would_grant(0, 1));
assert!(!allocator.would_grant(5_001, 1));
assert!(allocator.would_grant(2_500, 1));
}
#[test]
fn would_grant_predicts_logical_wrap_advance() {
let mut allocator = Allocator::new();
allocator
.become_leader(pms(1_000), pms(1_000), Epoch(1))
.unwrap();
allocator.try_grant(1_000, LOGICAL_MAX + 1).unwrap();
assert!(!allocator.would_grant(1_000, 1));
}
#[test]
fn would_grant_returns_false_when_advance_exceeds_physical_max() {
let mut allocator = Allocator::new();
allocator
.become_leader(PhysicalMs::MAX, PhysicalMs::MAX, Epoch(1))
.unwrap();
allocator
.try_grant(PHYSICAL_MS_MAX, LOGICAL_MAX + 1)
.unwrap();
assert!(!allocator.would_grant(PHYSICAL_MS_MAX, 1));
}
#[test]
fn default_constructs_not_leader_allocator() {
let allocator = Allocator::default();
assert!(!allocator.is_leader());
assert_eq!(allocator.epoch(), None);
}
#[test]
fn logical_wraps_to_next_physical_ms() {
let mut allocator = Allocator::new();
allocator
.become_leader(PhysicalMs::ZERO, PhysicalMs::ZERO, Epoch(1))
.unwrap();
allocator
.try_commit_window_extension(pms(10), Epoch(1))
.unwrap();
let first = allocator.try_grant(1, LOGICAL_MAX + 1).unwrap();
assert_eq!(first.physical_ms, 1);
assert_eq!(first.logical_start, 0);
let second = allocator.try_grant(1, 1).unwrap();
assert_eq!(second.physical_ms, 2);
assert_eq!(second.logical_start, 0);
}
#[test]
fn exact_fill_grant_normalizes_stored_state_to_packable() {
let mut allocator = Allocator::new();
allocator
.become_leader(PhysicalMs::ZERO, PhysicalMs::ZERO, Epoch(1))
.unwrap();
allocator
.try_commit_window_extension(pms(10), Epoch(1))
.unwrap();
let grant = allocator.try_grant(1, LOGICAL_MAX + 1).unwrap();
assert_eq!(grant.physical_ms, 1);
assert_eq!(grant.logical_start, 0);
let State::Leader {
next_physical_ms,
next_logical,
..
} = allocator.state
else {
panic!("expected leader state after a successful grant");
};
assert_eq!(next_physical_ms, 2);
assert_eq!(next_logical, 0);
assert!(Timestamp::try_pack(next_physical_ms, next_logical).is_ok());
}
#[test]
fn try_new_accepts_valid_grant_and_packs_boundaries() {
let grant = WindowGrant::try_new(1_000, 5, 3, Epoch(7)).unwrap();
assert_eq!(grant.physical_ms(), 1_000);
assert_eq!(grant.logical_start(), 5);
assert_eq!(grant.count(), 3);
assert_eq!(grant.epoch(), Epoch(7));
assert_eq!(grant.first(), Timestamp::pack(1_000, 5));
assert_eq!(grant.last(), Timestamp::pack(1_000, 7));
}
#[test]
fn try_new_accepts_max_logical_boundary() {
let grant = WindowGrant::try_new(1_000, 0, LOGICAL_MAX + 1, Epoch(1)).unwrap();
assert_eq!(grant.last(), Timestamp::pack(1_000, LOGICAL_MAX));
}
#[test]
fn try_new_rejects_zero_count() {
assert_eq!(
WindowGrant::try_new(1_000, 0, 0, Epoch(1)),
Err(CoreError::InvalidCount(0))
);
}
#[test]
fn try_new_rejects_out_of_range_physical_ms() {
assert_eq!(
WindowGrant::try_new(PHYSICAL_MS_MAX + 1, 0, 1, Epoch(1)),
Err(CoreError::PhysicalMsOutOfRange(PHYSICAL_MS_MAX + 1))
);
}
#[test]
fn try_new_rejects_logical_range_overflow() {
assert_eq!(
WindowGrant::try_new(1_000, LOGICAL_MAX, 2, Epoch(1)),
Err(CoreError::LogicalRangeOutOfRange {
logical_start: LOGICAL_MAX,
count: 2
})
);
}
#[test]
fn try_new_rejects_logical_count_u32_overflow() {
assert_eq!(
WindowGrant::try_new(1_000, u32::MAX, 2, Epoch(1)),
Err(CoreError::LogicalRangeOutOfRange {
logical_start: u32::MAX,
count: 2
})
);
}
#[test]
fn physical_ms_accepts_zero() {
assert_eq!(PhysicalMs::try_new(0).unwrap().get(), 0);
}
#[test]
fn physical_ms_accepts_max() {
assert_eq!(
PhysicalMs::try_new(PHYSICAL_MS_MAX).unwrap().get(),
PHYSICAL_MS_MAX,
);
}
#[test]
fn physical_ms_rejects_one_past_max() {
assert_eq!(
PhysicalMs::try_new(PHYSICAL_MS_MAX + 1),
Err(CoreError::PhysicalMsOutOfRange(PHYSICAL_MS_MAX + 1)),
);
}
#[test]
fn physical_ms_rejects_u64_max() {
assert_eq!(
PhysicalMs::try_new(u64::MAX),
Err(CoreError::PhysicalMsOutOfRange(u64::MAX)),
);
}
#[test]
fn physical_ms_max_const_matches_try_new() {
assert_eq!(
PhysicalMs::MAX,
PhysicalMs::try_new(PHYSICAL_MS_MAX).unwrap(),
);
}
#[test]
fn physical_ms_zero_const_matches_try_new_and_default() {
assert_eq!(PhysicalMs::ZERO, PhysicalMs::try_new(0).unwrap());
assert_eq!(PhysicalMs::default(), PhysicalMs::ZERO);
}
#[test]
fn physical_ms_try_from_matches_try_new() {
let good: u64 = 1_234_567;
let from_inherent = PhysicalMs::try_new(good).unwrap();
let from_trait: PhysicalMs = good.try_into().unwrap();
assert_eq!(from_inherent, from_trait);
let bad = PHYSICAL_MS_MAX + 1;
let bad_inherent = PhysicalMs::try_new(bad);
let bad_trait: Result<PhysicalMs, CoreError> = bad.try_into();
assert_eq!(bad_inherent, bad_trait);
}
#[test]
fn physical_ms_display_passes_through_inner_value() {
let v: u64 = 4_242_424_242;
assert_eq!(
format!("{}", PhysicalMs::try_new(v).unwrap()),
format!("{v}"),
);
}
#[test]
fn physical_ms_ordering_follows_inner_value() {
let a = PhysicalMs::try_new(5).unwrap();
let b = PhysicalMs::try_new(10).unwrap();
assert!(a < b);
assert!(b > a);
assert!(a <= a);
}
#[test]
fn physical_ms_is_copy_and_eq() {
fn assert_copy_eq<T: Copy + Eq>() {}
assert_copy_eq::<PhysicalMs>();
}
}