use std::fmt::Display;
use std::fmt::Formatter;
use crate::prelude::*;
use crate::util::fmt::hexdump;
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct ChunkName {
bytes: [u8; 4],
}
impl ChunkName {
pub const fn new(name: &'static str) -> Self {
assert!(name.len() == 4, "Expected string of length 4");
let bytes = name.as_bytes();
let mut i = 0;
while i < 4 {
assert!(
validate_char(bytes[i]),
"Expected chunk name to only consist of uppercase ASCII letters and digits",
);
i += 1;
}
let bytes: [u8; 4] = [bytes[0], bytes[1], bytes[2], bytes[3]];
Self { bytes }
}
pub fn from_bytes(bytes: [u8; 4]) -> Result<Self> {
let valid: bool = bytes.iter().all(|&byte| validate_char(byte));
if valid {
return Ok(Self { bytes });
}
let hexdump = hexdump(&bytes);
let msg = "only consist of uppercase ASCII digits and letters";
if let Some(string) = try_display(&bytes) {
bail!("Expected chunk name {string:?} [{hexdump}] to {msg}");
}
bail!("Expected chunk name [{hexdump}] to {msg}");
}
#[inline]
#[must_use]
pub const fn as_bytes(self) -> [u8; 4] {
self.bytes
}
#[inline]
#[must_use]
pub const fn as_str(&self) -> &str {
unsafe { str::from_utf8_unchecked(&self.bytes) }
}
}
impl Display for ChunkName {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
impl std::fmt::Debug for ChunkName {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "'{self}'")
}
}
#[inline]
const fn validate_char(byte: u8) -> bool {
matches!(byte, b'A'..=b'Z' | b'0'..=b'9')
}
#[inline]
fn try_display(chunk_name: &[u8; 4]) -> Option<&str> {
if chunk_name.iter().all(u8::is_ascii_control) {
return None;
}
str::from_utf8(chunk_name).ok()
}