use core::num::NonZeroU32;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct BoundedRevs(NonZeroU32);
impl BoundedRevs {
pub const MAX: u32 = 32;
pub const fn try_new(n: u32) -> Result<Self, RevsOutOfRange> {
if let Some(nz) = NonZeroU32::new(n) {
if nz.get() <= Self::MAX {
return Ok(Self(nz));
}
}
Err(RevsOutOfRange {
requested: n,
max: Self::MAX,
})
}
#[must_use]
pub const fn get(self) -> u32 {
self.0.get()
}
pub(crate) fn range_inclusive_one_to_self(self) -> impl Iterator<Item = Self> {
(1..=self.0.get()).filter_map(|n| NonZeroU32::new(n).map(Self))
}
}
impl core::fmt::Display for BoundedRevs {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
self.0.fmt(f)
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for BoundedRevs {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.0.get().serialize(serializer)
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for BoundedRevs {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let n = u32::deserialize(deserializer)?;
Self::try_new(n).map_err(serde::de::Error::custom)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[error("revolution count {requested} out of range (must be 1..={max})")]
pub struct RevsOutOfRange {
pub requested: u32,
pub max: u32,
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used)]
use super::*;
#[test]
fn try_new_accepts_one_through_max() {
for n in 1..=BoundedRevs::MAX {
let revs = BoundedRevs::try_new(n).unwrap();
assert_eq!(revs.get(), n);
}
}
#[test]
fn try_new_rejects_zero() {
let err = BoundedRevs::try_new(0).unwrap_err();
assert_eq!(
err,
RevsOutOfRange {
requested: 0,
max: BoundedRevs::MAX,
}
);
}
#[test]
fn try_new_rejects_max_plus_one() {
let err = BoundedRevs::try_new(BoundedRevs::MAX + 1).unwrap_err();
assert_eq!(
err,
RevsOutOfRange {
requested: BoundedRevs::MAX + 1,
max: BoundedRevs::MAX,
}
);
}
#[test]
fn try_new_rejects_u32_max() {
let err = BoundedRevs::try_new(u32::MAX).unwrap_err();
assert_eq!(
err,
RevsOutOfRange {
requested: u32::MAX,
max: BoundedRevs::MAX,
}
);
}
#[test]
fn try_new_is_const() {
const REVS: Result<BoundedRevs, RevsOutOfRange> = BoundedRevs::try_new(5);
assert_eq!(REVS.unwrap().get(), 5);
}
#[test]
fn display_matches_inner_value() {
let revs = BoundedRevs::try_new(5).unwrap();
assert_eq!(format!("{revs}"), "5");
}
#[cfg(feature = "serde")]
#[test]
fn serde_round_trip_preserves_value() {
let revs = BoundedRevs::try_new(7).unwrap();
let json = serde_json::to_string(&revs).unwrap();
assert_eq!(json, "7");
let back: BoundedRevs = serde_json::from_str(&json).unwrap();
assert_eq!(revs, back);
}
#[cfg(feature = "serde")]
#[test]
fn serde_rejects_out_of_range_on_deserialize() {
assert!(serde_json::from_str::<BoundedRevs>("0").is_err());
let too_big = format!("{}", BoundedRevs::MAX + 1);
assert!(serde_json::from_str::<BoundedRevs>(&too_big).is_err());
}
}