1use std::fmt;
19use std::str::FromStr;
20use std::sync::LazyLock;
21
22use regex::Regex;
23
24use super::VersionError;
25
26#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
34pub enum Version {
35 Pre10(u32, u32, u32),
38 Post10(u32, u32),
41}
42
43impl fmt::Display for Version {
44 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
45 match self {
46 Version::Pre10(a, b, c) => fmt.pad(&format!("{a}.{b}.{c}")),
47 Version::Post10(a, b) => fmt.pad(&format!("{a}.{b}")),
48 }
49 }
50}
51
52impl FromStr for Version {
53 type Err = VersionError;
54
55 fn from_str(s: &str) -> Result<Self, Self::Err> {
56 static RE: LazyLock<Regex> = LazyLock::new(|| {
57 Regex::new(r"(?x) \b (\d+) [.] (\d+) (?: [.] (\d+) )? \b")
58 .expect("invalid regex (for matching PostgreSQL versions)")
59 });
60 let badly_formed = |_| VersionError::BadlyFormed { text: Some(s.into()) };
61 match RE.captures(s) {
62 Some(caps) => {
63 let a = caps[1].parse::<u32>().map_err(badly_formed)?;
64 let b = caps[2].parse::<u32>().map_err(badly_formed)?;
65 match caps.get(3) {
66 None if a >= 10 => Ok(Version::Post10(a, b)),
67 None => Err(VersionError::BadlyFormed { text: Some(s.into()) }),
68 Some(_) if a >= 10 => Err(VersionError::BadlyFormed { text: Some(s.into()) }),
69 Some(m) => Ok(m
70 .as_str()
71 .parse::<u32>()
72 .map(|c| Version::Pre10(a, b, c))
73 .map_err(badly_formed)?),
74 }
75 }
76 None => Err(VersionError::NotFound { text: Some(s.into()) }),
77 }
78 }
79}
80
81#[cfg(test)]
82mod tests {
83 use super::Version::{Post10, Pre10};
84 use super::{Version, VersionError::*};
85
86 use std::cmp::Ordering;
87
88 #[test]
89 fn parses_version_below_10() {
90 assert_eq!(Ok(Pre10(9, 6, 17)), "9.6.17".parse());
91 }
92
93 #[test]
94 fn parses_version_above_10() {
95 assert_eq!(Ok(Post10(12, 2)), "12.2".parse());
96 }
97
98 #[test]
99 fn parse_returns_error_when_version_is_invalid() {
100 assert!(matches!(
102 "4294967296.0".parse::<Version>(),
103 Err(BadlyFormed { .. })
104 ));
105 }
106
107 #[test]
108 fn parse_returns_error_when_version_not_found() {
109 assert!(matches!("foo".parse::<Version>(), Err(NotFound { .. })));
110 }
111
112 #[test]
113 fn displays_version_below_10() {
114 assert_eq!("9.6.17", format!("{}", Pre10(9, 6, 17)));
115 }
116
117 #[test]
118 fn displays_version_above_10() {
119 assert_eq!("12.2", format!("{}", Post10(12, 2)));
120 }
121
122 #[test]
123 #[rustfmt::skip]
124 fn derive_partial_ord_works_as_expected() {
125 assert_eq!(Pre10(9, 10, 11).partial_cmp(&Post10(10, 11)), Some(Ordering::Less));
126 assert_eq!(Post10(10, 11).partial_cmp(&Pre10(9, 10, 11)), Some(Ordering::Greater));
127 assert_eq!(Pre10(9, 10, 11).partial_cmp(&Pre10(9, 10, 11)), Some(Ordering::Equal));
128 assert_eq!(Post10(10, 11).partial_cmp(&Post10(10, 11)), Some(Ordering::Equal));
129 }
130
131 #[test]
132 fn derive_ord_works_as_expected() {
133 let mut versions = vec![
134 Pre10(9, 10, 11),
135 Post10(10, 11),
136 Post10(14, 2),
137 Pre10(9, 10, 12),
138 Post10(10, 12),
139 ];
140 versions.sort(); assert_eq!(
142 versions,
143 vec![
144 Pre10(9, 10, 11),
145 Pre10(9, 10, 12),
146 Post10(10, 11),
147 Post10(10, 12),
148 Post10(14, 2)
149 ]
150 );
151 }
152}