version-rs/src/semver.rs

261 lines
6.6 KiB
Rust

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<Self, Self::Err> {
let (s, pre) = match s.split_once('_') {
Some((a, b)) => (a, b.parse::<PreRelease>()?),
None => (s, PreRelease::None),
};
let split = s.split('.').collect::<Vec<_>>();
match split.len() {
3 => {
let major = split.first().unwrap().parse::<u16>()?;
let minor = split.get(1).unwrap().parse::<u16>()?;
let patch = split.get(2).unwrap().parse::<u16>()?;
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<SemVer> 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<u64> for SemVer {
type Error = Error;
fn try_from(value: u64) -> Result<Self, Self::Error> {
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<Ordering> {
Some(self.cmp(other))
}
}
impl PartialEq<Extended> 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<Rapid> 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<Simple> for SemVer {
fn eq(&self, other: &Simple) -> bool {
self.major == other.major && self.minor == 0 && self.patch == 0 && self.pre == other.pre
}
}
impl PartialEq<GitRev> for SemVer {
fn eq(&self, _other: &GitRev) -> bool {
false
}
}
impl PartialOrd<Extended> for SemVer {
fn partial_cmp(&self, other: &Extended) -> Option<Ordering> {
let a = u64::from(*self);
let b = u64::from(*other);
Some(a.cmp(&b))
}
}
impl PartialOrd<Rapid> for SemVer {
fn partial_cmp(&self, other: &Rapid) -> Option<Ordering> {
let a = u64::from(*self);
let b = u64::from(*other);
Some(a.cmp(&b))
}
}
impl PartialOrd<Simple> for SemVer {
fn partial_cmp(&self, other: &Simple) -> Option<Ordering> {
let a = u64::from(*self);
let b = u64::from(*other);
Some(a.cmp(&b))
}
}
impl PartialOrd<GitRev> for SemVer {
fn partial_cmp(&self, _other: &GitRev) -> Option<Ordering> {
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::<SemVer>().unwrap(),
"1.0.0_alpha1".parse::<SemVer>().unwrap(),
);
assert_eq!(
"2.1.3_beta".parse::<SemVer>().unwrap(),
"2.1.3_beta1".parse::<SemVer>().unwrap(),
);
assert_eq!(
"1.11.0_rc".parse::<SemVer>().unwrap(),
"1.11.0_rc1".parse::<SemVer>().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);
}
}