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