Skip to main content

cmake_package/
version.rs

1// SPDX-FileCopyrightText: 2024 Daniel Vrátil <dvratil@kde.org>
2//
3// SPDX-License-Identifier: MIT
4
5#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, serde::Deserialize)]
6#[serde(rename_all = "UPPERCASE")]
7pub struct Version {
8    pub major: u32,
9    pub minor: u32,
10    pub patch: u32,
11}
12
13#[derive(Debug, Clone, PartialEq, serde::Deserialize)]
14pub struct Considered {
15    pub config: String,
16    pub version: String,
17}
18
19#[derive(Debug, Clone, PartialEq)]
20pub enum VersionError {
21    InvalidVersion,
22    VersionIncompatible(Vec<Considered>),
23}
24
25impl Version {
26    pub fn parse(version: &str) -> Result<Version, VersionError> {
27        let parts: Vec<&str> = version.split(&['.', '-']).collect();
28        if parts.is_empty() {
29            return Err(VersionError::InvalidVersion);
30        }
31
32        Ok(Version {
33            major: parts[0].parse().or(Err(VersionError::InvalidVersion))?,
34            minor: if parts.len() > 1 {
35                parts[1].parse().or(Err(VersionError::InvalidVersion))?
36            } else {
37                0
38            },
39            patch: if parts.len() > 2 {
40                parts[2].parse().or(Err(VersionError::InvalidVersion))?
41            } else {
42                0
43            },
44        })
45    }
46}
47
48impl TryInto<Version> for &str {
49    type Error = VersionError;
50
51    fn try_into(self) -> Result<Version, Self::Error> {
52        Version::parse(self)
53    }
54}
55
56impl TryInto<Version> for String {
57    type Error = VersionError;
58
59    fn try_into(self) -> Result<Version, Self::Error> {
60        Version::parse(&self)
61    }
62}
63
64impl From<Version> for String {
65    fn from(value: Version) -> Self {
66        format!("{}.{}.{}", value.major, value.minor, value.patch)
67    }
68}
69
70impl std::fmt::Display for Version {
71    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72        write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
73    }
74}
75
76#[cfg(test)]
77mod testing {
78    use super::*;
79
80    #[test]
81    fn test_version_parse_valid() {
82        assert_eq!(
83            Version::parse("1.2.3").unwrap(),
84            Version {
85                major: 1,
86                minor: 2,
87                patch: 3
88            }
89        );
90        assert_eq!(
91            Version::parse("1.2").unwrap(),
92            Version {
93                major: 1,
94                minor: 2,
95                patch: 0
96            }
97        );
98        assert_eq!(
99            Version::parse("1").unwrap(),
100            Version {
101                major: 1,
102                minor: 0,
103                patch: 0
104            }
105        );
106        assert_eq!(
107            Version::parse("4.0.20250530-gab5866c").unwrap(),
108            Version {
109                major: 4,
110                minor: 0,
111                patch: 20250530
112            }
113        )
114    }
115
116    #[test]
117    fn test_version_parse_invalid() {
118        assert!(Version::parse("").is_err());
119        assert!(Version::parse("1.2.3:4").is_err());
120        assert!(Version::parse("a.b.c").is_err());
121    }
122
123    #[test]
124    fn test_version_into_string() {
125        let version = Version {
126            major: 1,
127            minor: 2,
128            patch: 3,
129        };
130        let version_str: String = version.into();
131        assert_eq!(version_str, "1.2.3");
132    }
133
134    #[test]
135    fn test_version_partial_ord() {
136        let v1 = Version {
137            major: 1,
138            minor: 0,
139            patch: 0,
140        };
141        let v2 = Version {
142            major: 1,
143            minor: 1,
144            patch: 0,
145        };
146        let v3 = Version {
147            major: 1,
148            minor: 1,
149            patch: 1,
150        };
151
152        let v4 = Version {
153            major: 2,
154            minor: 0,
155            patch: 0,
156        };
157
158        assert!(v1 < v2);
159        assert!(v1 <= v2);
160        assert!(v2 < v3);
161        assert!(v2 <= v3);
162        assert!(v1 < v3);
163        assert!(v1 <= v3);
164        assert!(v3 > v2);
165        assert!(v3 >= v2);
166        assert!(v2 > v1);
167        assert!(v2 >= v1);
168        assert!(v3 > v1);
169        assert!(v3 >= v1);
170        assert!(v1 < v4);
171        assert!(v1 <= v4);
172        assert!(v4 > v1);
173        assert!(v4 >= v1);
174        assert!(v2 < v4);
175        assert!(v2 <= v4);
176        assert!(v4 > v2);
177        assert!(v4 >= v2);
178        assert!(v3 < v4);
179        assert!(v3 <= v4);
180        assert!(v4 > v3);
181        assert!(v4 >= v3);
182    }
183
184    #[test]
185    fn test_version_partial_eq() {
186        let v1 = Version {
187            major: 1,
188            minor: 0,
189            patch: 0,
190        };
191        let v2 = Version {
192            major: 1,
193            minor: 0,
194            patch: 0,
195        };
196        let v3 = Version {
197            major: 1,
198            minor: 1,
199            patch: 0,
200        };
201
202        assert_eq!(v1, v2);
203        assert_ne!(v1, v3);
204    }
205
206    #[test]
207    fn test_version_try_into() {
208        let version_str = "1.2.3";
209        let version: Version = version_str.try_into().unwrap();
210        assert_eq!(
211            version,
212            Version {
213                major: 1,
214                minor: 2,
215                patch: 3
216            }
217        );
218
219        let version_string = String::from("1.2.3");
220        let version: Version = version_string.try_into().unwrap();
221        assert_eq!(
222            version,
223            Version {
224                major: 1,
225                minor: 2,
226                patch: 3
227            }
228        );
229    }
230
231    #[test]
232    fn test_display() {
233        let version = Version {
234            major: 1,
235            minor: 2,
236            patch: 3,
237        };
238        assert_eq!(format!("{}", version), "1.2.3");
239    }
240}