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