use { crate::{Rapid, Version}, serde::{Deserialize, Serialize}, std::{cmp::Ordering, error::Error, fmt, num::ParseIntError, str::FromStr}, }; #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct SemVer { pub major: u32, pub minor: u32, pub patch: u32, } impl fmt::Display for SemVer { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}.{}.{}", self.major, self.minor, self.patch) } } impl PartialOrd for SemVer { fn partial_cmp(&self, other: &Self) -> Option { match self.major.partial_cmp(&other.major) { Some(Ordering::Greater) => Some(Ordering::Greater), Some(Ordering::Less) => Some(Ordering::Less), None => None, Some(Ordering::Equal) => match self.minor.partial_cmp(&other.minor) { Some(Ordering::Greater) => Some(Ordering::Greater), Some(Ordering::Less) => Some(Ordering::Less), None => None, Some(Ordering::Equal) => self.patch.partial_cmp(&other.patch), }, } } } impl Ord for SemVer { fn cmp(&self, other: &Self) -> std::cmp::Ordering { match self.major.cmp(&other.major) { Ordering::Greater => Ordering::Greater, Ordering::Less => Ordering::Less, Ordering::Equal => match self.minor.cmp(&other.minor) { Ordering::Greater => Ordering::Greater, Ordering::Less => Ordering::Less, Ordering::Equal => self.patch.cmp(&other.patch), }, } } } impl PartialEq for SemVer { fn eq(&self, other: &Rapid) -> bool { self.major == other.major && self.minor == other.minor && self.patch == 0 } } impl PartialOrd for SemVer { fn partial_cmp(&self, other: &Rapid) -> Option { match self.major.partial_cmp(&other.major) { Some(Ordering::Greater) => Some(Ordering::Greater), Some(Ordering::Less) => Some(Ordering::Less), None => None, Some(Ordering::Equal) => match self.minor.partial_cmp(&other.minor) { Some(Ordering::Greater) => Some(Ordering::Greater), Some(Ordering::Less) => Some(Ordering::Less), None => None, Some(Ordering::Equal) => match self.patch { 0 => Some(Ordering::Equal), _ => Some(Ordering::Greater), }, }, } } } impl PartialEq for SemVer { fn eq(&self, other: &u32) -> bool { self.major == *other && self.minor == 0 && self.patch == 0 } } impl PartialOrd for SemVer { fn partial_cmp(&self, other: &u32) -> Option { match self.major.cmp(other) { Ordering::Greater => Some(Ordering::Greater), Ordering::Less => Some(Ordering::Less), Ordering::Equal => { if self.minor == 0 && self.patch == 0 { Some(Ordering::Equal) } else { Some(Ordering::Greater) } } } } } impl PartialEq for u32 { fn eq(&self, other: &SemVer) -> bool { other.eq(self) } } impl PartialOrd for u32 { fn partial_cmp(&self, other: &SemVer) -> Option { match other.partial_cmp(self) { Some(Ordering::Less) => Some(Ordering::Greater), Some(Ordering::Greater) => Some(Ordering::Less), Some(Ordering::Equal) => Some(Ordering::Equal), None => None, } } } #[derive(Debug, PartialEq)] pub struct ParseSemVerError; impl fmt::Display for ParseSemVerError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Error parsing SemVer") } } impl Error for ParseSemVerError {} impl From for ParseSemVerError { fn from(_value: ParseIntError) -> Self { Self } } impl FromStr for SemVer { type Err = ParseSemVerError; fn from_str(s: &str) -> Result { 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::()?; Ok(Self { major, minor, patch, }) } _ => Err(ParseSemVerError), } } } impl TryFrom for SemVer { type Error = ParseSemVerError; fn try_from(value: Version) -> Result { match value { Version::SemVer(s) => Ok(SemVer { major: s.major, minor: s.minor, patch: s.patch, }), Version::Rapid(r) => Ok(SemVer { major: r.major, minor: r.minor, patch: 0, }), Version::Number(n) => Ok(Self { major: n, minor: 0, patch: 0, }), Version::Git(_) => Err(ParseSemVerError), } } } #[cfg(test)] mod test { use super::*; #[test] fn parse_semver() { assert_eq!( "1.0.3".parse::(), Ok(SemVer { major: 1, minor: 0, patch: 3 }) ); } #[test] fn cmp_semver_num_gt() { let sem = "42.69.0".parse::().unwrap(); let num = 42; assert!(sem > num); } #[test] fn cmp_semver_num_eq() { let sem = "42.0.0".parse::().unwrap(); let num = 42; assert_eq!(sem, num); } }