Skip to main content

schema_model/model/
version.rs

1use std::cmp::Ordering;
2use std::fmt;
3
4#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
5pub struct Version {
6    major_version: i32,
7    minor_version: i32,
8    patch_version: i32,
9    pre_release_suffix: bool,
10}
11
12impl Version {
13    pub fn new(major_version: i32, minor_version: i32) -> Self {
14        Self {
15            major_version,
16            minor_version,
17            patch_version: 0,
18            pre_release_suffix: false,
19        }
20    }
21
22    pub fn with_patch(major_version: i32, minor_version: i32, patch_version: i32) -> Self {
23        Self {
24            major_version,
25            minor_version,
26            patch_version,
27            pre_release_suffix: false,
28        }
29    }
30
31    pub fn with_patch_and_suffix(
32        major_version: i32,
33        minor_version: i32,
34        patch_version: i32,
35        pre_release_suffix: bool,
36    ) -> Self {
37        Self {
38            major_version,
39            minor_version,
40            patch_version,
41            pre_release_suffix,
42        }
43    }
44
45    pub fn parse(version_str: &str) -> Self {
46        // Detect "-SNAPSHOT" suffix
47        let mut s = version_str.trim();
48        let pre = s.contains("-SNAPSHOT");
49        if pre {
50            if let Some(idx) = s.find("-SNAPSHOT") {
51                s = &s[..idx];
52            }
53        }
54        let parts: Vec<&str> = s.split('.').collect();
55        let major = parts
56            .get(0)
57            .and_then(|p| p.parse::<i32>().ok())
58            .unwrap_or(0);
59        let minor = parts
60            .get(1)
61            .and_then(|p| p.parse::<i32>().ok())
62            .unwrap_or(0);
63        let patch = parts
64            .get(2)
65            .and_then(|p| p.parse::<i32>().ok())
66            .unwrap_or(0);
67        Self {
68            major_version: major,
69            minor_version: minor,
70            patch_version: patch,
71            pre_release_suffix: pre,
72        }
73    }
74
75    pub fn major_version(&self) -> i32 {
76        self.major_version
77    }
78    pub fn minor_version(&self) -> i32 {
79        self.minor_version
80    }
81    pub fn patch_version(&self) -> i32 {
82        self.patch_version
83    }
84    pub fn is_pre_release_suffix(&self) -> bool {
85        self.pre_release_suffix
86    }
87}
88
89impl fmt::Display for Version {
90    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91        if self.pre_release_suffix {
92            if self.patch_version < 1 {
93                write!(
94                    f,
95                    "{:02}.{:02}-SNAPSHOT",
96                    self.major_version, self.minor_version
97                )
98            } else {
99                write!(
100                    f,
101                    "{:02}.{:02}.{:02}-SNAPSHOT",
102                    self.major_version, self.minor_version, self.patch_version
103                )
104            }
105        } else if self.patch_version < 1 {
106            write!(f, "{:02}.{:02}", self.major_version, self.minor_version)
107        } else {
108            write!(
109                f,
110                "{:02}.{:02}.{:02}",
111                self.major_version, self.minor_version, self.patch_version
112            )
113        }
114    }
115}
116
117impl PartialOrd for Version {
118    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
119        Some(self.cmp(other))
120    }
121}
122
123impl Ord for Version {
124    fn cmp(&self, other: &Self) -> Ordering {
125        if std::ptr::eq(self, other) {
126            return Ordering::Equal;
127        }
128        match self.major_version.cmp(&other.major_version) {
129            Ordering::Equal => {}
130            ord => return ord,
131        }
132        match self.minor_version.cmp(&other.minor_version) {
133            Ordering::Equal => {}
134            ord => return ord,
135        }
136        match self.patch_version.cmp(&other.patch_version) {
137            Ordering::Equal => {}
138            ord => return ord,
139        }
140        match (self.pre_release_suffix, other.pre_release_suffix) {
141            (true, false) => Ordering::Greater,
142            (false, true) => Ordering::Less,
143            _ => Ordering::Equal,
144        }
145    }
146}
147
148impl From<&str> for Version {
149    fn from(value: &str) -> Self {
150        Version::parse(value)
151    }
152}
153
154#[cfg(test)]
155mod tests {
156    use super::*;
157
158    #[test]
159    fn parse_and_display() {
160        let v = Version::parse("1.2");
161        assert_eq!(v.major_version(), 1);
162        assert_eq!(v.minor_version(), 2);
163        assert_eq!(v.patch_version(), 0);
164        assert!(!v.is_pre_release_suffix());
165        assert_eq!(v.to_string(), "01.02");
166
167        let v2 = Version::parse("1.2.3-SNAPSHOT");
168        assert_eq!(v2.to_string(), "01.02.03-SNAPSHOT");
169        assert!(v2.is_pre_release_suffix());
170    }
171
172    #[test]
173    fn ordering_matches_java_logic() {
174        let a = Version::with_patch(1, 2, 0);
175        let b = Version::with_patch(1, 2, 1);
176        assert!(a < b);
177
178        let c = Version::with_patch_and_suffix(1, 2, 3, true);
179        let d = Version::with_patch_and_suffix(1, 2, 3, false);
180        // Snapshot considered greater when numbers equal
181        assert!(c > d);
182    }
183}