neodes_codec 0.1.3

Library to read and write data from DSN files as described in the NeoDes norm.
Documentation
use std::error::Error;
use std::fmt::{Display, Formatter};
use std::str::FromStr;

/// Structure representing then block part of a field ID
/// i.e. the `42` in `S21.G00.42.001`
#[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 {
	/// Creates a ShortFieldId from a number. Truncates the decimals to the last 2
	///
	/// # Examples
	///
	/// ```
	/// use neodes_codec::line::ShortBlockId;
	///
	/// assert_eq!(ShortBlockId::from_u8_lossy(021).to_string(), "21");
	/// assert_eq!(ShortBlockId::from_u8_lossy(221).to_string(), "21");
	/// ```
	#[inline]
	pub const fn from_u8_lossy(value: u8) -> Self {
		ShortBlockId(value % HIGH_BOUND)
	}
}

/// Parsing error returned when parsing of `ShortBlockId` from a string failed.
#[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;

	/// # Examples
	///
	/// ```
	/// # use std::error::Error;
	/// # fn main() -> Result<(), Box<dyn Error>> {
	/// use neodes_codec::line::{ ShortBlockId, ShortBlockIdParsingError};
	/// use std::str::FromStr;
	///
	/// assert_eq!(ShortBlockId::from_str("13"), Ok(ShortBlockId::from_u8_lossy(13)));
	/// assert_eq!(ShortBlockId::from_str("XX"), Err(ShortBlockIdParsingError));
	///
	/// # Ok(())
	/// # }
	/// ```
	#[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);
	}
}