#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Version {
major: u64,
minor: u64,
patch: u64,
release: bool,
}
impl Version {
pub fn parse(text: &str) -> Option<Self> {
let text = text.trim();
let (core, tail) = match text.find(['-', '+']) {
Some(at) => text.split_at(at),
None => (text, ""),
};
let mut parts = core.split('.');
let major = parts.next()?.parse().ok()?;
let minor = parts.next().unwrap_or("0").parse().ok()?;
let patch = parts.next().unwrap_or("0").parse().ok()?;
if parts.next().is_some() {
return None;
}
Some(Self {
major,
minor,
patch,
release: !tail.starts_with('-'),
})
}
}
pub fn newer<'a>(current: &str, published: &'a str) -> Option<&'a str> {
let installed = Version::parse(current)?;
let candidate = Version::parse(published)?;
(candidate > installed).then_some(published)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_should_read_a_plain_version() {
assert_eq!(Version::parse("0.3.0").unwrap().minor, 3);
}
#[test]
fn it_should_rank_a_newer_minor_above_an_older_one() {
assert!(Version::parse("0.4.0") > Version::parse("0.3.9"));
}
#[test]
fn it_should_rank_a_newer_major_above_a_much_higher_minor() {
assert!(Version::parse("1.0.0") > Version::parse("0.99.0"));
}
#[test]
fn it_should_rank_a_release_above_its_own_pre_release() {
assert!(Version::parse("0.4.0") > Version::parse("0.4.0-rc1"));
}
#[test]
fn it_should_read_a_version_carrying_build_metadata_as_a_release() {
assert!(Version::parse("0.4.0+deadbeef").unwrap().release);
}
#[test]
fn it_should_refuse_a_version_with_a_fourth_part() {
assert!(Version::parse("1.2.3.4").is_none());
}
#[test]
fn it_should_refuse_something_that_is_not_a_version_at_all() {
assert!(Version::parse("latest").is_none());
}
#[test]
fn it_should_report_a_newer_release() {
assert_eq!(newer("0.3.0", "0.4.0"), Some("0.4.0"));
}
#[test]
fn it_should_report_nothing_when_the_installed_version_is_the_newest() {
assert!(newer("0.3.0", "0.3.0").is_none());
}
#[test]
fn it_should_report_nothing_when_the_installed_version_is_ahead() {
assert!(newer("0.4.0", "0.3.0").is_none());
}
#[test]
fn it_should_report_nothing_when_nothing_has_been_cached_yet() {
assert!(newer("0.3.0", "").is_none());
}
#[test]
fn it_should_report_nothing_when_the_registry_answer_is_unreadable() {
assert!(newer("0.3.0", "not-a-version").is_none());
}
}