//! Version information for the file container format, semver-like internal version format. use super::*; use std::{ fmt, mem::size_of, error, }; /// Represents other states of this container format version #[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd, Copy, Serialize, Deserialize)] #[repr(u8)] pub enum Tag { None =0, Prerelease =1, Unstable =2, } /// The error used by `Tag` and `Version` when they parse invalid input. #[derive(Debug)] pub struct ParsingError; impl error::Error for ParsingError{} impl fmt::Display for ParsingError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "An attempt was made to parse invalid Version input") } } impl Tag { /// Try to parse a `u8` as `Tag`. fn try_from_u8(from: u8) -> Result { macro_rules! branches { ($($num:path),*) => { match from { $( x if x == $num as u8 => $num, )* _ => return Err(ParsingError), } } } Ok(branches! { Self::Prerelease, Self::Unstable, Self::None }) } } impl fmt::Display for Tag { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Prerelease => write!(f, "*"), Self::Unstable => write!(f, "~"), _ => Ok(()), } } } impl Default for Tag { #[inline] fn default() -> Self { Self::None } } /// Represents a container version number in this format: `Major.Minor.Bugfix.Tag` /// /// Should adhear to this principle /// * Major - If different, fail parsing. /// * Minor - If higher that current, fail parsing. /// * Bugfix - If higher than current, warn user. /// * Tag - Unused, but may represent things like prerelease or unstable #[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd, Copy, Default, Serialize, Deserialize)] #[repr(align(4))] pub struct Version(u8,u8,u8,Tag); static_assert!(std::mem::size_of::() == std::mem::size_of::<[u8; 4]>(), "Size of version != size of [u8; 4]"); static_assert!(std::mem::size_of::() == std::mem::size_of::(), "Size of version != size of u32"); static_assert!(std::mem::size_of::() == 4, "Size of Version (u32) != 4"); static_assert!(std::mem::align_of::() == std::mem::align_of::(), "Align of version != align of u32 with `repr(align(4))`"); impl fmt::Display for Version { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}.{}.{}{}", self.0, self.1, self.2, self.3) } } impl Version { /// Is this read version compatable with a system of version `other`? pub fn is_compat(&self, other: &Self) -> bool { self.0 == other.0 && self.1 <= other.1 } /// Should we warn about parsing with this version when system version is `other`? pub fn should_warn(&self, other: &Self) -> bool { self.2 > other.2 || self.3 == Tag::Unstable } /// Create a new instance of `Version`. pub const fn new(major: u8, minor: u8, bugfix: u8, tag: Tag) -> Self { Self(major,minor,bugfix,tag) } /// Get binary representation (4 bytes) pub fn as_bytes(&self) -> &[u8] { unsafe { std::slice::from_raw_parts(self as *const Self as *const u8, std::mem::size_of::()) } } /// Try to create an instance from bytes pub fn try_from_bytes(bytes: [u8; 4]) -> Result { Ok(Self::new(bytes[0], bytes[1], bytes[2], Tag::try_from(bytes[3])?)) } /// Uncheckedly create a version from bytes #[cfg(nightly)] #[inline(always)] pub const unsafe fn from_bytes_unchecked(bytes: [u8; 4]) -> Self { std::mem::transmute(bytes) } /// Uncheckedly create a version from bytes #[cfg(not(nightly))] #[inline(always)] pub unsafe fn from_bytes_unchecked(bytes: [u8; 4]) -> Self { let mut s = Self::default(); debug_assert_eq!(bytes::copy_slice(s.as_bytes_mut(), &bytes[..]), std::mem::size_of::()); s } /// Convert into 4 byte array #[inline(always)] pub const fn to_bytes(self) -> [u8; 4] { nightly! { if { unsafe {std::mem::transmute(self)} // we already statically asserted this to be okay } else { [self.0, self.1, self.2, self.3 as u8] } } } /// Get mutable binary representation (4 bytes) pub unsafe fn as_bytes_mut(&mut self) -> &mut [u8] { std::slice::from_raw_parts_mut(self as *mut Version as *mut u8, std::mem::size_of::()) } /// Encode as u32 #[inline] pub fn as_u32(&self) -> u32 { nightly! { if { self.to_u32() } else { debug_assert_eq!(size_of::(), size_of::()); unsafe{*(self as *const Self as *const u32)} } } } /// Convert this `Version` directly to `u32`. #[inline(always)] pub const fn to_u32(self) -> u32 { unsafe { std::mem::transmute(self) //we have already statically asserted that this is safe } } /// Decode u32 as self, assuming `Tag` is valid. #[cfg(nightly)] #[inline(always)] pub const unsafe fn from_u32_unchecked(from: u32) -> Self { std::mem::transmute(from) } #[cfg(not(nightly))] #[inline(always)] pub unsafe fn from_u32_unchecked(from:u32) -> Self { *(&from as *const u32 as *const Self) } /// Try to parse a `u32` encoded `Version`. pub fn try_from_u32(from: u32) -> Result { debug_assert_eq!(size_of::(), size_of::()); debug_assert_eq!(size_of::(), 4); let parts = &from as *const u32 as *const u8; macro_rules! index { ($array:ident, $index:expr) => { unsafe{*($array.offset($index))} } } Ok(Self::new(index!(parts, 0), index!(parts, 1), index!(parts, 2), Tag::try_from_u8(index!(parts, 3))?)) } } impl TryFrom for Tag { type Error = ParsingError; #[inline] fn try_from(from: u8) -> Result { Self::try_from_u8(from) } } impl From for u8 { #[inline] fn from(from: Tag) -> Self { from as u8 } } impl TryFrom for Version { type Error = ParsingError; #[inline] fn try_from(from: u32) -> Result { Self::try_from_u32(from) } } impl From for u32 { #[inline] fn from(from: Version) -> Self { from.as_u32() } } #[cfg(test)] mod tests { use super::*; #[test] fn vers_encode() { let version1 = Version::new(1,0,0,Tag::Unstable); let version2 = Version::try_from_u32(version1.as_u32()).unwrap(); let version3 = unsafe{Version::from_u32_unchecked(version1.as_u32())}; assert_eq!(format!("{}", version1), "1.0.0~"); assert_eq!(version1, version2); assert_eq!(version2, version3); } }