#[non_exhaustive]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ManufactureDate {
Manufactured {
week: Option<u8>,
year: u16,
},
ModelYear(u16),
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(try_from = "[u8; 3]", into = "[u8; 3]"))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ManufacturerId(pub [u8; 3]);
impl ManufacturerId {
pub fn from_ascii(bytes: [u8; 3]) -> Option<Self> {
if bytes.iter().all(|&b| b.is_ascii_uppercase()) {
Some(Self(bytes))
} else {
None
}
}
pub fn as_str(&self) -> &str {
let valid = self.is_valid();
debug_assert!(
valid,
"ManufacturerId invariant violated: bytes must be ASCII uppercase A-Z, got {:?}",
self.0
);
if valid {
core::str::from_utf8(&self.0).unwrap_or("")
} else {
""
}
}
fn is_valid(&self) -> bool {
self.0.iter().all(u8::is_ascii_uppercase)
}
}
impl TryFrom<[u8; 3]> for ManufacturerId {
type Error = &'static str;
fn try_from(bytes: [u8; 3]) -> Result<Self, Self::Error> {
Self::from_ascii(bytes).ok_or("ManufacturerId bytes must be ASCII uppercase A-Z")
}
}
impl From<ManufacturerId> for [u8; 3] {
fn from(id: ManufacturerId) -> Self {
id.0
}
}
impl core::fmt::Display for ManufacturerId {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
if self.is_valid() {
f.write_str(self.as_str())
} else {
write!(f, "{}", self.0.escape_ascii())
}
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MonitorString(pub [u8; 13]);
impl MonitorString {
pub fn as_str(&self) -> &str {
let bytes = &self.0;
let end = bytes.iter().position(|&b| b == 0x0A).unwrap_or(bytes.len());
let trimmed = match bytes[..end].iter().rposition(|&b| b != b' ') {
Some(i) => &bytes[..=i],
None => &[][..],
};
core::str::from_utf8(trimmed).unwrap_or("")
}
}
impl core::fmt::Display for MonitorString {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(self.as_str())
}
}
impl core::ops::Deref for MonitorString {
type Target = str;
fn deref(&self) -> &str {
self.as_str()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(any(feature = "alloc", feature = "std"))]
use crate::alloc::string::ToString;
#[test]
fn manufacturer_id_valid_ascii_uppercase() {
let id = ManufacturerId::from_ascii(*b"DEL").unwrap();
assert_eq!(id.as_str(), "DEL");
}
#[test]
fn manufacturer_id_rejects_lowercase() {
assert!(ManufacturerId::from_ascii(*b"del").is_none());
}
#[test]
fn manufacturer_id_rejects_digit() {
assert!(ManufacturerId::from_ascii(*b"D3L").is_none());
}
#[test]
#[cfg(any(feature = "alloc", feature = "std"))]
fn manufacturer_id_display() {
let id = ManufacturerId::from_ascii(*b"SAM").unwrap();
assert_eq!(id.to_string(), "SAM");
}
#[test]
fn manufacturer_id_try_from_checks_invariant() {
assert_eq!(
ManufacturerId::try_from(*b"GSM"),
Ok(ManufacturerId(*b"GSM"))
);
assert!(ManufacturerId::try_from([0xFF, 0x00, 0x00]).is_err());
}
#[test]
#[cfg(any(feature = "alloc", feature = "std"))]
fn manufacturer_id_display_escapes_invalid_bytes() {
assert_eq!(
ManufacturerId([0xFF, 0x00, 0x00]).to_string(),
r"\xff\x00\x00"
);
assert_eq!(ManufacturerId(*b"del").to_string(), "del");
}
#[test]
#[cfg(not(debug_assertions))]
fn manufacturer_id_as_str_invalid_bytes_is_empty_in_release() {
assert_eq!(ManufacturerId([0xFF, 0x00, 0x00]).as_str(), "");
}
#[test]
#[cfg(debug_assertions)]
#[should_panic(expected = "ManufacturerId invariant violated")]
fn manufacturer_id_as_str_invalid_bytes_panics_in_debug() {
let _ = ManufacturerId([0xFF, 0x00, 0x00]).as_str();
}
#[test]
#[cfg(all(feature = "serde", any(feature = "alloc", feature = "std")))]
fn manufacturer_id_serde_wire_format_is_byte_array() {
let id = ManufacturerId(*b"DEL");
let json = serde_json::to_string(&id).unwrap();
assert_eq!(json, "[68,69,76]");
assert_eq!(serde_json::from_str::<ManufacturerId>(&json).unwrap(), id);
}
#[test]
#[cfg(feature = "serde")]
fn manufacturer_id_deserialize_rejects_invalid_bytes() {
assert!(serde_json::from_str::<ManufacturerId>("[255,0,0]").is_err());
assert!(serde_json::from_str::<ManufacturerId>("[100,101,108]").is_err()); }
#[test]
fn monitor_string_strips_terminator_and_spaces() {
let mut buf = [b' '; 13];
let name = b"DELL U2722D";
buf[..name.len()].copy_from_slice(name);
buf[name.len()] = 0x0A;
assert_eq!(MonitorString(buf).as_str(), "DELL U2722D");
}
#[test]
fn monitor_string_no_terminator_strips_trailing_spaces() {
let mut buf = [b' '; 13];
buf[..3].copy_from_slice(b"ABC");
assert_eq!(MonitorString(buf).as_str(), "ABC");
}
#[test]
fn monitor_string_all_padding_gives_empty() {
assert_eq!(MonitorString([b' '; 13]).as_str(), "");
}
#[test]
fn monitor_string_deref() {
let mut buf = [b' '; 13];
buf[..3].copy_from_slice(b"LEN");
buf[3] = 0x0A;
let ms = MonitorString(buf);
let s: &str = &ms;
assert_eq!(s, "LEN");
}
#[test]
#[cfg(any(feature = "alloc", feature = "std"))]
fn monitor_string_display() {
let mut buf = [b' '; 13];
buf[..3].copy_from_slice(b"GSM");
buf[3] = 0x0A;
assert_eq!(MonitorString(buf).to_string(), "GSM");
}
}