1use std::{cmp::Reverse, convert::Infallible, str::FromStr};
2
3#[derive(PartialEq, Eq, Debug, Clone)]
43pub enum Version {
44 Stable(u32),
48
49 Beta(u32, Option<u32>),
53
54 Alpha(u32, Option<u32>),
58 Nonconformant(String),
62}
63
64impl Version {
65 fn try_parse(v: &str) -> Option<Version> {
66 let v = v.strip_prefix('v')?;
67 let major = v.split_terminator(|ch: char| !ch.is_ascii_digit()).next()?;
68 let v = &v[major.len()..];
69 let major: u32 = major.parse().ok()?;
70 if v.is_empty() {
71 return Some(Version::Stable(major));
72 }
73 if let Some(suf) = v.strip_prefix("alpha") {
74 return if suf.is_empty() {
75 Some(Version::Alpha(major, None))
76 } else {
77 Some(Version::Alpha(major, Some(suf.parse().ok()?)))
78 };
79 }
80 if let Some(suf) = v.strip_prefix("beta") {
81 return if suf.is_empty() {
82 Some(Version::Beta(major, None))
83 } else {
84 Some(Version::Beta(major, Some(suf.parse().ok()?)))
85 };
86 }
87 None
88 }
89
90 pub fn parse(v: &str) -> Version {
97 match Self::try_parse(v) {
98 Some(ver) => ver,
99 None => Version::Nonconformant(v.to_string()),
100 }
101 }
102}
103
104impl FromStr for Version {
106 type Err = Infallible;
107
108 fn from_str(s: &str) -> Result<Self, Self::Err> {
109 Ok(Version::parse(s))
110 }
111}
112
113#[derive(PartialEq, Eq, PartialOrd, Ord)]
114enum Stability {
115 Nonconformant,
116 Alpha,
117 Beta,
118 Stable,
119}
120
121#[derive(PartialEq, Eq, PartialOrd, Ord)]
123struct Priority {
124 stability: Stability,
125 major: u32,
126 minor: Option<u32>,
127 nonconformant: Option<Reverse<String>>,
128}
129
130#[derive(PartialEq, Eq, PartialOrd, Ord)]
132struct Generation {
133 major: u32,
134 stability: Stability,
135 minor: Option<u32>,
136 nonconformant: Option<Reverse<String>>,
137}
138
139impl Version {
140 pub fn priority(&self) -> impl Ord {
161 match self {
162 &Self::Stable(major) => Priority {
163 stability: Stability::Stable,
164 major,
165 minor: None,
166 nonconformant: None,
167 },
168 &Self::Beta(major, minor) => Priority {
169 stability: Stability::Beta,
170 major,
171 minor,
172 nonconformant: None,
173 },
174 &Self::Alpha(major, minor) => Priority {
175 stability: Stability::Alpha,
176 major,
177 minor,
178 nonconformant: None,
179 },
180 Self::Nonconformant(nonconformant) => Priority {
181 stability: Stability::Nonconformant,
182 major: 0,
183 minor: None,
184 nonconformant: Some(Reverse(nonconformant.clone())),
185 },
186 }
187 }
188
189 pub fn generation(&self) -> impl Ord {
205 match self {
206 &Self::Stable(major) => Generation {
207 stability: Stability::Stable,
208 major,
209 minor: None,
210 nonconformant: None,
211 },
212 &Self::Beta(major, minor) => Generation {
213 stability: Stability::Beta,
214 major,
215 minor,
216 nonconformant: None,
217 },
218 &Self::Alpha(major, minor) => Generation {
219 stability: Stability::Alpha,
220 major,
221 minor,
222 nonconformant: None,
223 },
224 Self::Nonconformant(nonconformant) => Generation {
225 stability: Stability::Nonconformant,
226 major: 0,
227 minor: None,
228 nonconformant: Some(Reverse(nonconformant.clone())),
229 },
230 }
231 }
232}
233
234#[cfg(test)]
235mod tests {
236 use super::Version;
237 use std::{cmp::Reverse, str::FromStr};
238
239 #[test]
240 fn test_stable() {
241 assert_eq!(Version::parse("v1"), Version::Stable(1));
242 assert_eq!(Version::parse("v3"), Version::Stable(3));
243 assert_eq!(Version::parse("v10"), Version::Stable(10));
244 }
245
246 #[test]
247 fn test_prerelease() {
248 assert_eq!(Version::parse("v1beta"), Version::Beta(1, None));
249 assert_eq!(Version::parse("v2alpha1"), Version::Alpha(2, Some(1)));
250 assert_eq!(Version::parse("v10beta12"), Version::Beta(10, Some(12)));
251 }
252
253 fn check_not_parses(s: &str) {
254 assert_eq!(Version::parse(s), Version::Nonconformant(s.to_string()))
255 }
256
257 #[test]
258 fn test_nonconformant() {
259 check_not_parses("");
260 check_not_parses("foo");
261 check_not_parses("v");
262 check_not_parses("v-1");
263 check_not_parses("valpha");
264 check_not_parses("vbeta3");
265 check_not_parses("vv1");
266 check_not_parses("v1alpha1hi");
267 check_not_parses("v1zeta3");
268 }
269
270 #[test]
271 fn test_version_fromstr() {
272 assert_eq!(
273 Version::from_str("infallible").unwrap(),
274 Version::Nonconformant("infallible".to_string())
275 );
276 }
277
278 #[test]
279 fn test_version_priority_ord() {
280 assert!(Version::Stable(2).priority() > Version::Stable(1).priority());
282 assert!(Version::Stable(1).priority() > Version::Beta(1, None).priority());
283 assert!(Version::Stable(1).priority() > Version::Beta(2, None).priority());
284 assert!(Version::Stable(2).priority() > Version::Alpha(1, Some(2)).priority());
285 assert!(Version::Stable(1).priority() > Version::Alpha(2, Some(2)).priority());
286 assert!(Version::Beta(1, None).priority() > Version::Nonconformant("ver3".into()).priority());
287
288 assert!(Version::Stable(2).priority() > Version::Stable(1).priority());
289 assert!(Version::Stable(1).priority() > Version::Beta(2, None).priority());
290 assert!(Version::Stable(1).priority() > Version::Beta(2, Some(2)).priority());
291 assert!(Version::Stable(1).priority() > Version::Alpha(2, None).priority());
292 assert!(Version::Stable(1).priority() > Version::Alpha(2, Some(3)).priority());
293 assert!(Version::Stable(1).priority() > Version::Nonconformant("foo".to_string()).priority());
294 assert!(Version::Beta(1, Some(1)).priority() > Version::Beta(1, None).priority());
295 assert!(Version::Beta(1, Some(2)).priority() > Version::Beta(1, Some(1)).priority());
296 assert!(Version::Beta(1, None).priority() > Version::Alpha(1, None).priority());
297 assert!(Version::Beta(1, None).priority() > Version::Alpha(1, Some(3)).priority());
298 assert!(Version::Beta(1, None).priority() > Version::Nonconformant("foo".to_string()).priority());
299 assert!(Version::Beta(1, Some(2)).priority() > Version::Nonconformant("foo".to_string()).priority());
300 assert!(Version::Alpha(1, Some(1)).priority() > Version::Alpha(1, None).priority());
301 assert!(Version::Alpha(1, Some(2)).priority() > Version::Alpha(1, Some(1)).priority());
302 assert!(Version::Alpha(1, None).priority() > Version::Nonconformant("foo".to_string()).priority());
303 assert!(Version::Alpha(1, Some(2)).priority() > Version::Nonconformant("foo".to_string()).priority());
304 assert!(
305 Version::Nonconformant("bar".to_string()).priority()
306 > Version::Nonconformant("foo".to_string()).priority()
307 );
308 assert!(
309 Version::Nonconformant("foo1".to_string()).priority()
310 > Version::Nonconformant("foo10".to_string()).priority()
311 );
312
313 let mut vers = vec![
316 Version::Beta(2, Some(2)),
317 Version::Stable(1),
318 Version::Nonconformant("hi".into()),
319 Version::Alpha(3, Some(2)),
320 Version::Stable(2),
321 Version::Beta(2, Some(3)),
322 ];
323 vers.sort_by_cached_key(|x| Reverse(x.priority()));
324 assert_eq!(vers, vec![
325 Version::Stable(2),
326 Version::Stable(1),
327 Version::Beta(2, Some(3)),
328 Version::Beta(2, Some(2)),
329 Version::Alpha(3, Some(2)),
330 Version::Nonconformant("hi".into()),
331 ]);
332 }
333
334 #[test]
335 fn test_version_generation_ord() {
336 assert!(Version::Stable(2).generation() > Version::Stable(1).generation());
337 assert!(Version::Stable(1).generation() > Version::Beta(1, None).generation());
338 assert!(Version::Stable(1).generation() < Version::Beta(2, None).generation());
339 assert!(Version::Stable(2).generation() > Version::Alpha(1, Some(2)).generation());
340 assert!(Version::Stable(1).generation() < Version::Alpha(2, Some(2)).generation());
341 assert!(Version::Beta(1, None).generation() > Version::Nonconformant("ver3".into()).generation());
342
343 assert!(Version::Stable(2).generation() > Version::Stable(1).generation());
344 assert!(Version::Stable(1).generation() < Version::Beta(2, None).generation());
345 assert!(Version::Stable(1).generation() < Version::Beta(2, Some(2)).generation());
346 assert!(Version::Stable(1).generation() < Version::Alpha(2, None).generation());
347 assert!(Version::Stable(1).generation() < Version::Alpha(2, Some(3)).generation());
348 assert!(Version::Stable(1).generation() > Version::Nonconformant("foo".to_string()).generation());
349 assert!(Version::Beta(1, Some(1)).generation() > Version::Beta(1, None).generation());
350 assert!(Version::Beta(1, Some(2)).generation() > Version::Beta(1, Some(1)).generation());
351 assert!(Version::Beta(1, None).generation() > Version::Alpha(1, None).generation());
352 assert!(Version::Beta(1, None).generation() > Version::Alpha(1, Some(3)).generation());
353 assert!(Version::Beta(1, None).generation() > Version::Nonconformant("foo".to_string()).generation());
354 assert!(
355 Version::Beta(1, Some(2)).generation() > Version::Nonconformant("foo".to_string()).generation()
356 );
357 assert!(Version::Alpha(1, Some(1)).generation() > Version::Alpha(1, None).generation());
358 assert!(Version::Alpha(1, Some(2)).generation() > Version::Alpha(1, Some(1)).generation());
359 assert!(
360 Version::Alpha(1, None).generation() > Version::Nonconformant("foo".to_string()).generation()
361 );
362 assert!(
363 Version::Alpha(1, Some(2)).generation() > Version::Nonconformant("foo".to_string()).generation()
364 );
365 assert!(
366 Version::Nonconformant("bar".to_string()).generation()
367 > Version::Nonconformant("foo".to_string()).generation()
368 );
369 assert!(
370 Version::Nonconformant("foo1".to_string()).generation()
371 > Version::Nonconformant("foo10".to_string()).generation()
372 );
373
374 let mut vers = vec![
377 Version::Beta(2, Some(2)),
378 Version::Stable(1),
379 Version::Nonconformant("hi".into()),
380 Version::Alpha(3, Some(2)),
381 Version::Stable(2),
382 Version::Beta(2, Some(3)),
383 ];
384 vers.sort_by_cached_key(|x| Reverse(x.generation()));
385 assert_eq!(vers, vec![
386 Version::Alpha(3, Some(2)),
387 Version::Stable(2),
388 Version::Beta(2, Some(3)),
389 Version::Beta(2, Some(2)),
390 Version::Stable(1),
391 Version::Nonconformant("hi".into()),
392 ]);
393 }
394}