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 ShortFieldId(u16);
impl Display for ShortFieldId {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{:03}", self.0)
}
}
const HIGH_BOUND: u16 = 1000;
impl ShortFieldId {
#[inline]
pub const fn from_u16_lossy(value: u16) -> Self {
ShortFieldId(value % HIGH_BOUND)
}
}
impl FromStr for ShortFieldId {
type Err = ShortFieldIdParsingError;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.len() == 3 {
let i = s.parse().map_err(|_| ShortFieldIdParsingError)?;
Ok(ShortFieldId(i))
} else {
Err(ShortFieldIdParsingError)
}
}
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub struct ShortFieldIdParsingError;
impl Error for ShortFieldIdParsingError {}
impl Display for ShortFieldIdParsingError {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "Cannot parse ShortFieldId")
}
}
macro_rules! impl_numeric_cmp {
( $( $t:ty ),* ) => {
$(
impl PartialEq<$t> for ShortFieldId {
#[inline]
fn eq(&self, other: &$t) -> bool {
match u16::try_from(*other) {
Ok(val) => self.0 == val,
Err(_) => false,
}
}
}
impl PartialEq<ShortFieldId> for $t {
#[inline]
fn eq(&self, other: &ShortFieldId) -> bool {
match u16::try_from(*self) {
Ok(val) => other.0 == val,
Err(_) => false,
}
}
}
impl TryFrom<$t> for ShortFieldId {
type Error = ShortFieldIdParsingError;
#[inline]
fn try_from(value: $t) -> Result<Self, Self::Error> {
match u16::try_from(value) {
Ok(x) if x < HIGH_BOUND => Ok(Self(x)),
_ => Err(ShortFieldIdParsingError)
}
}
}
)*
}
}
macro_rules! impl_string_cmp {
( $( $t:ty ),* ) => {
$(
impl PartialEq<$t> for ShortFieldId {
#[inline]
fn eq(&self, other: &$t) -> bool {
let s = other.as_ref();
match ShortFieldId::from_str(s) {
Ok(n) => *self == n,
Err(_) => false,
}
}
}
impl PartialEq<ShortFieldId> for $t {
#[inline]
fn eq(&self, other: &ShortFieldId) -> bool {
other == self
}
}
)*
}
}
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::ShortFieldId;
use crate::line::id::short_field_id::ShortFieldIdParsingError;
use parameterized::parameterized;
use quickcheck_macros::quickcheck;
use std::str::FromStr;
#[parameterized(input = {
"X01",
"0X1",
"00X",
"0",
"00",
"0000"
})]
fn returns_parsing_errors(input: &str) {
assert_eq!(ShortFieldId::from_str(input), Err(ShortFieldIdParsingError));
}
#[quickcheck]
fn check_from_lossy(n: u16) {
assert_eq!(
ShortFieldId::from_u16_lossy(n),
ShortFieldId::from_u16_lossy(n % 1000)
);
}
#[quickcheck]
fn check_display(n: u16) {
assert_eq!(
ShortFieldId::from_u16_lossy(n).to_string(),
format!("{:03}", n % 1000)
);
}
#[quickcheck]
fn check_from_str(n: u16) {
assert_eq!(
ShortFieldId::from_str(format!("{:03}", n % 1000).as_str()),
Ok(ShortFieldId::from_u16_lossy(n))
);
}
#[quickcheck]
fn check_from_int(n: isize) {
let short_field_id = ShortFieldId::try_from(n);
assert_eq!(short_field_id.is_ok(), n >= 0 && n < 100);
if let Ok(short_field_id) = short_field_id {
assert_eq!(n as u16, short_field_id.0);
}
}
#[test]
fn test_equals() {
let id = ShortFieldId::from_u16_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, "021");
assert_eq!(id, "021".to_string());
assert_eq!("021", id);
assert_eq!("021".to_string(), id);
}
#[parameterized(input = {
"22",
"20",
"121",
"21.0",
"abc",
"",
"21 ",
" 21"
})]
fn test_not_equals(input: &str) {
let id = ShortFieldId::from_u16_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() {
let expected = Ok(ShortFieldId::from_u16_lossy(7));
assert_eq!(ShortFieldId::try_from(7usize), expected);
assert_eq!(ShortFieldId::try_from(7u8), expected);
assert_eq!(ShortFieldId::try_from(7u16), expected);
assert_eq!(ShortFieldId::try_from(7u32), expected);
assert_eq!(ShortFieldId::try_from(7u64), expected);
assert_eq!(ShortFieldId::try_from(7u128), expected);
assert_eq!(ShortFieldId::try_from(7isize), expected);
assert_eq!(ShortFieldId::try_from(7i8), expected);
assert_eq!(ShortFieldId::try_from(7i16), expected);
assert_eq!(ShortFieldId::try_from(7i32), expected);
assert_eq!(ShortFieldId::try_from(7i64), expected);
assert_eq!(ShortFieldId::try_from(7i128), expected);
}
#[test]
fn test_try_from_number_fail() {
let expected = Err(ShortFieldIdParsingError);
assert_eq!(ShortFieldId::try_from(1117usize), expected);
assert_eq!(ShortFieldId::try_from(1117u16), expected);
assert_eq!(ShortFieldId::try_from(1117u32), expected);
assert_eq!(ShortFieldId::try_from(1117u64), expected);
assert_eq!(ShortFieldId::try_from(1117u128), expected);
assert_eq!(ShortFieldId::try_from(1117isize), expected);
assert_eq!(ShortFieldId::try_from(1117i16), expected);
assert_eq!(ShortFieldId::try_from(1117i32), expected);
assert_eq!(ShortFieldId::try_from(1117i64), expected);
assert_eq!(ShortFieldId::try_from(1117i128), expected);
}
}