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