use std::fmt;
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct PatchIndex(u32);
impl PatchIndex {
#[must_use]
pub const fn new(idx: u32) -> Self {
Self(idx)
}
#[must_use]
pub const fn get(self) -> u32 {
self.0
}
}
impl fmt::Display for PatchIndex {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl From<u32> for PatchIndex {
fn from(v: u32) -> Self {
Self(v)
}
}
impl From<PatchIndex> for u32 {
fn from(v: PatchIndex) -> Self {
v.0
}
}
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ChunkTag([u8; 4]);
impl ChunkTag {
pub const FHDR: ChunkTag = ChunkTag(*b"FHDR");
pub const APLY: ChunkTag = ChunkTag(*b"APLY");
pub const APFS: ChunkTag = ChunkTag(*b"APFS");
pub const ADIR: ChunkTag = ChunkTag(*b"ADIR");
pub const DELD: ChunkTag = ChunkTag(*b"DELD");
pub const SQPK: ChunkTag = ChunkTag(*b"SQPK");
pub const EOF: ChunkTag = ChunkTag(*b"EOF_");
#[must_use]
pub const fn new(bytes: [u8; 4]) -> Self {
Self(bytes)
}
#[must_use]
pub const fn from_bytes(bytes: &[u8; 4]) -> Self {
Self(*bytes)
}
#[must_use]
pub const fn as_bytes(&self) -> &[u8; 4] {
&self.0
}
#[must_use]
pub fn as_str(&self) -> Option<&str> {
std::str::from_utf8(&self.0).ok()
}
}
impl fmt::Display for ChunkTag {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut buf = [0u8; 4];
for (out, b) in buf.iter_mut().zip(self.0.iter()) {
*out = if b.is_ascii_graphic() || *b == b' ' {
*b
} else if *b == 0 {
b'_'
} else {
b'.'
};
}
f.write_str(std::str::from_utf8(&buf).unwrap_or("????"))
}
}
impl From<[u8; 4]> for ChunkTag {
fn from(v: [u8; 4]) -> Self {
Self(v)
}
}
impl From<ChunkTag> for [u8; 4] {
fn from(v: ChunkTag) -> Self {
v.0
}
}
impl PartialEq<[u8; 4]> for ChunkTag {
fn eq(&self, other: &[u8; 4]) -> bool {
&self.0 == other
}
}
impl PartialEq<ChunkTag> for [u8; 4] {
fn eq(&self, other: &ChunkTag) -> bool {
self == &other.0
}
}
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SchemaVersion(u32);
impl SchemaVersion {
#[must_use]
pub const fn new(v: u32) -> Self {
Self(v)
}
#[must_use]
pub const fn get(self) -> u32 {
self.0
}
#[must_use]
pub const fn compatible_with(self, other: Self) -> bool {
self.0 == other.0
}
#[must_use]
pub const fn wrapping_add(self, rhs: u32) -> Self {
Self(self.0.wrapping_add(rhs))
}
#[must_use]
pub const fn wrapping_sub(self, rhs: u32) -> Self {
Self(self.0.wrapping_sub(rhs))
}
}
impl fmt::Display for SchemaVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl From<u32> for SchemaVersion {
fn from(v: u32) -> Self {
Self(v)
}
}
impl From<SchemaVersion> for u32 {
fn from(v: SchemaVersion) -> Self {
v.0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn patch_index_round_trip() {
let p = PatchIndex::new(7);
assert_eq!(p.get(), 7);
assert_eq!(u32::from(p), 7);
assert_eq!(PatchIndex::from(7u32), p);
}
#[test]
fn chunk_tag_display_printable_ascii() {
assert_eq!(format!("{}", ChunkTag::SQPK), "SQPK");
assert_eq!(format!("{}", ChunkTag::EOF), "EOF_");
assert_eq!(format!("{}", ChunkTag::new([0, b'A', b'B', b'C'])), "_ABC");
assert_eq!(
format!("{}", ChunkTag::new([0xff, b'A', b'B', b'C'])),
".ABC"
);
}
#[test]
fn chunk_tag_constants_match_ascii() {
assert_eq!(ChunkTag::FHDR.as_bytes(), b"FHDR");
assert_eq!(ChunkTag::SQPK.as_bytes(), b"SQPK");
assert_eq!(ChunkTag::EOF.as_bytes(), b"EOF_");
}
#[test]
fn chunk_tag_eq_array() {
let tag = ChunkTag::SQPK;
assert!(tag == *b"SQPK");
assert!(*b"SQPK" == tag);
}
#[test]
fn schema_version_compat_is_strict_equality() {
let a = SchemaVersion::new(1);
let b = SchemaVersion::new(1);
let c = SchemaVersion::new(2);
assert!(a.compatible_with(b));
assert!(!a.compatible_with(c));
}
}