neodes_codec 0.1.3

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

/// Structure representing the three first parts of a field ID
/// i.e. the `S21.G00.42` in `S21.G00.42.001`
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
#[cfg_attr(
	feature = "serde",
	derive(serde_with::DeserializeFromStr, serde_with::SerializeDisplay)
)]
pub struct BlockId {
	short_struc_id: ShortStrucId,
	short_group_id: ShortGroupId,
	short_block_id: ShortBlockId,
}

impl BlockId {
	/// Creates a BlockId from numbers. Truncates the decimals to the last 2
	#[inline]
	pub const fn new(
		short_struc_id: ShortStrucId,
		short_group_id: ShortGroupId,
		short_block_id: ShortBlockId,
	) -> BlockId {
		BlockId {
			short_struc_id,
			short_group_id,
			short_block_id,
		}
	}

	/// Short Structure ID
	///
	/// # Examples
	///
	/// ```
	/// # use std::error::Error;
	/// # fn main() -> Result<(), Box<dyn Error>> {
	/// use neodes_codec::line::{BlockId, ShortStrucId};
	/// use std::str::FromStr;
	///
	/// let block_id = BlockId::from_str("S21.G00.00")?;
	/// assert_eq!(block_id.short_struc_id(), ShortStrucId::from_u8_lossy(21));
	///
	/// # Ok(())
	/// # }
	/// ```
	#[inline]
	pub fn short_struc_id(&self) -> ShortStrucId {
		self.short_struc_id
	}

	/// # Examples
	///
	/// ```
	/// # use std::error::Error;
	/// # fn main() -> Result<(), Box<dyn Error>> {
	/// use neodes_codec::line::{BlockId, ShortGroupId };
	/// use std::str::FromStr;
	///
	/// let block_id = BlockId::from_str("S21.G01.13")?;
	/// assert_eq!(block_id.short_group_id(), ShortGroupId::from_u8_lossy(1));
	///
	/// # Ok(())
	/// # }
	/// ```
	#[inline]
	pub fn short_group_id(&self) -> ShortGroupId {
		self.short_group_id
	}

	/// Short Structure ID
	/// ```
	/// # use std::error::Error;
	/// # fn main() -> Result<(), Box<dyn Error>> {
	/// use neodes_codec::line::{BlockId, ShortBlockId};
	/// use std::str::FromStr;
	///
	/// let block_id = BlockId::from_str("S21.G00.13")?;
	/// assert_eq!(block_id.short_block_id(), ShortBlockId::from_u8_lossy(13));
	///
	/// # Ok(())
	/// # }
	/// ```
	#[inline]
	pub fn short_block_id(&self) -> ShortBlockId {
		self.short_block_id
	}

	/// Create Field ID from Block ID
	/// ```
	/// # use std::error::Error;
	/// # fn main() -> Result<(), Box<dyn Error>> {
	/// use neodes_codec::line::{BlockId, ShortFieldId, FieldId};
	/// use std::str::FromStr;
	///
	/// let block_id = BlockId::from_str("S21.G00.13")?;
	/// let short_field_id = ShortFieldId::from_u16_lossy(111);
	///
	/// let field_id = block_id.with_field_id(short_field_id);
	///
	/// assert_eq!(field_id, FieldId::from_str("S21.G00.13.111")?);
	///
	/// # Ok(())
	/// # }
	/// ```
	#[inline]
	pub fn with_field_id(&self, field_id: ShortFieldId) -> FieldId {
		FieldId::new(
			self.short_struc_id,
			self.short_group_id,
			self.short_block_id,
			field_id,
		)
	}
}

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

impl FromStr for BlockId {
	type Err = BlockIdParseError;

	/// ```
	/// # use std::error::Error;
	/// # fn main() -> Result<(), Box<dyn Error>> {
	/// use neodes_codec::line::{BlockId, BlockIdParseError, ShortBlockId, ShortFieldId, ShortGroupId, ShortStrucId};
	/// use std::str::FromStr;
	///
	/// assert_eq!(
	///     BlockId::from_str("S21.G00.01"),
	///     Ok(BlockId::new(
	///         ShortStrucId::from_u8_lossy(21),
	///         ShortGroupId::from_u8_lossy(0),
	///         ShortBlockId::from_u8_lossy(1))
	///     )
	/// );
	/// assert_eq!(BlockId::from_str("S21.G00"), Err(BlockIdParseError::Format));
	///
	/// # Ok(())
	/// # }
	/// ```
	#[inline]
	fn from_str(s: &str) -> Result<Self, Self::Err> {
		let mut split_n = s.splitn(3, '.');

		let next_split = split_n.next().ok_or(BlockIdParseError::Format)?;
		let short_struc_id = ShortStrucId::from_str(next_split)?;

		let next_split = split_n.next().ok_or(BlockIdParseError::Format)?;
		let short_group_id = ShortGroupId::from_str(next_split)?;

		let next_split = split_n.next().ok_or(BlockIdParseError::Format)?;
		let short_block_id = ShortBlockId::from_str(next_split)?;

		Ok(BlockId {
			short_struc_id,
			short_group_id,
			short_block_id,
		})
	}
}

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

	/// Structure id was unreadable
	Structure,

	/// Group id was unreadable
	Group,

	/// Block id was unreadable
	Block,
}

impl Error for BlockIdParseError {}

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

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

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

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

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

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

impl_string_cmp!(str, &str, String);

#[cfg(test)]
mod tests {
	use crate::line::{BlockId, ShortBlockId, ShortGroupId, ShortStrucId};
	use parameterized::parameterized;
	use std::str::FromStr;

	#[parameterized(input = {
			"S215G00.00",
			"S21.G00|00",
			"S2X.G00.00",
			"S21.G0X.00",
			"S21.G00.0X",
			"S21.G00.00.",
			"S21.G00.00.001"
		})]
	fn returns_parsing_errors(input: &str) {
		assert!(BlockId::from_str(input).is_err());
	}

	#[test]
	fn can_parse_string() {
		assert_eq!(
			BlockId::from_str("S21.G00.00"),
			Ok(BlockId {
				short_struc_id: ShortStrucId::from_u8_lossy(21),
				short_group_id: ShortGroupId::from_u8_lossy(0),
				short_block_id: ShortBlockId::from_u8_lossy(0),
			})
		);
	}

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

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

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

	#[parameterized(input = {
		"S21.G00.14",
		"S22.G00.13",
		"S21.G01.13",
		"S21.G00.13.001",
		"abc",
		"",
		"S21.G00.13 ",
		" S21.G00.13"
	})]
	fn test_string_not_equals(input: &str) {
		let id = BlockId::new(
			ShortStrucId::from_u8_lossy(21),
			ShortGroupId::from_u8_lossy(0),
			ShortBlockId::from_u8_lossy(13),
		);

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