timelake_format/
version.rs1use std::fmt;
5use std::str::FromStr;
6
7use crate::{FormatError, Result};
8
9#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
19pub struct FormatVersion {
20 pub major: u32,
22 pub minor: u32,
24 pub patch: u32,
26}
27
28impl FormatVersion {
29 pub const UNVERSIONED: Self = Self::new(0, 1, 0);
31
32 pub const fn new(major: u32, minor: u32, patch: u32) -> Self {
34 Self { major, minor, patch }
35 }
36
37 pub fn readable_by(self, known: Self) -> bool {
39 self.breaking() <= known.breaking()
40 }
41
42 pub fn writable_by(self, known: Self) -> bool {
45 (self.major, self.minor) <= (known.major, known.minor)
46 }
47
48 fn breaking(self) -> (u32, u32) {
50 if self.major == 0 { (0, self.minor) } else { (self.major, 0) }
51 }
52}
53
54impl fmt::Display for FormatVersion {
55 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56 write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
57 }
58}
59
60impl FromStr for FormatVersion {
61 type Err = FormatError;
62
63 fn from_str(s: &str) -> Result<Self> {
65 let bad = || FormatError::new(format!("a format version is MAJOR.MINOR.PATCH, got {s:?}"));
66 let parts = s.trim().split('.').map(str::parse).collect::<std::result::Result<Vec<u32>, _>>();
67 match parts.map_err(|_| bad())?[..] {
68 [major, minor, patch] => Ok(Self::new(major, minor, patch)),
69 [legacy] => Ok(Self::new(0, legacy, 0)),
70 _ => Err(bad()),
71 }
72 }
73}
74
75#[cfg(test)]
76mod tests {
77 use super::*;
78
79 fn v(s: &str) -> FormatVersion {
80 s.parse().unwrap()
81 }
82
83 #[test]
84 fn parses_semver_and_the_legacy_integer() {
85 assert_eq!(v("0.2.0"), FormatVersion::new(0, 2, 0));
86 assert_eq!(v(" 1.12.3 ").to_string(), "1.12.3");
87 assert_eq!(v("2"), v("0.2.0"));
88 assert_eq!(v("1"), FormatVersion::UNVERSIONED);
89 for bad in ["", "1.2", "1.2.3.4", "v1.2.3", "1.-2.3"] {
90 assert!(bad.parse::<FormatVersion>().is_err(), "{bad:?}");
91 }
92 }
93
94 #[test]
95 fn compatibility_follows_the_bump() {
96 let known = v("0.2.0");
97 assert!(v("0.1.0").readable_by(known) && v("0.1.0").writable_by(known));
98 assert!(v("0.2.5").readable_by(known) && v("0.2.5").writable_by(known));
99 assert!(!v("0.3.0").readable_by(known) && !v("0.3.0").writable_by(known));
100 assert!(!v("1.0.0").readable_by(known));
101
102 let known = v("1.2.0");
103 assert!(v("0.9.0").readable_by(known) && v("0.9.0").writable_by(known));
104 assert!(v("1.2.7").writable_by(known));
105 assert!(v("1.3.0").readable_by(known) && !v("1.3.0").writable_by(known));
106 assert!(!v("2.0.0").readable_by(known) && !v("2.0.0").writable_by(known));
107 }
108}