neodes_codec 0.1.3

Library to read and write data from DSN files as described in the NeoDes norm.
Documentation
use crate::line::{
	BlockId, BlockIdParseError, ShortBlockId, ShortBlockIdParsingError, ShortFieldId,
	ShortFieldIdParsingError, ShortGroupId, ShortGroupIdParsingError, ShortStrucId,
	ShortStrucIdParsingError,
};
use std::error::Error;
use std::fmt::{Display, Formatter};
use std::str::FromStr;

/// Structure representing the field ID
/// i.e. `S21.G00.42.001`
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
#[cfg_attr(
	feature = "serde",
	derive(serde_with::DeserializeFromStr, serde_with::SerializeDisplay)
)]
pub struct FieldId {
	/// Block part of the field ID
	block_id: BlockId,

	/// End part of the field ID
	short_field_id: ShortFieldId,
}

impl FieldId {
	/// Block part of the field ID
	#[inline]
	pub fn block_id(&self) -> BlockId {
		self.block_id
	}

	/// End part of the field ID
	#[inline]
	pub fn short_field_id(&self) -> ShortFieldId {
		self.short_field_id
	}

	/// Creates a new FieldId from its parts
	#[inline]
	pub const fn new(
		short_struc_id: ShortStrucId,
		short_group_id: ShortGroupId,
		short_block_id: ShortBlockId,
		short_field_id: ShortFieldId,
	) -> Self {
		Self {
			block_id: BlockId::new(short_struc_id, short_group_id, short_block_id),
			short_field_id,
		}
	}
}

impl Display for FieldId {
	#[inline]
	fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
		f.write_fmt(format_args!("{}.{:03}", self.block_id, self.short_field_id))
	}
}

impl FromStr for FieldId {
	type Err = FieldIdParseError;

	/// # Examples
	///
	/// ```
	/// # use std::error::Error;
	/// # fn main() -> Result<(), Box<dyn Error>> {
	/// use neodes_codec::line::{FieldId, FieldIdParseError, ShortBlockId, ShortFieldId, ShortGroupId, ShortStrucId};
	/// use std::str::FromStr;
	///
	/// assert_eq!(
	///     FieldId::from_str("S21.G00.01.123"),
	///     Ok(FieldId::new(
	///         ShortStrucId::from_u8_lossy(21),
	///         ShortGroupId::from_u8_lossy(0),
	///         ShortBlockId::from_u8_lossy(1),
	///         ShortFieldId::from_u16_lossy(123))
	///     )
	/// );
	/// assert_eq!(FieldId::from_str("S21.G00.01123"), Err(FieldIdParseError::Format));
	///
	/// # Ok(())
	/// # }
	/// ```
	#[inline]
	fn from_str(s: &str) -> Result<Self, Self::Err> {
		let (block_s, field_s) = s.rsplit_once('.').ok_or(FieldIdParseError::Format)?;
		Ok(FieldId {
			block_id: BlockId::from_str(block_s)?,
			short_field_id: ShortFieldId::from_str(field_s)?,
		})
	}
}

/// Parsing error returned when parsing of `FieldId` from a string failed.
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub enum FieldIdParseError {
	/// Field id was badly formed (missing dots)
	Format,

	/// Structure id was unreadable
	Structure,

	/// Group id was unreadable
	Group,

	/// Block id was unreadable
	Block,

	/// Field id (the last part) was unreadable
	Field,
}

impl Error for FieldIdParseError {}

impl From<ShortStrucIdParsingError> for FieldIdParseError {
	#[inline]
	fn from(_: ShortStrucIdParsingError) -> Self {
		FieldIdParseError::Structure
	}
}

impl From<ShortGroupIdParsingError> for FieldIdParseError {
	#[inline]
	fn from(_: ShortGroupIdParsingError) -> Self {
		FieldIdParseError::Group
	}
}

impl From<ShortBlockIdParsingError> for FieldIdParseError {
	#[inline]
	fn from(_: ShortBlockIdParsingError) -> Self {
		FieldIdParseError::Block
	}
}

impl From<ShortFieldIdParsingError> for FieldIdParseError {
	#[inline]
	fn from(_: ShortFieldIdParsingError) -> Self {
		FieldIdParseError::Field
	}
}

impl From<BlockIdParseError> for FieldIdParseError {
	#[inline]
	fn from(value: BlockIdParseError) -> Self {
		match value {
			BlockIdParseError::Format => FieldIdParseError::Format,
			BlockIdParseError::Structure => FieldIdParseError::Structure,
			BlockIdParseError::Group => FieldIdParseError::Group,
			BlockIdParseError::Block => FieldIdParseError::Block,
		}
	}
}

impl Display for FieldIdParseError {
	#[inline]
	fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
		match self {
			FieldIdParseError::Format => {
				write!(f, "Wrong Field ID format")
			}
			FieldIdParseError::Structure => {
				write!(f, "Cannot parse structure ID")
			}
			FieldIdParseError::Group => {
				write!(f, "Cannot parse group ID")
			}
			FieldIdParseError::Block => {
				write!(f, "Cannot parse block ID")
			}
			FieldIdParseError::Field => {
				write!(f, "Cannot parse field ID")
			}
		}
	}
}

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

			impl PartialEq<FieldId> for $t {
				#[inline]
				fn eq(&self, other: &FieldId) -> bool {
					other == self
				}
			}
        )*
    }
}

impl_string_cmp!(str, &str, String);

#[cfg(test)]
mod tests {
	use std::str::FromStr;

	use crate::line::id::field_id::FieldId;
	use crate::line::{ShortBlockId, ShortFieldId, ShortGroupId, ShortStrucId};
	use parameterized::parameterized;

	#[parameterized(input = {
			"S21",
			"S20.001",
			"S21.G00.00.0X1",
		})]
	fn returns_parsing_errors(input: &str) {
		assert!(FieldId::from_str(input).is_err());
	}

	#[test]
	fn test_string_equals() {
		let id = FieldId::new(
			ShortStrucId::from_u8_lossy(21),
			ShortGroupId::from_u8_lossy(0),
			ShortBlockId::from_u8_lossy(13),
			ShortFieldId::from_u16_lossy(123),
		);

		assert_eq!(id, "S21.G00.13.123");
		assert_eq!(id, "S21.G00.13.123".to_string());

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

	#[parameterized(input = {
		"S21.G00.13.124",
		"S21.G00.14.123",
		"S22.G00.13.123",
		"S21.G01.13.123",
		"S21.G00.13",
		"abc",
		"",
		"S21.G00.13.123 ",
		" S21.G00.13.123"
	})]
	fn test_string_not_equals(input: &str) {
		let id = FieldId::new(
			ShortStrucId::from_u8_lossy(21),
			ShortGroupId::from_u8_lossy(0),
			ShortBlockId::from_u8_lossy(13),
			ShortFieldId::from_u16_lossy(123),
		);

		assert_ne!(id, input);
		assert_ne!(input, id);
		assert_ne!(id, input.to_string());
		assert_ne!(input.to_string(), id);
	}
}