use std::cmp::Ordering;
pub(crate) const INSTALLED_VERSION: &str = env!("CARGO_PKG_VERSION");
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct ReleaseInfo {
pub tag: String,
pub notes: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum ReleaseStatus {
Pending,
UpToDate { tag: String },
Available(ReleaseInfo),
Inconclusive { tag: String },
Failed,
}
impl ReleaseStatus {
pub(crate) fn from_fetch(result: Result<ReleaseInfo, String>) -> Self {
match result {
Ok(info) => match compare_to_installed(&info.tag) {
Some(Ordering::Greater) => ReleaseStatus::Available(info),
Some(_) => ReleaseStatus::UpToDate { tag: info.tag },
None => ReleaseStatus::Inconclusive { tag: info.tag },
},
Err(_) => ReleaseStatus::Failed,
}
}
pub(crate) fn tag(&self) -> Option<&str> {
match self {
ReleaseStatus::UpToDate { tag } | ReleaseStatus::Inconclusive { tag } => Some(tag),
ReleaseStatus::Available(info) => Some(&info.tag),
ReleaseStatus::Pending | ReleaseStatus::Failed => None,
}
}
}
pub(crate) fn compare_to_installed(tag: &str) -> Option<Ordering> {
compare_versions(INSTALLED_VERSION, tag)
}
pub(crate) fn compare_versions(installed: &str, tag: &str) -> Option<Ordering> {
let installed = parse_version(installed)?;
let tag = parse_version(tag)?;
Some(tag.cmp(&installed))
}
fn parse_version(v: &str) -> Option<Vec<u64>> {
v.trim()
.trim_start_matches('v')
.split('.')
.map(|part| part.parse().ok())
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
fn info(tag: &str) -> ReleaseInfo {
ReleaseInfo {
tag: tag.to_owned(),
notes: Vec::new(),
}
}
fn newer(installed: &str, tag: &str) -> bool {
compare_versions(installed, tag) == Some(Ordering::Greater)
}
#[test]
fn compare_versions_orders_numeric_versions() {
assert_eq!(compare_versions("0.1.0", "v0.1.0"), Some(Ordering::Equal));
assert!(newer("0.1.0", "v0.2.0"));
assert!(newer("0.1.0", "v0.1.1"));
assert_eq!(
compare_versions("0.2.0", "v0.1.9"),
Some(Ordering::Less),
"ahead of release is not an update"
);
assert!(newer("0.9.0", "v0.10.0"));
assert!(newer("1.0", "v1.0.1"));
}
#[test]
fn compare_versions_reports_an_uncomparable_pair_as_none() {
assert_eq!(compare_versions("0.1.0", "nightly"), None);
assert_eq!(compare_versions("0.1.0", "v0.2.0-rc1"), None);
assert_eq!(compare_versions("0.1.0-beta", "v0.2.0"), None);
assert_eq!(compare_versions("0.1.0", "v0.2.0+build.7"), None);
}
#[test]
fn from_fetch_classifies_each_outcome() {
assert_eq!(
ReleaseStatus::from_fetch(Err("offline".to_owned())),
ReleaseStatus::Failed
);
let same = ReleaseStatus::from_fetch(Ok(info(INSTALLED_VERSION)));
assert_eq!(
same,
ReleaseStatus::UpToDate {
tag: INSTALLED_VERSION.to_owned()
}
);
assert_eq!(
ReleaseStatus::from_fetch(Ok(info("v999.0.0"))),
ReleaseStatus::Available(info("v999.0.0"))
);
assert_eq!(
ReleaseStatus::from_fetch(Ok(info("v999.0.0-rc1"))),
ReleaseStatus::Inconclusive {
tag: "v999.0.0-rc1".to_owned()
}
);
}
}