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, 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}