use core::num::NonZeroU64;
use thiserror::Error;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Dot {
station: u32,
counter: NonZeroU64,
}
impl Dot {
#[must_use]
pub const fn new(station: u32, counter: NonZeroU64) -> Self {
Self { station, counter }
}
pub const fn from_parts(station: u32, counter: u64) -> Result<Self, InvalidDot> {
match NonZeroU64::new(counter) {
Some(counter) => Ok(Self { station, counter }),
None => Err(InvalidDot { station }),
}
}
#[must_use]
pub const fn station(self) -> u32 {
self.station
}
#[must_use]
pub const fn counter(self) -> u64 {
self.counter.get()
}
#[must_use]
pub const fn counter_nonzero(self) -> NonZeroU64 {
self.counter
}
#[must_use]
pub const fn raw(self) -> RawDot {
RawDot {
station: self.station,
counter: self.counter.get(),
}
}
}
impl core::fmt::Display for Dot {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "({}, {})", self.station, self.counter)
}
}
impl TryFrom<(u32, u64)> for Dot {
type Error = InvalidDot;
fn try_from((station, counter): (u32, u64)) -> Result<Self, InvalidDot> {
Self::from_parts(station, counter)
}
}
impl TryFrom<RawDot> for Dot {
type Error = InvalidDot;
fn try_from(raw: RawDot) -> Result<Self, InvalidDot> {
Self::from_parts(raw.station, raw.counter)
}
}
impl From<Dot> for (u32, u64) {
fn from(dot: Dot) -> Self {
(dot.station, dot.counter.get())
}
}
impl From<Dot> for RawDot {
fn from(dot: Dot) -> Self {
dot.raw()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct RawDot {
pub station: u32,
pub counter: u64,
}
impl RawDot {
#[must_use]
pub const fn new(station: u32, counter: u64) -> Self {
Self { station, counter }
}
}
impl core::fmt::Display for RawDot {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "({}, {})", self.station, self.counter)
}
}
impl From<(u32, u64)> for RawDot {
fn from((station, counter): (u32, u64)) -> Self {
Self { station, counter }
}
}
impl From<RawDot> for (u32, u64) {
fn from(raw: RawDot) -> Self {
(raw.station, raw.counter)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Error)]
#[error("station {station} names the non-dot counter zero")]
pub struct InvalidDot {
pub station: u32,
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec::Vec;
extern crate alloc;
#[test]
fn the_non_dot_zero_is_unrepresentable_and_the_station_domain_is_whole() {
assert_eq!(Dot::from_parts(3, 0), Err(InvalidDot { station: 3 }));
assert_eq!(Dot::try_from((0, 0)), Err(InvalidDot { station: 0 }));
let zero_station = Dot::from_parts(0, 1).unwrap();
assert_eq!(zero_station.station(), 0);
assert_eq!(zero_station.counter(), 1);
}
#[test]
fn ordering_matches_the_tuple_it_replaces() {
let tuples = [(0u32, 1u64), (0, 2), (1, 1), (1, 7), (2, 1)];
let dots: Vec<Dot> = tuples
.iter()
.map(|&pair| Dot::try_from(pair).unwrap())
.collect();
let mut sorted = dots.clone();
sorted.sort_unstable();
assert_eq!(dots, sorted);
let raws: Vec<RawDot> = dots.iter().map(|&dot| dot.raw()).collect();
let mut raw_sorted = raws.clone();
raw_sorted.sort_unstable();
assert_eq!(raws, raw_sorted);
}
#[test]
fn the_crossing_round_trips_and_the_surrender_is_total() {
let dot = Dot::from_parts(7, 11).unwrap();
assert_eq!(Dot::try_from(dot.raw()), Ok(dot));
assert_eq!(<(u32, u64)>::from(dot), (7, 11));
let dangling = RawDot::new(7, 0);
assert_eq!(Dot::try_from(dangling), Err(InvalidDot { station: 7 }));
assert_eq!(<(u32, u64)>::from(dangling), (7, 0));
}
}