lowlevel-types 1.1.0

Rust types for working with low level data structures, such as ASCII characters and strings
Documentation
use std::fmt::Display;

use serde::{Deserialize, Deserializer, Serialize, de::Visitor};

use crate::ascii::{char::Char, error::ASCIIError};

/// A fixed length ASCII string of length N
#[derive(Clone, Debug, Hash, PartialOrd)]
pub struct FixedLengthString<const N: usize>(pub [Char; N]);

impl<const N: usize> FixedLengthString<N> {
	/// Constructs a new instance
	pub fn new() -> Self {
		Self([Char(0x00); N])
	}

	/// Returns the length of the string (in bytes)
	pub fn len(&self) -> usize {
		self.0.len()
	}

	/// Returns the raw bytes of the string
	pub fn as_bytes(&self) -> [u8; N] {
		self.0.map(|c| u8::from(c)).clone()
	}
}

impl<const N: usize> Display for FixedLengthString<N> {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		self.0
			.iter()
			.for_each(|c| f.write_str(c.char().to_string().as_str()).unwrap());
		Ok(())
	}
}

impl<const N: usize> PartialEq<FixedLengthString<N>> for FixedLengthString<N> {
	fn eq(&self, other: &FixedLengthString<N>) -> bool {
		self.0 == other.0
	}
}

impl<const N: usize> PartialEq<&str> for FixedLengthString<N> {
	fn eq(&self, other: &&str) -> bool {
		let s = String::from(self.clone());
		s.as_str() == *other
	}
}

impl<const N: usize> From<Vec<u8>> for FixedLengthString<N> {
	fn from(value: Vec<u8>) -> Self {
		FixedLengthString::<N>::try_from(value.as_slice()).unwrap()
	}
}

impl<const N: usize> TryFrom<&[u8]> for FixedLengthString<N> {
	type Error = ASCIIError;

	fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
		if value.len() > N {
			Err(ASCIIError {
				message: format!(
					"array &[u8] of length {} too long for AString<{}>",
					value.len(),
					N
				),
			})
		} else {
			let v: &mut [Char; N] = &mut [Char(0x00); N];
			for i in 0..value.len() {
				v[i] = Char(value[i].clone());
			}
			Ok(FixedLengthString(v.clone()))
		}
	}
}

impl<const N: usize> From<[u8; N]> for FixedLengthString<N> {
	fn from(value: [u8; N]) -> Self {
		FixedLengthString(value.map(|c| Char(c)))
	}
}

impl<const N: usize> TryFrom<&String> for FixedLengthString<N> {
	type Error = ASCIIError;

	fn try_from(value: &String) -> Result<Self, Self::Error> {
		FixedLengthString::<N>::try_from(value.as_str())
	}
}

impl<const N: usize> TryFrom<&str> for FixedLengthString<N> {
	type Error = ASCIIError;

	fn try_from(value: &str) -> Result<Self, Self::Error> {
		if !value.is_ascii() {
			Err(ASCIIError {
				message: format!("attempt to convert an Unicode string to an AString"),
			})
		} else {
			let v: &mut Vec<u8> = &mut Vec::new();
			for c in value.chars() {
				v.push(c as u8);
			}
			Ok(FixedLengthString::from(v.clone()))
		}
	}
}

impl<const N: usize> From<FixedLengthString<N>> for String {
	fn from(value: FixedLengthString<N>) -> Self {
		let s = &mut String::new();
		for c in value.0 {
			s.push(c.char());
		}
		s.clone()
	}
}

#[cfg(feature = "serde")]
impl<const N: usize> Serialize for FixedLengthString<N> {
	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
	where
		S: serde::Serializer,
	{
		let bytes = &self.as_bytes() as &[u8];
		serializer.serialize_bytes(bytes)
	}
}

#[cfg(feature = "serde")]
impl<'de, const N: usize> Deserialize<'de> for FixedLengthString<N> {
	fn deserialize<D>(deserializer: D) -> Result<FixedLengthString<N>, D::Error>
	where
		D: Deserializer<'de>,
	{
		deserializer.deserialize_bytes(AStringVisitor::<N>)
	}
}

#[cfg(feature = "serde")]
pub struct AStringVisitor<const N: usize>;

#[cfg(feature = "serde")]
impl<'de, const N: usize> Visitor<'de> for AStringVisitor<N> {
	type Value = FixedLengthString<N>;

	fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
		formatter.write_str(format!("an array of {} ASCII bytes", N).as_str())
	}

	fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
	where
		E: serde::de::Error,
	{
		if v.len() != N {
			Err(serde::de::Error::invalid_length(v.len(), &self))
		} else {
			Ok(FixedLengthString::try_from(v).unwrap())
		}
	}
}

#[cfg(test)]
mod tests {
	use crate::ascii::{self, char::Char, fixedlengthstring::FixedLengthString};

	#[test]
	fn test_create() {
		let s1 = FixedLengthString([Char(0x00)]);
		assert_eq!(s1.len(), 1);
		assert_eq!(s1, "\0");
		let s: FixedLengthString<10> = FixedLengthString::new();
		assert_eq!(s.len(), 10);
	}

	#[test]
	fn test_from_vec_of_u8() {
		let v: Vec<u8> = vec![0x41 as u8];
		let s1: FixedLengthString<1> = FixedLengthString::from(v);
		assert_eq!(s1, "A");
	}

	#[test]
	fn test_from_array_of_u8() {
		let s1: FixedLengthString<1> = FixedLengthString::try_from(&[0x41 as u8] as &[u8]).unwrap();
		assert_eq!(s1, "A");
		let s2: FixedLengthString<1> = FixedLengthString::from([0x41 as u8; 1]);
		assert_eq!(s2, "A");
		assert!(FixedLengthString::<1>::try_from(&[0x41 as u8; 2] as &[u8]).is_err());
	}

	#[test]
	fn test_from_string() {
		let s1: FixedLengthString<1> = FixedLengthString::try_from(&String::from("A")).unwrap();
		assert_eq!(s1, "A");
		assert!(FixedLengthString::<1>::try_from(&String::from("👿")).is_err());
	}

	#[test]
	fn test_string_try_from_astring() {
		let s4 = String::from(FixedLengthString::<1>::try_from("A").unwrap());
		assert_eq!(s4, "A");
	}

	#[test]
	fn test_display() {
		let s = FixedLengthString([ascii::Char(0x41); 1]);
		assert_eq!(format!("{}", s), "A");
	}
}