use std::error::Error;
use std::fmt::{Display, Formatter};
use std::str::FromStr;
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
#[cfg_attr(
feature = "serde",
derive(serde_with::DeserializeFromStr, serde_with::SerializeDisplay)
)]
pub struct ShortBlockId(u8);
impl Display for ShortBlockId {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{:02}", self.0)
}
}
const HIGH_BOUND: u8 = 100;
impl ShortBlockId {
#[inline]
pub const fn from_u8_lossy(value: u8) -> Self {
ShortBlockId(value % HIGH_BOUND)
}
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub struct ShortBlockIdParsingError;
impl Error for ShortBlockIdParsingError {}
impl Display for ShortBlockIdParsingError {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "Cannot parse ShortBlockId")
}
}
impl FromStr for ShortBlockId {
type Err = ShortBlockIdParsingError;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.len() == 2 {
let i = s.parse().map_err(|_| ShortBlockIdParsingError)?;
Ok(ShortBlockId(i))
} else {
Err(ShortBlockIdParsingError)
}
}
}
macro_rules! impl_numeric_cmp {
( $( $t:ty ),* ) => {
$(
impl PartialEq<$t> for ShortBlockId {
#[inline]
fn eq(&self, other: &$t) -> bool {
match ShortBlockId::try_from(*other) {
Ok(val) => *self == val,
Err(_) => false,
}
}
}
impl PartialEq<ShortBlockId> for $t {
#[inline]
fn eq(&self, other: &ShortBlockId) -> bool {
other == self
}
}
impl TryFrom<$t> for ShortBlockId {
type Error = ShortBlockIdParsingError;
#[inline]
fn try_from(value: $t) -> Result<Self, Self::Error> {
match u8::try_from(value) {
Ok(x) if x < HIGH_BOUND => Ok(Self(x)),
_ => Err(ShortBlockIdParsingError)
}
}
}
)*
}
}
macro_rules! impl_string_cmp {
( $( $t:ty ),* ) => {
$(
impl PartialEq<$t> for ShortBlockId {
#[inline]
fn eq(&self, other: &$t) -> bool {
let s = other.as_ref();
match u8::from_str(s) {
Ok(n) => self.0 == n,
Err(_) => false,
}
}
}
impl PartialEq<ShortBlockId> for $t {
#[inline]
fn eq(&self, other: &ShortBlockId) -> bool {
match u8::from_str(self) {
Ok(val) => other.0 == val,
Err(_) => false,
}
}
}
)*
}
}
impl_numeric_cmp!(
usize, u8, u16, u32, u64, u128, isize, i8, i16, i32, i64, i128
);
impl_string_cmp!(str, &str, String);
#[cfg(test)]
mod tests {
use crate::line::id::short_block_id::{ShortBlockId, ShortBlockIdParsingError};
use parameterized::parameterized;
use quickcheck_macros::quickcheck;
use std::str::FromStr;
#[parameterized(input = {
"X1",
"0X",
"0",
"000"
})]
fn returns_parsing_errors(input: &str) {
assert_eq!(ShortBlockId::from_str(input), Err(ShortBlockIdParsingError));
}
#[quickcheck]
fn check_from_lossy(n: u8) {
assert_eq!(
ShortBlockId::from_u8_lossy(n),
ShortBlockId::from_u8_lossy(n % 100)
);
}
#[quickcheck]
fn check_display(n: u8) {
assert_eq!(
ShortBlockId::from_u8_lossy(n).to_string(),
format!("{:02}", n % 100)
);
}
#[quickcheck]
fn check_from_str(n: u8) {
assert_eq!(
ShortBlockId::from_str(format!("{:02}", n % 100).as_str()),
Ok(ShortBlockId::from_u8_lossy(n))
);
}
#[quickcheck]
fn check_from_int(n: isize) {
let short_block_id = ShortBlockId::try_from(n);
assert_eq!(short_block_id.is_ok(), n >= 0 && n < 100);
if let Ok(short_block_id) = short_block_id {
assert_eq!(n as u8, short_block_id.0);
}
}
#[test]
fn test_equals() {
let id = ShortBlockId::from_u8_lossy(21);
assert_eq!(id, 21usize);
assert_eq!(id, 21u8);
assert_eq!(id, 21u16);
assert_eq!(id, 21u32);
assert_eq!(id, 21u64);
assert_eq!(id, 21u128);
assert_eq!(id, 21isize);
assert_eq!(id, 21i8);
assert_eq!(id, 21i16);
assert_eq!(id, 21i32);
assert_eq!(id, 21i64);
assert_eq!(id, 21i128);
assert_eq!(21usize, id);
assert_eq!(21u8, id);
assert_eq!(21u16, id);
assert_eq!(21u32, id);
assert_eq!(21u64, id);
assert_eq!(21u128, id);
assert_eq!(21isize, id);
assert_eq!(21i8, id);
assert_eq!(21i16, id);
assert_eq!(21i32, id);
assert_eq!(21i64, id);
assert_eq!(21i128, id);
assert_eq!(id, "21");
assert_eq!(id, "21".to_string());
assert_eq!("21", id);
assert_eq!("21".to_string(), id);
}
#[parameterized(input = {
"22",
"20",
"121",
"21.0",
"abc",
"",
"21 ",
" 21"
})]
fn test_not_equals(input: &str) {
let id = ShortBlockId::from_u8_lossy(21);
assert_ne!(id, input);
assert_ne!(input, id);
assert_ne!(id, input.to_string());
assert_ne!(input.to_string(), id);
}
#[test]
fn test_from_number() {
let expected = Ok(ShortBlockId::from_u8_lossy(7));
assert_eq!(ShortBlockId::try_from(7usize), expected);
assert_eq!(ShortBlockId::try_from(7u8), expected);
assert_eq!(ShortBlockId::try_from(7u16), expected);
assert_eq!(ShortBlockId::try_from(7u32), expected);
assert_eq!(ShortBlockId::try_from(7u64), expected);
assert_eq!(ShortBlockId::try_from(7u128), expected);
assert_eq!(ShortBlockId::try_from(7isize), expected);
assert_eq!(ShortBlockId::try_from(7i8), expected);
assert_eq!(ShortBlockId::try_from(7i16), expected);
assert_eq!(ShortBlockId::try_from(7i32), expected);
assert_eq!(ShortBlockId::try_from(7i64), expected);
assert_eq!(ShortBlockId::try_from(7i128), expected);
}
#[test]
fn test_try_from_number_fail() {
let expected = Err(ShortBlockIdParsingError);
assert_eq!(ShortBlockId::try_from(117usize), expected);
assert_eq!(ShortBlockId::try_from(117u16), expected);
assert_eq!(ShortBlockId::try_from(117u32), expected);
assert_eq!(ShortBlockId::try_from(117u64), expected);
assert_eq!(ShortBlockId::try_from(117u128), expected);
assert_eq!(ShortBlockId::try_from(117isize), expected);
assert_eq!(ShortBlockId::try_from(117i16), expected);
assert_eq!(ShortBlockId::try_from(117i32), expected);
assert_eq!(ShortBlockId::try_from(117i64), expected);
assert_eq!(ShortBlockId::try_from(117i128), expected);
}
}