qualia_client_core/
qapp_version.rs1use std::cmp::Ordering;
7
8#[derive(Debug, Clone, PartialEq, Eq)]
10pub struct ParsedVersion {
11 pub major: u32,
12 pub minor: u32,
13 pub patch: u32,
14 pub pre_release: Option<String>,
16}
17
18pub fn normalize_version_label(raw: &str) -> String {
20 let trimmed = raw.trim().trim_start_matches('v').trim();
21 let core = trimmed.split('+').next().unwrap_or(trimmed);
22 core.to_string()
23}
24
25pub fn parse_version(raw: &str) -> Option<ParsedVersion> {
27 let label = normalize_version_label(raw);
28 if label.is_empty() {
29 return None;
30 }
31
32 let (numeric, pre) = match label.split_once('-') {
33 Some((n, p)) if !p.is_empty() => (n, Some(p.to_string())),
34 _ => (label.as_str(), None),
35 };
36
37 let mut parts = numeric.split('.');
38 let major = parts.next()?.parse().ok()?;
39 let minor = parts.next().unwrap_or("0").parse().ok()?;
40 let patch = parts.next().unwrap_or("0").parse().ok()?;
41
42 Some(ParsedVersion {
43 major,
44 minor,
45 patch,
46 pre_release: pre,
47 })
48}
49
50pub fn compare_parsed(a: &ParsedVersion, b: &ParsedVersion) -> Ordering {
51 (a.major, a.minor, a.patch)
52 .cmp(&(b.major, b.minor, b.patch))
53 .then_with(|| compare_pre_release(&a.pre_release, &b.pre_release))
54}
55
56fn compare_pre_release(a: &Option<String>, b: &Option<String>) -> Ordering {
57 match (a, b) {
58 (None, None) => Ordering::Equal,
59 (None, Some(_)) => Ordering::Greater,
60 (Some(_), None) => Ordering::Less,
61 (Some(a), Some(b)) => a.cmp(b),
62 }
63}
64
65pub fn compare_versions(a: &str, b: &str) -> Option<Ordering> {
67 let pa = parse_version(a)?;
68 let pb = parse_version(b)?;
69 Some(compare_parsed(&pa, &pb))
70}
71
72pub fn is_version_newer(candidate: &str, installed: &str) -> bool {
74 matches!(
75 compare_versions(candidate, installed),
76 Some(Ordering::Greater)
77 )
78}
79
80#[cfg(test)]
81mod tests {
82 use super::*;
83
84 #[test]
85 fn stable_beats_prerelease_at_same_numbers() {
86 assert!(is_version_newer("0.0.12", "0.0.12-dev"));
87 assert!(!is_version_newer("0.0.12-dev", "0.0.12"));
88 }
89
90 #[test]
91 fn patch_bump_is_newer() {
92 assert!(is_version_newer("0.0.13", "0.0.12"));
93 assert!(!is_version_newer("0.0.12", "0.0.13"));
94 }
95
96 #[test]
97 fn v_prefix_and_whitespace() {
98 use std::cmp::Ordering;
99
100 assert!(parse_version(" v0.0.12 ").is_some());
101 assert!(!is_version_newer("v0.0.12", "0.0.12"));
102 assert_eq!(compare_versions("v0.0.12", "0.0.12"), Some(Ordering::Equal));
103 }
104
105 #[test]
106 fn equal_versions_not_newer() {
107 assert!(!is_version_newer("0.0.12", "0.0.12"));
108 assert!(!is_version_newer("0.0.12-dev", "0.0.12-dev"));
109 }
110}