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 structure part of a field ID
/// i.e. the `S21` 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 ShortStrucId(u8);

impl Display for ShortStrucId {
	#[inline]
	fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
		write!(f, "S{:02}", self.0)
	}
}

const HIGH_BOUND: u8 = 100;

impl ShortStrucId {
	/// Creates a ShortFieldId from a number. Truncates the decimals to the last 2
	///
	/// # Examples
	///
	/// ```
	/// use neodes_codec::line::ShortStrucId;
	///
	/// assert_eq!(ShortStrucId::from_u8_lossy(021).to_string(), "S21");
	/// assert_eq!(ShortStrucId::from_u8_lossy(221).to_string(), "S21");
	/// ```
	#[inline]
	pub const fn from_u8_lossy(value: u8) -> Self {
		ShortStrucId(value % HIGH_BOUND)
	}
}

/// Parsing error returned when parsing of `ShortStrucId` from a string failed.
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub struct ShortStrucIdParsingError;

impl Error for ShortStrucIdParsingError {}

impl Display for ShortStrucIdParsingError {
	#[inline]
	fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
		write!(f, "Cannot parse ShortStrucId")
	}
}

impl FromStr for ShortStrucId {
	type Err = ShortStrucIdParsingError;

	/// # Examples
	///
	/// ```
	/// # use std::error::Error;
	/// # fn main() -> Result<(), Box<dyn Error>> {
	/// use neodes_codec::line::{ ShortStrucId, ShortStrucIdParsingError};
	/// use std::str::FromStr;
	///
	/// assert_eq!(ShortStrucId::from_str("S13"), Ok(ShortStrucId::from_u8_lossy(13)));
	/// assert_eq!(ShortStrucId::from_str("XXX"), Err(ShortStrucIdParsingError));
	///
	/// # Ok(())
	/// # }
	/// ```
	#[inline]
	fn from_str(s: &str) -> Result<Self, Self::Err> {
		if s.len() == 3 && s.starts_with('S') {
			let i = s[1..3].parse().map_err(|_| ShortStrucIdParsingError)?;
			Ok(ShortStrucId(i))
		} else {
			Err(ShortStrucIdParsingError)
		}
	}
}

macro_rules! impl_numeric_cmp {
    ( $( $t:ty ),* ) => {
        $(
            impl PartialEq<$t> for ShortStrucId {
				#[inline]
                fn eq(&self, other: &$t) -> bool {
                    match u8::try_from(*other) {
                        Ok(val) => self.0 == val,
                        Err(_) => false,
                    }
                }
            }

			impl PartialEq<ShortStrucId> for $t {
				#[inline]
				fn eq(&self, other: &ShortStrucId) -> bool {
					match u8::try_from(*self) {
							Ok(val) => other.0 == val,
							Err(_) => false,
						}
				}
			}

			impl TryFrom<$t> for ShortStrucId {
				type Error = ShortStrucIdParsingError;
				#[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(ShortStrucIdParsingError)
					}
				}
			}
        )*
    }
}

macro_rules! impl_string_cmp {
    ( $( $t:ty ),* ) => {
        $(
            impl PartialEq<$t> for ShortStrucId {
				#[inline]
                fn eq(&self, other: &$t) -> bool {
                    let s = other.as_ref();
                    match ShortStrucId::from_str(s) {
                        Ok(n) => *self == n,
                        Err(_) => false,
                    }
                }
            }

			impl PartialEq<ShortStrucId> for $t {
				#[inline]
				fn eq(&self, other: &ShortStrucId) -> 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::id::short_struc_id::{ShortStrucId, ShortStrucIdParsingError};
	use parameterized::parameterized;
	use quickcheck_macros::quickcheck;
	use std::str::FromStr;

	#[parameterized(input = {
			"X01",
			"SX1",
			"S",
			"S0",
			"S000"
		})]
	fn returns_parsing_errors(input: &str) {
		assert_eq!(ShortStrucId::from_str(input), Err(ShortStrucIdParsingError));
	}

	#[quickcheck]
	fn check_from_lossy(n: u8) {
		assert_eq!(
			ShortStrucId::from_u8_lossy(n),
			ShortStrucId::from_u8_lossy(n % 100)
		);
	}

	#[quickcheck]
	fn check_display(n: u8) {
		assert_eq!(
			ShortStrucId::from_u8_lossy(n).to_string(),
			format!("S{:02}", n % 100)
		);
	}

	#[quickcheck]
	fn check_from_str(n: u8) {
		assert_eq!(
			ShortStrucId::from_str(format!("S{:02}", n % 100).as_str()),
			Ok(ShortStrucId::from_u8_lossy(n))
		);
	}

	#[quickcheck]
	fn check_from_int(n: isize) {
		let short_struc_id = ShortStrucId::try_from(n);
		assert_eq!(short_struc_id.is_ok(), n >= 0 && n < 100);
		if let Ok(short_struc_id) = short_struc_id {
			assert_eq!(n as u8, short_struc_id.0);
		}
	}

	#[test]
	fn test_equals() {
		let id = ShortStrucId::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, "S21");
		assert_eq!(id, "S21".to_string());

		assert_eq!("S21", id);
		assert_eq!("S21".to_string(), id);
	}

	#[parameterized(input = {
		"22",
		"20",
		"121",
		"21.0",
		"abc",
		"",
		"21 ",
		" 21"
	})]
	fn test_not_equals(input: &str) {
		let id = ShortStrucId::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(ShortStrucId::from_u8_lossy(7));
		assert_eq!(ShortStrucId::try_from(7usize), expected);
		assert_eq!(ShortStrucId::try_from(7u8), expected);
		assert_eq!(ShortStrucId::try_from(7u16), expected);
		assert_eq!(ShortStrucId::try_from(7u32), expected);
		assert_eq!(ShortStrucId::try_from(7u64), expected);
		assert_eq!(ShortStrucId::try_from(7u128), expected);

		assert_eq!(ShortStrucId::try_from(7isize), expected);
		assert_eq!(ShortStrucId::try_from(7i8), expected);
		assert_eq!(ShortStrucId::try_from(7i16), expected);
		assert_eq!(ShortStrucId::try_from(7i32), expected);
		assert_eq!(ShortStrucId::try_from(7i64), expected);
		assert_eq!(ShortStrucId::try_from(7i128), expected);
	}

	#[test]
	fn test_try_from_number_fail() {
		let expected = Err(ShortStrucIdParsingError);
		assert_eq!(ShortStrucId::try_from(117usize), expected);
		assert_eq!(ShortStrucId::try_from(117u16), expected);
		assert_eq!(ShortStrucId::try_from(117u32), expected);
		assert_eq!(ShortStrucId::try_from(117u64), expected);
		assert_eq!(ShortStrucId::try_from(117u128), expected);

		assert_eq!(ShortStrucId::try_from(117isize), expected);
		assert_eq!(ShortStrucId::try_from(117i16), expected);
		assert_eq!(ShortStrucId::try_from(117i32), expected);
		assert_eq!(ShortStrucId::try_from(117i64), expected);
		assert_eq!(ShortStrucId::try_from(117i128), expected);
	}
}