use { crate::{ error::Error, extended::Extended, gitrev::GitRev, prerelease::PreRelease, rapid::Rapid, simple::Simple, MAX_U12, }, serde::{Deserialize, Serialize}, std::{cmp::Ordering, fmt, str::FromStr}, }; #[derive(Clone, Copy, Debug, Deserialize, Serialize)] pub struct SemVer { pub major: u16, pub minor: u16, pub patch: u16, pub pre: PreRelease, } impl fmt::Display for SemVer { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}.{}.{}", self.major, self.minor, self.patch)?; match self.pre { PreRelease::None => Ok(()), p => write!(f, "_{p}"), } } } impl FromStr for SemVer { type Err = Error; fn from_str(s: &str) -> Result { let (s, pre) = match s.split_once('_') { Some((a, b)) => (a, b.parse::()?), None => (s, PreRelease::None), }; let split = s.split('.').collect::>(); match split.len() { 3 => { let major = split.first().unwrap().parse::()?; let minor = split.get(1).unwrap().parse::()?; let patch = split.get(2).unwrap().parse::()?; if major > MAX_U12 || minor > MAX_U12 || patch > MAX_U12 { return Err(Error::Range); } Ok(Self { major, minor, patch, pre, }) } _ => Err(Error::ParseSemver), } } } impl From for u64 { fn from(value: SemVer) -> Self { let major = u64::from(value.major) << 52; let minor = u64::from(value.minor) << 40; let patch = u64::from(value.patch) << 28; let pre = u64::from(u16::from(value.pre)); major | minor | patch | pre } } impl TryFrom for SemVer { type Error = Error; fn try_from(value: u64) -> Result { let mut mask: u64 = 0o7777 << 52; let major = (value & mask) >> 52; mask = 0o7777 << 40; let minor = (value & mask) >> 40; mask = 0o7777 << 28; let patch = (value & mask) >> 28; mask = 0o37777; let p = u16::try_from(value & mask)?; let pre: PreRelease = p.try_into()?; Ok(Self { major: major.try_into()?, minor: minor.try_into()?, patch: patch.try_into()?, pre, }) } } impl PartialEq for SemVer { fn eq(&self, other: &Self) -> bool { self.major == other.major && self.minor == other.minor && self.patch == other.patch && self.pre == other.pre } } impl Eq for SemVer {} impl Ord for SemVer { fn cmp(&self, other: &Self) -> Ordering { let a = u64::from(*self); let b = u64::from(*other); a.cmp(&b) } } impl PartialOrd for SemVer { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl PartialEq for SemVer { fn eq(&self, other: &Extended) -> bool { self.major == other.major && self.minor == other.minor && self.patch == other.patch && other.build == 0 && self.pre == other.pre } } impl PartialEq for SemVer { fn eq(&self, other: &Rapid) -> bool { self.major == other.major && self.minor == other.minor && self.patch == 0 && self.pre == other.pre } } impl PartialEq for SemVer { fn eq(&self, other: &Simple) -> bool { self.major == other.major && self.minor == 0 && self.patch == 0 && self.pre == other.pre } } impl PartialEq for SemVer { fn eq(&self, _other: &GitRev) -> bool { false } } impl PartialOrd for SemVer { fn partial_cmp(&self, other: &Extended) -> Option { let a = u64::from(*self); let b = u64::from(*other); Some(a.cmp(&b)) } } impl PartialOrd for SemVer { fn partial_cmp(&self, other: &Rapid) -> Option { let a = u64::from(*self); let b = u64::from(*other); Some(a.cmp(&b)) } } impl PartialOrd for SemVer { fn partial_cmp(&self, other: &Simple) -> Option { let a = u64::from(*self); let b = u64::from(*other); Some(a.cmp(&b)) } } impl PartialOrd for SemVer { fn partial_cmp(&self, _other: &GitRev) -> Option { None } } #[cfg(test)] mod tests { use std::num::NonZeroU16; use super::*; #[test] fn from_str() { let mut s: SemVer = "1.2.3".parse().unwrap(); assert_eq!( s, SemVer { major: 1, minor: 2, patch: 3, pre: PreRelease::None, } ); s = "0.3.0_alpha4".parse().unwrap(); assert_eq!( s, SemVer { major: 0, minor: 3, patch: 0, pre: PreRelease::Alpha(Some(NonZeroU16::new(4).unwrap())), } ) } #[test] fn to_string() { let mut s = SemVer { major: 1, minor: 0, patch: 2, pre: PreRelease::None, }; assert_eq!(s.to_string(), "1.0.2"); s = SemVer { major: 2, minor: 1, patch: 14, pre: PreRelease::Beta(Some(NonZeroU16::new(2).unwrap())), }; assert_eq!(s.to_string(), "2.1.14_beta2"); } #[test] fn to_from_u64() { let sem = SemVer { major: 1, minor: 0, patch: 11, pre: PreRelease::Beta(Some(NonZeroU16::new(2).unwrap())), }; assert_eq!(SemVer::try_from(u64::from(sem)).unwrap(), sem); } #[test] fn eq() { assert_eq!( "1.0.0_alpha".parse::().unwrap(), "1.0.0_alpha1".parse::().unwrap(), ); assert_eq!( "2.1.3_beta".parse::().unwrap(), "2.1.3_beta1".parse::().unwrap(), ); assert_eq!( "1.11.0_rc".parse::().unwrap(), "1.11.0_rc1".parse::().unwrap(), ); } #[test] fn ord() { let a: SemVer = "1.0.2".parse().unwrap(); let b: SemVer = "1.0.3".parse().unwrap(); let c: SemVer = "1.0.2_alpha1".parse().unwrap(); let d: SemVer = "1.0.2_alpha2".parse().unwrap(); assert!(a < b); assert!(c < a); assert!(d > c); } }