use std::fmt::{Display, Formatter};
use std::num::NonZeroU16;
use crate::{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 PlusID(NonZeroU16);
impl PlusID {
pub fn new(id: u16) -> Result<Self, PieceIdError> { id.try_into() }
}
impl Display for PlusID {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.0) }
}
impl From<PlusID> for u16 {
fn from(value: PlusID) -> Self { value.0.get() }
}
impl From<PlusID> for usize {
fn from(value: PlusID) -> Self { value.0.get().into() }
}
macro_rules! implement_plus_id_construct_from_int {
( u8 ) => {
impl TryFrom<u8> for PlusID {
type Error = PieceIdError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
Ok(Self(NonZeroU16::try_from(u16::from(value)).map_err(|_| {
PieceIdError {
kind: PieceIdErrorKind::InvalidPlusID,
value: value.to_string(),
}
})?))
}
}
};
( u16) => {
impl TryFrom<u16> for PlusID {
type Error = PieceIdError;
fn try_from(value: u16) -> Result<Self, Self::Error> {
Ok(Self(NonZeroU16::try_from(value).map_err(|_| {
PieceIdError {
kind: PieceIdErrorKind::InvalidPlusID,
value: value.to_string(),
}
})?))
}
}
};
( $IntTy:ty ) => {
impl TryFrom<$IntTy> for PlusID {
type Error = PieceIdError;
fn try_from(value: $IntTy) -> Result<Self, Self::Error> {
Ok(Self(
NonZeroU16::try_from(u16::try_from(value).map_err(|_| {
PieceIdError {
kind: PieceIdErrorKind::InvalidPlusID,
value: value.to_string(),
}
})?)
.map_err(|_| {
PieceIdError {
kind: PieceIdErrorKind::InvalidPlusID,
value: value.to_string(),
}
})?,
))
}
}
};
}
implement_plus_id_construct_from_int!(u8);
implement_plus_id_construct_from_int!(u16);
implement_plus_id_construct_from_int!(u32);
implement_plus_id_construct_from_int!(u64);
implement_plus_id_construct_from_int!(usize);
implement_plus_id_construct_from_int!(i8);
implement_plus_id_construct_from_int!(i16);
implement_plus_id_construct_from_int!(i32);
implement_plus_id_construct_from_int!(i64);
implement_plus_id_construct_from_int!(isize);
impl PartialEq<u8> for PlusID {
fn eq(&self, other: &u8) -> bool { self.0.get() == u16::from(*other) }
}
impl PartialEq<u16> for PlusID {
fn eq(&self, other: &u16) -> bool { self.0.get() == *other }
}
impl PartialEq<u32> for PlusID {
fn eq(&self, other: &u32) -> bool { u32::from(self.0.get()) == *other }
}
impl PartialEq<u64> for PlusID {
fn eq(&self, other: &u64) -> bool { u64::from(self.0.get()) == *other }
}
impl PartialEq<usize> for PlusID {
fn eq(&self, other: &usize) -> bool { usize::from(self.0.get()) == *other }
}
impl PartialEq<i8> for PlusID {
fn eq(&self, other: &i8) -> bool { i32::from(self.0.get()) == i32::from(*other) }
}
impl PartialEq<i16> for PlusID {
fn eq(&self, other: &i16) -> bool { i32::from(self.0.get()) == i32::from(*other) }
}
impl PartialEq<i32> for PlusID {
fn eq(&self, other: &i32) -> bool { i32::from(self.0.get()) == *other }
}
impl PartialEq<i64> for PlusID {
fn eq(&self, other: &i64) -> bool { i64::from(self.0.get()) == *other }
}
impl PartialEq<isize> for PlusID {
fn eq(&self, other: &isize) -> bool {
match isize::try_from(self.0.get()) {
Ok(self_isize) => self_isize == *other,
Err(_) => false,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_plus_id_can_be_constructed_from_integers() -> Result<(), PieceIdError> {
assert!(PlusID::new(42)? == 42_u8);
assert!(PlusID::try_from(42_u8)? == 42_u8);
assert!(PlusID::try_from(42_u16)? == 42_u8);
assert!(PlusID::try_from(42_u32)? == 42_u8);
assert!(PlusID::try_from(42_u64)? == 42_u8);
assert!(PlusID::try_from(42_usize)? == 42_u8);
assert!(PlusID::try_from(42_i8)? == 42_u8);
assert!(PlusID::try_from(42_i16)? == 42_u8);
assert!(PlusID::try_from(42_i32)? == 42_u8);
assert!(PlusID::try_from(42_i64)? == 42_u8);
assert!(PlusID::try_from(42_isize)? == 42_u8);
assert!(PlusID::try_from(-1_i32).is_err());
Ok(())
}
#[test]
fn test_plus_id_is_value_constrained() {
assert!(PlusID::new(0).is_err());
assert!(PlusID::try_from(0_u8).is_err());
assert!(PlusID::try_from(usize::from(u16::MAX) + 1).is_err());
}
#[test]
fn test_plus_id_is_comparable_to_integers() -> Result<(), PieceIdError> {
assert!(PlusID::try_from(1_u8)? == 1_u8);
assert!(PlusID::try_from(1_u8)? == 1_u16);
assert!(PlusID::try_from(1_u8)? == 1_u32);
assert!(PlusID::try_from(1_u8)? == 1_u64);
assert!(PlusID::try_from(1_u8)? == 1_usize);
assert!(PlusID::try_from(1_u8)? == 1_i8);
assert!(PlusID::try_from(1_u8)? == 1_i16);
assert!(PlusID::try_from(1_u8)? == 1_i32);
assert!(PlusID::try_from(1_u8)? == 1_i64);
assert!(PlusID::try_from(1_u8)? == 1_isize);
assert!(PlusID::try_from(1_u8)? != 2_u8);
assert!(PlusID::try_from(1_u8)? != 2_u16);
assert!(PlusID::try_from(1_u8)? != 2_u32);
assert!(PlusID::try_from(1_u8)? != 2_u64);
assert!(PlusID::try_from(1_u8)? != 2_usize);
assert!(PlusID::try_from(1_u8)? != 2_i8);
assert!(PlusID::try_from(1_u8)? != 2_i16);
assert!(PlusID::try_from(1_u8)? != 2_i32);
assert!(PlusID::try_from(1_u8)? != 2_i64);
assert!(PlusID::try_from(1_u8)? != 2_isize);
Ok(())
}
#[cfg(feature = "serde")]
mod test_plus_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(&PlusID::new(42)?)?, "42");
assert_eq!(serde_json::from_str::<PlusID>("42")?, PlusID::new(42)?);
Ok(())
}
#[test]
fn it_rejects_ids_out_of_range() {
assert!(serde_json::from_str::<PlusID>("0").is_err());
assert!(serde_json::from_str::<PlusID>("65536").is_err());
assert!(serde_json::from_str::<PlusID>("-1").is_err());
}
}
}