const MAX_BOXES_COUNT: u16 = 4096;
const MAX_PUSHERS_COUNT: u16 = 512;
use std::fmt::{Display, Formatter};
use super::errors::{PieceIdError, PieceIdErrorKind};
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(try_from = "u16", into = "u16")
)]
pub struct BoxID(u16);
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(try_from = "u16", into = "u16")
)]
pub struct GoalID(u16);
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(try_from = "u16", into = "u16")
)]
pub struct PusherID(u16);
macro_rules! implement_piece_id_construct_from_int {
( $PieceIdTy:ident, $ErrKind:expr, u8 ) => {
impl From<u8> for $PieceIdTy {
fn from(id: u8) -> Self { Self(id.into()) }
}
};
( $PieceIdTy:ident, $ErrKind:expr, u16 ) => {
impl TryFrom<u16> for $PieceIdTy {
type Error = PieceIdError;
fn try_from(value: u16) -> Result<Self, Self::Error> {
if value > Self::MAX.into() {
Err(PieceIdError {
kind: $ErrKind,
value: value.to_string(),
})
} else {
Ok(Self(value))
}
}
}
};
( $PieceIdTy:ident, $ErrKind:expr, $IntTy:ty ) => {
impl TryFrom<$IntTy> for $PieceIdTy {
type Error = PieceIdError;
fn try_from(value: $IntTy) -> Result<Self, Self::Error> {
u16::try_from(value)
.map_err(|_| {
PieceIdError {
kind: $ErrKind,
value: value.to_string(),
}
})?
.try_into()
}
}
};
}
macro_rules! implement_piece_id {
( $PieceIdTy:ident, $MaxConst:ident, $ErrKind:expr ) => {
impl $PieceIdTy {
const FIRST: u16 = 0;
pub const MAX: u16 = $MaxConst - 1;
pub fn new(id: u16) -> Result<Self, PieceIdError> { id.try_into() }
}
impl Display for $PieceIdTy {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.0) }
}
impl Default for $PieceIdTy {
fn default() -> Self { Self($PieceIdTy::FIRST) }
}
impl From<$PieceIdTy> for u16 {
fn from(value: $PieceIdTy) -> Self { return value.0; }
}
impl From<$PieceIdTy> for usize {
fn from(value: $PieceIdTy) -> Self { return value.0.into(); }
}
implement_piece_id_construct_from_int!($PieceIdTy, $ErrKind, u8);
implement_piece_id_construct_from_int!($PieceIdTy, $ErrKind, u16);
implement_piece_id_construct_from_int!($PieceIdTy, $ErrKind, u32);
implement_piece_id_construct_from_int!($PieceIdTy, $ErrKind, u64);
implement_piece_id_construct_from_int!($PieceIdTy, $ErrKind, usize);
implement_piece_id_construct_from_int!($PieceIdTy, $ErrKind, i8);
implement_piece_id_construct_from_int!($PieceIdTy, $ErrKind, i16);
implement_piece_id_construct_from_int!($PieceIdTy, $ErrKind, i32);
implement_piece_id_construct_from_int!($PieceIdTy, $ErrKind, i64);
implement_piece_id_construct_from_int!($PieceIdTy, $ErrKind, isize);
impl PartialEq<u8> for $PieceIdTy {
fn eq(&self, other: &u8) -> bool { self.0 == u16::from(*other) }
}
impl PartialEq<u16> for $PieceIdTy {
fn eq(&self, other: &u16) -> bool { self.0 == *other }
}
impl PartialEq<u32> for $PieceIdTy {
fn eq(&self, other: &u32) -> bool { u32::from(self.0) == *other }
}
impl PartialEq<u64> for $PieceIdTy {
fn eq(&self, other: &u64) -> bool { u64::from(self.0) == *other }
}
impl PartialEq<usize> for $PieceIdTy {
fn eq(&self, other: &usize) -> bool { usize::from(self.0) == *other }
}
impl PartialEq<i8> for $PieceIdTy {
fn eq(&self, other: &i8) -> bool { i32::from(self.0) == i32::from(*other) }
}
impl PartialEq<i16> for $PieceIdTy {
fn eq(&self, other: &i16) -> bool { i32::from(self.0) == i32::from(*other) }
}
impl PartialEq<i32> for $PieceIdTy {
fn eq(&self, other: &i32) -> bool { i32::from(self.0) == *other }
}
impl PartialEq<i64> for $PieceIdTy {
fn eq(&self, other: &i64) -> bool { i64::from(self.0) == *other }
}
impl PartialEq<isize> for $PieceIdTy {
fn eq(&self, other: &isize) -> bool {
match isize::try_from(self.0) {
Ok(self_isize) => self_isize == *other,
Err(_) => false,
}
}
}
};
}
implement_piece_id!(BoxID, MAX_BOXES_COUNT, PieceIdErrorKind::InvalidBoxID);
implement_piece_id!(GoalID, MAX_BOXES_COUNT, PieceIdErrorKind::InvalidGoalID);
implement_piece_id!(PusherID, MAX_PUSHERS_COUNT, PieceIdErrorKind::InvalidPusherID);
impl PartialEq<GoalID> for BoxID {
fn eq(&self, other: &GoalID) -> bool { self.0 == other.0 }
}
impl PartialEq<BoxID> for GoalID {
fn eq(&self, other: &BoxID) -> bool { self.0 == other.0 }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_box_id_can_be_constructed_from_integers() -> Result<(), PieceIdError> {
assert!(BoxID::new(42)? == 42_u8);
assert!(BoxID::from(42_u8) == 42_u8);
assert!(BoxID::try_from(42_u16)? == 42_u8);
assert!(BoxID::try_from(42_u32)? == 42_u8);
assert!(BoxID::try_from(42_u64)? == 42_u8);
assert!(BoxID::try_from(42_usize)? == 42_u8);
assert!(BoxID::try_from(42_i8)? == 42_u8);
assert!(BoxID::try_from(42_i16)? == 42_u8);
assert!(BoxID::try_from(42_i32)? == 42_u8);
assert!(BoxID::try_from(42_i64)? == 42_u8);
assert!(BoxID::try_from(42_isize)? == 42_u8);
assert!(BoxID::try_from(-1_i32).is_err());
Ok(())
}
#[test]
fn test_box_id_is_value_constrained() {
assert!(BoxID::try_from(BoxID::FIRST).is_ok());
assert!(BoxID::try_from(BoxID::MAX).is_ok());
assert!(BoxID::default() == BoxID::FIRST);
assert!(BoxID::try_from(usize::from(BoxID::MAX) + 1).is_err());
assert!(BoxID::try_from(MAX_BOXES_COUNT - 1).is_ok());
assert!(BoxID::try_from(MAX_BOXES_COUNT).is_err());
}
#[test]
fn test_box_id_is_comparable() {
assert!(BoxID::from(0) == BoxID::from(0));
assert!(BoxID::from(0) == 0_u8);
assert!(BoxID::from(0) == 0_u16);
assert!(BoxID::from(0) == 0_u32);
assert!(BoxID::from(0) == 0_u64);
assert!(BoxID::from(0) == 0_usize);
assert!(BoxID::from(0) == 0_i8);
assert!(BoxID::from(0) == 0_i16);
assert!(BoxID::from(0) == 0_i32);
assert!(BoxID::from(0) == 0_i64);
assert!(BoxID::from(0) == 0_isize);
assert!(BoxID::from(0) != BoxID::from(1));
assert!(BoxID::from(0) != 1_u8);
assert!(BoxID::from(0) != 1_u16);
assert!(BoxID::from(0) != 1_u32);
assert!(BoxID::from(0) != 1_u64);
assert!(BoxID::from(0) != 1_usize);
assert!(BoxID::from(0) != 1_i8);
assert!(BoxID::from(0) != 1_i16);
assert!(BoxID::from(0) != 1_i32);
assert!(BoxID::from(0) != 1_i64);
assert!(BoxID::from(0) != 1_isize);
assert!(BoxID::from(0) == GoalID::from(0));
assert!(BoxID::from(0) != GoalID::from(1));
}
#[test]
fn test_goal_id_can_be_constructed_from_integers() -> Result<(), PieceIdError> {
assert!(GoalID::new(42)? == 42_u8);
assert!(GoalID::from(42_u8) == 42_u8);
assert!(GoalID::try_from(42_u16)? == 42_u8);
assert!(GoalID::try_from(42_u32)? == 42_u8);
assert!(GoalID::try_from(42_u64)? == 42_u8);
assert!(GoalID::try_from(42_usize)? == 42_u8);
assert!(GoalID::try_from(42_i8)? == 42_u8);
assert!(GoalID::try_from(42_i16)? == 42_u8);
assert!(GoalID::try_from(42_i32)? == 42_u8);
assert!(GoalID::try_from(42_i64)? == 42_u8);
assert!(GoalID::try_from(42_isize)? == 42_u8);
assert!(GoalID::try_from(-1_i32).is_err());
Ok(())
}
#[test]
fn test_goal_id_is_value_constrained() {
assert!(GoalID::try_from(GoalID::FIRST).is_ok());
assert!(GoalID::try_from(GoalID::MAX).is_ok());
assert!(GoalID::default() == GoalID::FIRST);
assert!(GoalID::try_from(usize::from(GoalID::MAX) + 1).is_err());
assert!(GoalID::try_from(MAX_BOXES_COUNT - 1).is_ok());
assert!(GoalID::try_from(MAX_BOXES_COUNT).is_err());
}
#[test]
fn test_goal_id_is_comparable() {
assert!(GoalID::from(0) == GoalID::from(0));
assert!(GoalID::from(0) == 0_u8);
assert!(GoalID::from(0) == 0_u16);
assert!(GoalID::from(0) == 0_u32);
assert!(GoalID::from(0) == 0_u64);
assert!(GoalID::from(0) == 0_usize);
assert!(GoalID::from(0) == 0_i8);
assert!(GoalID::from(0) == 0_i16);
assert!(GoalID::from(0) == 0_i32);
assert!(GoalID::from(0) == 0_i64);
assert!(GoalID::from(0) == 0_isize);
assert!(GoalID::from(0) != GoalID::from(1));
assert!(GoalID::from(0) != 1_u8);
assert!(GoalID::from(0) != 1_u16);
assert!(GoalID::from(0) != 1_u32);
assert!(GoalID::from(0) != 1_u64);
assert!(GoalID::from(0) != 1_usize);
assert!(GoalID::from(0) != 1_i8);
assert!(GoalID::from(0) != 1_i16);
assert!(GoalID::from(0) != 1_i32);
assert!(GoalID::from(0) != 1_i64);
assert!(GoalID::from(0) != 1_isize);
assert!(GoalID::from(0) == BoxID::from(0));
assert!(GoalID::from(0) != BoxID::from(1));
}
#[test]
fn test_pusher_id_can_be_constructed_from_integers() -> Result<(), PieceIdError> {
assert!(PusherID::new(42)? == 42_u8);
assert!(PusherID::from(42_u8) == 42_u8);
assert!(PusherID::try_from(42_u16)? == 42_u8);
assert!(PusherID::try_from(42_u32)? == 42_u8);
assert!(PusherID::try_from(42_u64)? == 42_u8);
assert!(PusherID::try_from(42_usize)? == 42_u8);
assert!(PusherID::try_from(42_i8)? == 42_u8);
assert!(PusherID::try_from(42_i16)? == 42_u8);
assert!(PusherID::try_from(42_i32)? == 42_u8);
assert!(PusherID::try_from(42_i64)? == 42_u8);
assert!(PusherID::try_from(42_isize)? == 42_u8);
assert!(PusherID::try_from(-1_i32).is_err());
Ok(())
}
#[test]
fn test_pusher_id_is_value_constrained() {
assert!(PusherID::try_from(PusherID::FIRST).is_ok());
assert!(PusherID::try_from(PusherID::MAX).is_ok());
assert!(PusherID::default() == PusherID::FIRST);
assert!(PusherID::try_from(usize::from(PusherID::MAX) + 1).is_err());
assert!(PusherID::try_from(MAX_PUSHERS_COUNT - 1).is_ok());
assert!(PusherID::try_from(MAX_PUSHERS_COUNT).is_err());
}
#[test]
fn test_pusher_id_is_comparable() {
assert!(PusherID::from(0) == PusherID::from(0));
assert!(PusherID::from(0) == 0_u8);
assert!(PusherID::from(0) == 0_u16);
assert!(PusherID::from(0) == 0_u32);
assert!(PusherID::from(0) == 0_u64);
assert!(PusherID::from(0) == 0_usize);
assert!(PusherID::from(0) == 0_i8);
assert!(PusherID::from(0) == 0_i16);
assert!(PusherID::from(0) == 0_i32);
assert!(PusherID::from(0) == 0_i64);
assert!(PusherID::from(0) == 0_isize);
assert!(PusherID::from(0) != PusherID::from(1));
assert!(PusherID::from(0) != 1_u8);
assert!(PusherID::from(0) != 1_u16);
assert!(PusherID::from(0) != 1_u32);
assert!(PusherID::from(0) != 1_u64);
assert!(PusherID::from(0) != 1_usize);
assert!(PusherID::from(0) != 1_i8);
assert!(PusherID::from(0) != 1_i16);
assert!(PusherID::from(0) != 1_i32);
assert!(PusherID::from(0) != 1_i64);
assert!(PusherID::from(0) != 1_isize);
}
#[cfg(feature = "serde")]
mod test_piece_id_serde {
use std::error::Error;
use super::*;
#[test]
fn it_serializes_as_a_bare_integer() -> Result<(), Box<dyn Error>> {
assert_eq!(serde_json::to_string(&BoxID::from(42_u8))?, "42");
assert_eq!(serde_json::to_string(&GoalID::from(42_u8))?, "42");
assert_eq!(serde_json::to_string(&PusherID::from(42_u8))?, "42");
assert_eq!(serde_json::from_str::<BoxID>("42")?, BoxID::from(42_u8));
assert_eq!(serde_json::from_str::<GoalID>("42")?, GoalID::from(42_u8));
assert_eq!(serde_json::from_str::<PusherID>("42")?, PusherID::from(42_u8));
Ok(())
}
#[test]
fn it_rejects_ids_out_of_range() {
assert!(serde_json::from_str::<BoxID>(&MAX_BOXES_COUNT.to_string()).is_err());
assert!(serde_json::from_str::<GoalID>(&MAX_BOXES_COUNT.to_string()).is_err());
assert!(serde_json::from_str::<PusherID>(&MAX_PUSHERS_COUNT.to_string()).is_err());
assert!(serde_json::from_str::<BoxID>("65536").is_err());
assert!(serde_json::from_str::<BoxID>("-1").is_err());
}
#[test]
fn it_accepts_the_largest_legal_id() -> Result<(), Box<dyn Error>> {
assert_eq!(
serde_json::from_str::<BoxID>(&BoxID::MAX.to_string())?,
BoxID::try_from(BoxID::MAX)?
);
assert_eq!(
serde_json::from_str::<PusherID>(&PusherID::MAX.to_string())?,
PusherID::try_from(PusherID::MAX)?
);
Ok(())
}
}
}