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 end of a field ID
/// i.e. the `001` 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 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 {
	/// Creates a ShortFieldId from a number. Truncates the decimals to the last 3
	///
	/// # Examples
	///
	/// ```
	/// use neodes_codec::line::ShortFieldId;
	///
	/// assert_eq!(ShortFieldId::from_u16_lossy(0234).to_string(), "234");
	/// assert_eq!(ShortFieldId::from_u16_lossy(1234).to_string(), "234");
	/// ```
	#[inline]
	pub const fn from_u16_lossy(value: u16) -> Self {
		ShortFieldId(value % HIGH_BOUND)
	}
}

impl FromStr for ShortFieldId {
	type Err = ShortFieldIdParsingError;

	/// # Examples
	///
	/// ```
	/// # use std::error::Error;
	/// # fn main() -> Result<(), Box<dyn Error>> {
	/// use neodes_codec::line::{ ShortFieldId, ShortFieldIdParsingError};
	/// use std::str::FromStr;
	///
	/// assert_eq!(ShortFieldId::from_str("123"), Ok(ShortFieldId::from_u16_lossy(123)));
	/// assert_eq!(ShortFieldId::from_str("XXX"), Err(ShortFieldIdParsingError));
	///
	/// # Ok(())
	/// # }
	/// ```
	#[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)
		}
	}
}

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