1use std::cmp::Ordering;
7
8use crate::messages::{raise, Refusal};
9
10pub fn parse(s: &str) -> Option<(u32, u32, u32)> {
13 let t = s.trim_matches(' ');
14 if t.is_empty() {
15 return None;
16 }
17 let parts: Vec<&str> = t.split('.').collect();
18 if parts.len() != 3 {
19 return None;
20 }
21 let mut n = [0u32; 3];
22 for (i, p) in parts.iter().enumerate() {
23 if p.is_empty() || p.len() > 9 || !p.chars().all(|c| c.is_ascii_digit()) {
24 return None;
25 }
26 n[i] = p.parse().ok()?;
27 }
28 Some((n[0], n[1], n[2]))
29}
30
31pub fn compare(a: &str, b: &str) -> Result<Ordering, Refusal> {
34 let pa = parse(a).ok_or_else(|| raise("vla-version-malformed", &[("value", a)]))?;
35 let pb = parse(b).ok_or_else(|| raise("vla-version-malformed", &[("value", b)]))?;
36 Ok(pa.cmp(&pb))
37}
38
39pub fn at_least(minimum: &str) -> Result<bool, Refusal> {
41 Ok(compare(crate::VERSION, minimum)? != Ordering::Less)
42}
43
44#[cfg(test)]
45mod tests {
46 use super::*;
47
48 #[test]
49 fn parse_takes_three_numeric_parts_and_nothing_else() {
50 assert_eq!(parse("0.5.2"), Some((0, 5, 2)));
51 assert_eq!(parse(" 10.0.1 "), Some((10, 0, 1)));
52 assert_eq!(parse("banana"), None);
53 assert_eq!(parse("1.2"), None);
54 assert_eq!(parse("1.2.3.4"), None);
55 assert_eq!(parse("1..3"), None);
56 assert_eq!(parse("1.2.x"), None);
57 assert_eq!(parse(""), None);
58 assert_eq!(parse("1234567890.0.0"), None);
59 }
60
61 #[test]
62 fn compare_orders_by_part_and_refuses_a_malformed_one() {
63 assert_eq!(compare("0.5.2", "0.5.10").unwrap(), Ordering::Less);
64 assert_eq!(compare("1.0.0", "0.9.9").unwrap(), Ordering::Greater);
65 assert_eq!(compare("0.7.1", "0.7.1").unwrap(), Ordering::Equal);
66 let r = compare("x", "0.1.0").unwrap_err();
67 assert_eq!(r.id, "vla-version-malformed");
68 assert!(at_least("0.0.0").unwrap());
69 assert!(!at_least("999.0.0").unwrap());
70 assert!(at_least(crate::VERSION).unwrap());
71 }
72}