1use std::cmp::Ordering;
3
4#[derive(Debug, PartialEq, Eq, std::hash::Hash, Clone)]
5pub struct Version {
11 pub components: Vec<u32>,
13}
14
15impl std::fmt::Display for Version {
16 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
17 f.write_str(
18 &self
19 .components
20 .iter()
21 .map(|c| c.to_string())
22 .collect::<Vec<_>>()
23 .join("."),
24 )
25 }
26}
27
28impl Version {
29 pub fn new(major: u32, minor: u32, patch: Option<u32>) -> Self {
31 Self {
32 components: if let Some(patch) = patch {
33 vec![major, minor, patch]
34 } else {
35 vec![major, minor]
36 },
37 }
38 }
39}
40
41impl std::str::FromStr for Version {
42 type Err = String;
43
44 fn from_str(s: &str) -> Result<Self, Self::Err> {
45 let components = s
48 .split(|c| c == '.' || c == '-')
49 .map(|part| {
50 part.parse()
51 .map_err(|_| format!("Invalid version component: {s}"))
52 })
53 .collect::<Result<Vec<_>, _>>()?;
54
55 if components.len() < 2 {
56 return Err(format!("Invalid version string: {s}"));
57 }
58
59 Ok(Self { components })
60 }
61}
62
63impl Ord for Version {
64 fn cmp(&self, other: &Self) -> Ordering {
65 for (a, b) in self.components.iter().zip(other.components.iter()) {
66 match a.cmp(b) {
67 Ordering::Equal => continue,
68 ordering => return ordering,
69 }
70 }
71 self.components.len().cmp(&other.components.len())
72 }
73}
74
75impl PartialOrd for Version {
76 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
77 Some(self.cmp(other))
78 }
79}
80
81#[cfg(test)]
82mod tests {
83 use super::Version;
84 use std::str::FromStr;
85
86 #[test]
87 fn test_version_from_str() {
88 let version = Version::from_str("1.2.3").unwrap();
89 assert_eq!(version, Version::new(1, 2, Some(3)));
90
91 let version = Version::from_str("2.5-1").unwrap();
93 assert_eq!(version.components, vec![2, 5, 1]);
94
95 let version = Version::from_str("1.2.3.9000").unwrap();
97 assert_eq!(version.components, vec![1, 2, 3, 9000]);
98 }
99
100 #[test]
101 fn test_version_cmp() {
102 use std::cmp::Ordering;
103
104 let v1 = Version::from_str("1.2.3").unwrap();
105 let v2 = Version::from_str("1.2.3").unwrap();
106 assert_eq!(v1.cmp(&v2), Ordering::Equal);
107
108 let v1 = Version::from_str("1.2.3").unwrap();
109 let v2 = Version::from_str("1.2.4").unwrap();
110 assert_eq!(v1.cmp(&v2), Ordering::Less);
111
112 let v1 = Version::from_str("2.5-1").unwrap();
114 let v2 = Version::from_str("2.5.1").unwrap();
115 assert_eq!(v1.cmp(&v2), Ordering::Equal);
116
117 let v1 = Version::from_str("1.2.3.9000").unwrap();
119 let v2 = Version::from_str("1.2.3").unwrap();
120 assert_eq!(v1.cmp(&v2), Ordering::Greater);
121
122 let v1 = Version::from_str("1.2.3.9000").unwrap();
123 let v2 = Version::from_str("1.2.4").unwrap();
124 assert_eq!(v1.cmp(&v2), Ordering::Less);
125 }
126
127 #[test]
128 fn test_version_display() {
129 let version = Version::from_str("1.2.3").unwrap();
131 assert_eq!(version.to_string(), "1.2.3");
132
133 let version = Version::from_str("2.5-1").unwrap();
134 assert_eq!(version.to_string(), "2.5.1");
135 }
136
137 #[test]
138 fn test_version_invalid() {
139 assert!(Version::from_str("a").is_err());
140 assert!(Version::from_str("1.a.3").is_err());
141 assert!(Version::from_str("1").is_err());
143 }
144}