1use std::cmp::Ordering;
2use std::fmt;
3use std::str::FromStr;
4
5use regex::Regex;
6
7use super::{Version, VersionError};
8
9#[derive(Copy, Clone, Debug)]
18pub enum PartialVersion {
19 Pre10m(u32, u32),
23 Pre10mm(u32, u32, u32),
27 Post10m(u32),
31 Post10mm(u32, u32),
35}
36
37impl From<&PartialVersion> for Version {
44 fn from(partial: &PartialVersion) -> Self {
45 use PartialVersion::*;
46 match *partial {
47 Pre10m(a, b) => Version::Pre10(a, b, 0),
48 Pre10mm(a, b, c) => Version::Pre10(a, b, c),
49 Post10m(a) => Version::Post10(a, 0),
50 Post10mm(a, b) => Version::Post10(a, b),
51 }
52 }
53}
54
55impl From<PartialVersion> for Version {
57 fn from(partial: PartialVersion) -> Self {
58 (&partial).into()
59 }
60}
61
62impl From<&Version> for PartialVersion {
64 fn from(version: &Version) -> Self {
65 use Version::*;
66 match *version {
67 Pre10(a, b, c) => PartialVersion::Pre10mm(a, b, c),
68 Post10(a, b) => PartialVersion::Post10mm(a, b),
69 }
70 }
71}
72
73impl From<Version> for PartialVersion {
75 fn from(version: Version) -> Self {
76 (&version).into()
77 }
78}
79
80impl PartialVersion {
81 pub fn checked(self) -> Result<Self, VersionError> {
89 use PartialVersion::*;
90 match self {
91 Pre10m(a, ..) | Pre10mm(a, ..) if a < 10 => Ok(self),
92 Post10m(a) | Post10mm(a, ..) if a >= 10 => Ok(self),
93 _ => Err(VersionError::BadlyFormed { text: Some(self.to_string()) }),
94 }
95 }
96
97 #[allow(dead_code)]
119 pub fn compatible(&self, version: Version) -> bool {
120 use PartialVersion::*;
121 match (*self, version) {
122 (Pre10m(a, b), Version::Pre10(x, y, _)) => a == x && b == y,
123 (Pre10mm(a, b, c), Version::Pre10(x, y, z)) => a == x && b == y && c <= z,
124 (Post10m(a), Version::Post10(x, _)) => a == x,
125 (Post10mm(a, b), Version::Post10(x, y)) => a == x && b <= y,
126 _ => false,
127 }
128 }
129
130 #[must_use]
132 pub fn widened(&self) -> PartialVersion {
133 use PartialVersion::*;
134 match self {
135 Pre10mm(a, b, _) => Pre10m(*a, *b),
136 Post10mm(a, _) => Post10m(*a),
137 _ => *self,
138 }
139 }
140
141 #[allow(dead_code)]
148 pub fn sort_key(&self) -> (u32, Option<u32>, Option<u32>) {
149 use PartialVersion::*;
150 match *self {
151 Pre10m(a, b) => (a, Some(b), None),
152 Pre10mm(a, b, c) => (a, Some(b), Some(c)),
153 Post10m(a) => (a, None, None),
154 Post10mm(a, b) => (a, Some(b), None),
155 }
156 }
157}
158
159impl PartialEq for PartialVersion {
160 fn eq(&self, other: &Self) -> bool {
161 self.partial_cmp(other) == Some(Ordering::Equal)
162 }
163}
164
165impl PartialOrd for PartialVersion {
166 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
167 use PartialVersion::*;
168 match (*self, *other) {
169 (Pre10m(a, b), Pre10m(x, y)) => Some((a, b).cmp(&(x, y))),
170 (Pre10m(a, b), Pre10mm(x, y, _)) => Some((a, b).cmp(&(x, y))),
171 (Pre10mm(a, b, _), Pre10m(x, y)) => Some((a, b).cmp(&(x, y))),
172 (Pre10mm(a, b, c), Pre10mm(x, y, z)) => Some((a, b, c).cmp(&(x, y, z))),
173
174 (Post10m(a), Post10m(x)) => Some(a.cmp(&x)),
175 (Post10m(a), Post10mm(x, _)) => Some(a.cmp(&x)),
176 (Post10mm(a, _), Post10m(x)) => Some(a.cmp(&x)),
177 (Post10mm(a, b), Post10mm(x, y)) => Some((a, b).cmp(&(x, y))),
178
179 (Pre10m(..), Post10m(..)) => Some(Ordering::Less),
180 (Pre10m(..), Post10mm(..)) => Some(Ordering::Less),
181 (Pre10mm(..), Post10m(..)) => Some(Ordering::Less),
182 (Pre10mm(..), Post10mm(..)) => Some(Ordering::Less),
183
184 (Post10m(..), Pre10m(..)) => Some(Ordering::Greater),
185 (Post10m(..), Pre10mm(..)) => Some(Ordering::Greater),
186 (Post10mm(..), Pre10m(..)) => Some(Ordering::Greater),
187 (Post10mm(..), Pre10mm(..)) => Some(Ordering::Greater),
188 }
189 }
190}
191
192impl fmt::Display for PartialVersion {
193 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
194 match *self {
195 Self::Pre10m(a, b) => fmt.pad(&format!("{a}.{b}")),
196 Self::Pre10mm(a, b, c) => fmt.pad(&format!("{a}.{b}.{c}")),
197 Self::Post10m(a) => fmt.pad(&format!("{a}")),
198 Self::Post10mm(a, b) => fmt.pad(&format!("{a}.{b}")),
199 }
200 }
201}
202
203impl FromStr for PartialVersion {
204 type Err = VersionError;
205
206 fn from_str(s: &str) -> Result<Self, Self::Err> {
207 lazy_static! {
208 static ref RE: Regex =
209 Regex::new(r"(?x) \b (\d+) (?: [.] (\d+) (?: [.] (\d+) )? )? \b")
210 .expect("invalid regex (for matching partial PostgreSQL versions)");
211 }
212 match RE.captures(s) {
213 Some(caps) => match (
214 caps.get(1).and_then(|n| n.as_str().parse::<u32>().ok()),
215 caps.get(2).and_then(|n| n.as_str().parse::<u32>().ok()),
216 caps.get(3).and_then(|n| n.as_str().parse::<u32>().ok()),
217 ) {
218 (Some(a), Some(b), None) if a < 10 => Ok(Self::Pre10m(a, b)),
219 (Some(a), Some(b), Some(c)) if a < 10 => Ok(Self::Pre10mm(a, b, c)),
220 (Some(a), None, None) if a >= 10 => Ok(Self::Post10m(a)),
221 (Some(a), Some(b), None) if a >= 10 => Ok(Self::Post10mm(a, b)),
222 _ => Err(VersionError::BadlyFormed { text: Some(s.into()) }),
223 },
224 None => Err(VersionError::NotFound { text: Some(s.into()) }),
225 }
226 }
227}
228
229#[cfg(test)]
230mod tests {
231 use super::super::{Version, VersionError::*};
232 use super::{PartialVersion, PartialVersion::*};
233
234 use rand::seq::SliceRandom;
235 use rand::thread_rng;
236
237 #[test]
238 fn parses_version_below_10() {
239 assert_eq!(Ok(Pre10mm(9, 6, 17)), "9.6.17".parse());
240 assert_eq!(Ok(Pre10m(9, 6)), "9.6".parse());
241 }
242
243 #[test]
244 fn parses_version_above_10() {
245 assert_eq!(Ok(Post10mm(12, 2)), "12.2".parse());
246 assert_eq!(Ok(Post10m(12)), "12".parse());
247 }
248
249 #[test]
250 fn parse_returns_error_when_version_is_invalid() {
251 assert!(matches!(
253 "4294967296.0".parse::<PartialVersion>(),
254 Err(BadlyFormed { .. })
255 ));
256 assert!(matches!(
258 "9".parse::<PartialVersion>(),
259 Err(BadlyFormed { .. })
260 ));
261 assert!(matches!(
263 "10.10.10".parse::<PartialVersion>(),
264 Err(BadlyFormed { .. })
265 ));
266 }
267
268 #[test]
269 fn parse_returns_error_when_version_not_found() {
270 assert!(matches!(
271 "foo".parse::<PartialVersion>(),
272 Err(NotFound { .. })
273 ));
274 }
275
276 #[test]
277 fn checked_returns_self_when_variant_is_valid() {
278 use PartialVersion::*;
279 assert_eq!(Ok(Pre10m(9, 0)), Pre10m(9, 0).checked());
280 assert_eq!(Ok(Pre10mm(9, 0, 0)), Pre10mm(9, 0, 0).checked());
281 assert_eq!(Ok(Post10m(10)), Post10m(10).checked());
282 assert_eq!(Ok(Post10mm(10, 0)), Post10mm(10, 0).checked());
283 }
284
285 #[test]
286 fn checked_returns_error_when_variant_is_invalid() {
287 use PartialVersion::*;
288 assert!(matches!(Pre10m(10, 0).checked(), Err(BadlyFormed { .. })));
289 assert!(matches!(
290 Pre10mm(10, 0, 0).checked(),
291 Err(BadlyFormed { .. })
292 ));
293 assert!(matches!(Post10m(9).checked(), Err(BadlyFormed { .. })));
294 assert!(matches!(Post10mm(9, 0).checked(), Err(BadlyFormed { .. })));
295 }
296
297 #[test]
298 fn displays_version_below_10() {
299 assert_eq!("9.6.17", format!("{}", Pre10mm(9, 6, 17)));
300 assert_eq!("9.6", format!("{}", Pre10m(9, 6)));
301 }
302
303 #[test]
304 fn displays_version_above_10() {
305 assert_eq!("12.2", format!("{}", Post10mm(12, 2)));
306 assert_eq!("12", format!("{}", Post10m(12)));
307 }
308
309 #[test]
310 fn converts_partial_version_to_version() {
311 assert_eq!(Version::Pre10(9, 1, 2), Pre10mm(9, 1, 2).into());
312 assert_eq!(Version::Pre10(9, 1, 0), Pre10m(9, 1).into());
313 assert_eq!(Version::Post10(14, 2), Post10mm(14, 2).into());
314 assert_eq!(Version::Post10(14, 0), Post10m(14).into());
315 }
316
317 #[test]
318 fn compatible_below_10() {
319 let version = "9.6.16".parse().unwrap();
320 assert!(Pre10mm(9, 6, 16).compatible(version));
321 assert!(Pre10m(9, 6).compatible(version));
322 }
323
324 #[test]
325 fn not_compatible_below_10() {
326 let version = "9.6.16".parse().unwrap();
327 assert!(!Pre10mm(9, 6, 17).compatible(version));
328 assert!(!Pre10m(9, 7).compatible(version));
329 assert!(!Pre10mm(8, 6, 16).compatible(version));
330 assert!(!Pre10m(8, 6).compatible(version));
331 }
332
333 #[test]
334 fn compatible_above_10() {
335 let version = "12.6".parse().unwrap();
336 assert!(Post10mm(12, 6).compatible(version));
337 assert!(Post10m(12).compatible(version));
338 }
339
340 #[test]
341 fn not_compatible_above_10() {
342 let version = "12.6".parse().unwrap();
343 assert!(!Post10mm(12, 7).compatible(version));
344 assert!(!Post10m(13).compatible(version));
345 assert!(!Post10mm(11, 6).compatible(version));
346 assert!(!Post10m(11).compatible(version));
347 }
348
349 #[test]
350 fn not_compatible_below_10_with_above_10() {
351 let version = "12.6".parse().unwrap();
352 assert!(!Pre10m(9, 1).compatible(version));
353 assert!(!Pre10mm(9, 1, 2).compatible(version));
354 let version = "9.1.2".parse().unwrap();
355 assert!(!Post10m(12).compatible(version));
356 assert!(!Post10mm(12, 6).compatible(version));
357 }
358
359 #[test]
360 fn widened_removes_minor_or_patch_number() {
361 assert_eq!(Pre10mm(9, 1, 2), Pre10m(9, 1));
362 assert_eq!(Post10mm(12, 9), Post10m(12));
363 assert_eq!(Pre10m(9, 1), Pre10m(9, 1));
364 assert_eq!(Post10m(12), Post10m(12));
365 }
366
367 #[test]
368 fn partial_ord_works_as_expected() {
369 let mut versions = vec![
370 Pre10mm(9, 10, 11),
371 Pre10mm(9, 10, 12),
372 Pre10m(8, 11),
373 Pre10m(9, 11),
374 Pre10m(9, 12),
375 Post10mm(10, 11),
376 Post10m(11),
377 ];
378 let mut rng = thread_rng();
379 for _ in 0..1000 {
380 versions.shuffle(&mut rng);
381 versions.sort_by(|a, b| a.partial_cmp(b).unwrap());
382 assert_eq!(
383 versions,
384 vec![
385 Pre10m(8, 11),
386 Pre10mm(9, 10, 11),
387 Pre10mm(9, 10, 12),
388 Pre10m(9, 11),
389 Pre10m(9, 12),
390 Post10mm(10, 11),
391 Post10m(11),
392 ]
393 );
394 }
395 }
396
397 #[test]
398 fn sort_key_works_as_expected() {
399 let mut versions = vec![
400 Pre10mm(9, 0, 0),
401 Pre10mm(9, 10, 11),
402 Pre10mm(9, 10, 12),
403 Pre10m(9, 0),
404 Pre10m(8, 11),
405 Pre10m(9, 11),
406 Pre10m(9, 12),
407 Post10mm(10, 11),
408 Post10m(11),
409 ];
410 let mut rng = thread_rng();
411 for _ in 0..1000 {
412 versions.shuffle(&mut rng);
413 versions.sort_by_key(PartialVersion::sort_key);
414 assert_eq!(
415 versions,
416 vec![
417 Pre10m(8, 11),
418 Pre10m(9, 0),
419 Pre10mm(9, 0, 0),
420 Pre10mm(9, 10, 11),
421 Pre10mm(9, 10, 12),
422 Pre10m(9, 11),
423 Pre10m(9, 12),
424 Post10mm(10, 11),
425 Post10m(11),
426 ]
427 );
428 }
429 }
430}