use std::cmp::Ordering;
use std::error::Error;
use std::fmt::{Display, Formatter, Result as FResult};
use std::str::FromStr;
use serde::de::{self, Visitor};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[cfg(test)]
mod tests {
use serde_test::{Token, assert_de_tokens, assert_de_tokens_error, assert_ser_tokens};
use super::*;
const ACCIDENTAL_LATEST_STRING: &str = const_format::formatcp!("{}.{}.{}", u64::MAX, u64::MAX, u64::MAX);
#[test]
fn test_eq() {
assert!(Version::new(42, 21, 10) == Version::new(42, 21, 10));
assert!(Version::new(42, 21, 10) != Version::new(43, 21, 10));
assert!(Version::new(42, 21, 10) > Version::new(42, 21, 9));
assert!(Version::new(42, 21, 10) > Version::new(42, 20, 10));
assert!(Version::new(42, 21, 10) > Version::new(41, 21, 10));
assert!(Version::new(42, 21, 10) < Version::new(42, 21, 11));
assert!(Version::new(42, 21, 10) < Version::new(42, 22, 10));
assert!(Version::new(42, 21, 10) < Version::new(43, 21, 10));
}
#[test]
fn test_parse() {
assert_eq!(Version::from_str("42.21.10"), Ok(Version::new(42, 21, 10)));
assert_eq!(Version::from_str("42.21"), Ok(Version::new(42, 21, 0)));
assert_eq!(Version::from_str("42"), Ok(Version::new(42, 0, 0)));
assert_eq!(Version::from_str("latest"), Ok(Version::latest()));
assert_eq!(Version::from_str(&format!("{}.{}.{}", u64::MAX, u64::MAX, u64::MAX)), Err(ParseError::AccidentalLatest));
assert_eq!(Version::from_str("a"), Err(ParseError::MajorParseError { raw: String::from("a"), err: u64::from_str("a").unwrap_err() }));
assert_eq!(Version::from_str("42.a"), Err(ParseError::MinorParseError { raw: String::from("a"), err: u64::from_str("a").unwrap_err() }));
assert_eq!(Version::from_str("42.21.a"), Err(ParseError::PatchParseError { raw: String::from("a"), err: u64::from_str("a").unwrap_err() }));
assert_eq!(Version::from_str("a.b.c"), Err(ParseError::MajorParseError { raw: String::from("a"), err: u64::from_str("a").unwrap_err() }));
assert_eq!(Version::from_str("42.b.c"), Err(ParseError::MinorParseError { raw: String::from("b"), err: u64::from_str("b").unwrap_err() }));
}
#[test]
fn test_resolve() {
let mut latest = Version::latest();
let versions =
vec![Version::new(21, 21, 10), Version::new(42, 20, 10), Version::new(42, 21, 10), Version::new(42, 19, 10), Version::new(0, 0, 0)];
assert!(latest.resolve_latest(versions.clone()).is_ok());
assert_eq!(latest, Version::new(42, 21, 10));
let mut latest = Version::new(42, 21, 10);
assert_eq!(latest.resolve_latest(versions), Err(ResolveError::AlreadyResolved { version: Version::new(42, 21, 10) }));
let mut latest = Version::latest();
let versions = vec![Version::new(21, 21, 10), Version::latest(), Version::new(42, 21, 10)];
assert_eq!(latest.resolve_latest(versions), Err(ResolveError::NotResolved));
let mut latest = Version::latest();
let versions = vec![];
assert_eq!(latest.resolve_latest(versions), Err(ResolveError::NoVersions));
}
#[test]
fn test_semver() {
let semversion = semver::Version::new(42, 21, 10);
let version = Version::from(semversion.clone());
assert_eq!(semversion.major, version.major);
assert_eq!(semversion.minor, version.minor);
assert_eq!(semversion.patch, version.patch);
let semversion = semver::Version::new(10, 21, 42);
let version = Version::from(&semversion);
assert_eq!(semversion.major, version.major);
assert_eq!(semversion.minor, version.minor);
assert_eq!(semversion.patch, version.patch);
assert_eq!(Version::new(42, 21, 10), semver::Version::new(42, 21, 10));
assert_ne!(Version::latest(), semver::Version::new(u64::MAX, u64::MAX, u64::MAX));
assert!(Version::new(42, 21, 10) > semver::Version::new(42, 21, 9));
assert!(Version::new(42, 21, 10) > semver::Version::new(42, 20, 10));
assert!(Version::new(42, 21, 10) > semver::Version::new(41, 21, 10));
assert!(Version::new(42, 21, 10) < semver::Version::new(42, 21, 11));
assert!(Version::new(42, 21, 10) < semver::Version::new(42, 22, 10));
assert!(Version::new(42, 21, 10) < semver::Version::new(43, 21, 10));
}
#[test]
fn test_serde_serialize() {
assert_ser_tokens(&Version::new(42, 21, 10), &[Token::Str("42.21.10")]);
assert_ser_tokens(&Version::new(42, 0, 10), &[Token::Str("42.0.10")]);
assert_ser_tokens(&Version::latest(), &[Token::Str("latest")]);
}
#[test]
fn test_serde_deserialize() {
assert_de_tokens(&Version::new(42, 21, 10), &[Token::Str("42.21.10")]);
assert_de_tokens(&Version::new(42, 0, 10), &[Token::Str("42.0.10")]);
assert_de_tokens(&Version::latest(), &[Token::Str("latest")]);
assert_de_tokens_error::<Version>(&[Token::Str(ACCIDENTAL_LATEST_STRING)], &format!("{}", ParseError::AccidentalLatest));
assert_de_tokens_error::<Version>(
&[Token::Str("a")],
&format!("{}", ParseError::MajorParseError { raw: String::from("a"), err: u64::from_str("a").unwrap_err() }),
);
assert_de_tokens_error::<Version>(
&[Token::Str("42.a")],
&format!("{}", ParseError::MinorParseError { raw: String::from("a"), err: u64::from_str("a").unwrap_err() }),
);
assert_de_tokens_error::<Version>(
&[Token::Str("42.21.a")],
&format!("{}", ParseError::PatchParseError { raw: String::from("a"), err: u64::from_str("a").unwrap_err() }),
);
assert_de_tokens_error::<Version>(
&[Token::Str("a.b.c")],
&format!("{}", ParseError::MajorParseError { raw: String::from("a"), err: u64::from_str("a").unwrap_err() }),
);
assert_de_tokens_error::<Version>(
&[Token::Str("42.b.c")],
&format!("{}", ParseError::MinorParseError { raw: String::from("b"), err: u64::from_str("b").unwrap_err() }),
);
}
}
#[derive(Debug, Eq, PartialEq)]
pub enum ResolveError {
AlreadyResolved { version: Version },
NotResolved,
NoVersions,
}
impl Display for ResolveError {
fn fmt(&self, f: &mut Formatter<'_>) -> FResult {
match self {
ResolveError::AlreadyResolved { version } => write!(f, "Cannot resolve already resolved version '{version}'"),
ResolveError::NotResolved => write!(f, "Cannot resolve version with unresolved versions"),
ResolveError::NoVersions => write!(f, "Cannot resolve version without any versions given"),
}
}
}
impl Error for ResolveError {}
#[derive(Debug, Eq, PartialEq)]
pub enum ParseError {
AccidentalLatest,
MajorParseError { raw: String, err: std::num::ParseIntError },
MinorParseError { raw: String, err: std::num::ParseIntError },
PatchParseError { raw: String, err: std::num::ParseIntError },
TooManyColons { raw: String, got: usize },
IllegalVersion { raw: String, raw_version: String, err: Box<Self> },
}
impl Display for ParseError {
fn fmt(&self, f: &mut Formatter<'_>) -> FResult {
use ParseError::*;
match self {
AccidentalLatest => {
write!(f, "A version with all numbers to {} (64-bit, unsigned integer max) cannot be created; use 'latest' instead", u64::MAX)
},
MajorParseError { raw, err } => write!(f, "Could not parse major version number '{raw}': {err}"),
MinorParseError { raw, err } => write!(f, "Could not parse minor version number '{raw}': {err}"),
PatchParseError { raw, err } => write!(f, "Could not parse patch version number '{raw}': {err}"),
TooManyColons { raw, got } => write!(f, "Given 'NAME[:VERSION]' pair '{raw}' has too many colons (got {got}, expected at most 1)"),
IllegalVersion { raw, raw_version, err } => write!(f, "Could not parse version '{raw_version}' in '{raw}': {err}"),
}
}
}
impl Error for ParseError {}
struct VersionVisitor;
impl<'de> Visitor<'de> for VersionVisitor {
type Value = Version;
fn expecting(&self, formatter: &mut Formatter<'_>) -> FResult { formatter.write_str("a semanting version") }
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
Version::from_str(value).map_err(|err| E::custom(format!("{err}")))
}
}
#[derive(Clone, Copy, Debug, Eq)]
pub struct Version {
pub major: u64,
pub minor: u64,
pub patch: u64,
}
impl Version {
pub const fn new(major: u64, minor: u64, patch: u64) -> Self {
let result = Self { major, minor, patch };
if result.is_latest() {
panic!(
"A version with all numbers set to 9,223,372,036,854,775,807 (64-bit, unsigned integer max) cannot be created; use 'latest' instead"
);
}
result
}
#[inline]
pub const fn latest() -> Self { Self { major: u64::MAX, minor: u64::MAX, patch: u64::MAX } }
pub fn from_package_pair(package: &str) -> Result<(String, Self), ParseError> {
let colons: usize = package.matches(':').count();
if colons == 0 {
Ok((package.into(), Self::latest()))
} else if colons == 1 {
let colon_pos = package.find(':').unwrap();
let name: &str = &package[..colon_pos];
let version: &str = &package[colon_pos + 1..];
let version: Self = match Self::from_str(version) {
Ok(version) => version,
Err(err) => {
return Err(ParseError::IllegalVersion { raw: package.into(), raw_version: version.into(), err: Box::new(err) });
},
};
Ok((name.to_string(), version))
} else {
Err(ParseError::TooManyColons { raw: package.into(), got: colons })
}
}
pub fn resolve_latest<I: IntoIterator<Item = Self>>(&mut self, iter: I) -> Result<(), ResolveError> {
if !self.is_latest() {
return Err(ResolveError::AlreadyResolved { version: *self });
}
let mut last_version: Option<Version> = None;
for version in iter {
if version.is_latest() {
return Err(ResolveError::NotResolved);
}
if let Some(lversion) = &last_version {
if &version > lversion {
last_version = Some(version);
}
} else {
last_version = Some(version);
}
}
if let Some(version) = last_version {
*self = version;
Ok(())
} else {
Err(ResolveError::NoVersions)
}
}
#[inline]
pub const fn is_latest(&self) -> bool { self.major == u64::MAX && self.minor == u64::MAX && self.patch == u64::MAX }
}
impl Default for Version {
#[inline]
fn default() -> Self { Self::new(0, 0, 0) }
}
impl PartialEq for Version {
#[inline]
fn eq(&self, other: &Self) -> bool { self.major == other.major && self.minor == other.minor && self.patch == other.patch }
}
impl Ord for Version {
fn cmp(&self, other: &Self) -> Ordering {
let order = self.major.cmp(&other.major);
if order.is_ne() {
return order;
}
let order = self.minor.cmp(&other.minor);
if order.is_ne() {
return order;
}
self.patch.cmp(&other.patch)
}
}
impl PartialOrd for Version {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) }
}
impl FromStr for Version {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.to_lowercase() == "latest" {
return Ok(Self::latest());
}
let dot1 = s.find('.');
let dot2 = match &dot1 {
Some(pos1) => s[*pos1 + 1..].find('.').map(|pos2| *pos1 + 1 + pos2),
None => None,
};
let smajor: &str = match &dot1 {
Some(pos1) => &s[..*pos1],
None => s,
};
let sminor: &str = match dot1 {
Some(pos1) => match &dot2 {
Some(pos2) => &s[pos1 + 1..*pos2],
None => &s[pos1 + 1..],
},
None => "",
};
let spatch: &str = match dot2 {
Some(pos2) => &s[pos2 + 1..],
None => "",
};
let smajor = if !smajor.is_empty() && smajor.starts_with('v') { &smajor[1..] } else { smajor };
let major = match u64::from_str(smajor) {
Ok(major) => major,
Err(err) => {
return Err(ParseError::MajorParseError { raw: smajor.to_string(), err });
},
};
let minor = if !sminor.is_empty() {
match u64::from_str(sminor) {
Ok(minor) => minor,
Err(err) => {
return Err(ParseError::MinorParseError { raw: sminor.to_string(), err });
},
}
} else {
0
};
let patch = if !spatch.is_empty() {
match u64::from_str(spatch) {
Ok(patch) => patch,
Err(err) => {
return Err(ParseError::PatchParseError { raw: spatch.to_string(), err });
},
}
} else {
0
};
let result = Self { major, minor, patch };
if result.is_latest() {
return Err(ParseError::AccidentalLatest);
}
Ok(result)
}
}
impl Display for Version {
fn fmt(&self, f: &mut Formatter<'_>) -> FResult {
if self.is_latest() { write!(f, "latest") } else { write!(f, "{}.{}.{}", self.major, self.minor, self.patch) }
}
}
impl AsRef<Version> for Version {
#[inline]
fn as_ref(&self) -> &Self { self }
}
impl From<&Version> for Version {
#[inline]
fn from(value: &Version) -> Self { *value }
}
impl From<&mut Version> for Version {
#[inline]
fn from(value: &mut Version) -> Self { *value }
}
impl PartialEq<semver::Version> for Version {
#[inline]
fn eq(&self, other: &semver::Version) -> bool {
!self.is_latest() && self.major == other.major && self.minor == other.minor && self.patch == other.patch
}
}
impl PartialOrd<semver::Version> for Version {
#[inline]
fn partial_cmp(&self, other: &semver::Version) -> Option<Ordering> {
if self.is_latest() {
return None;
}
let order = self.major.cmp(&other.major);
if order.is_ne() {
return Some(order);
}
let order = self.minor.cmp(&other.minor);
if order.is_ne() {
return Some(order);
}
Some(self.patch.cmp(&other.patch))
}
}
impl From<semver::Version> for Version {
#[inline]
fn from(version: semver::Version) -> Self { Self { major: version.major, minor: version.minor, patch: version.patch } }
}
impl From<&semver::Version> for Version {
#[inline]
fn from(version: &semver::Version) -> Self { Self { major: version.major, minor: version.minor, patch: version.patch } }
}
impl From<Version> for String {
#[inline]
fn from(value: Version) -> Self { format!("{value}") }
}
impl From<&Version> for String {
#[inline]
fn from(value: &Version) -> Self { format!("{value}") }
}
impl Serialize for Version {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
impl<'de> Deserialize<'de> for Version {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_str(VersionVisitor)
}
}