1use std::fmt;
2use std::fmt::Formatter;
3use std::str::FromStr;
4
5#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
11pub struct RustVersion {
12 version: version_number::FullVersion,
13}
14
15impl RustVersion {
16 pub fn new(major: u64, minor: u64, patch: u64) -> Self {
22 Self {
23 version: version_number::FullVersion {
24 major,
25 minor,
26 patch,
27 },
28 }
29 }
30}
31
32impl RustVersion {
33 pub fn major(&self) -> u64 {
35 self.version.major
36 }
37
38 pub fn minor(&self) -> u64 {
40 self.version.minor
41 }
42
43 pub fn patch(&self) -> u64 {
45 self.version.patch
46 }
47}
48
49impl FromStr for RustVersion {
50 type Err = ParseError;
51
52 fn from_str(s: &str) -> Result<Self, Self::Err> {
53 use version_number::parsers::error::ExpectedError;
54 use version_number::parsers::error::NumericError;
55 use version_number::ParserError;
56
57 version_number::FullVersion::parse(s)
58 .map(|version| Self { version })
59 .map_err(|e| match e {
60 ParserError::Expected(inner) => match inner {
61 ExpectedError::Numeric { got, .. } => ParseError::Expected("0-9", got),
62 ExpectedError::Separator { got, .. } => ParseError::Expected(".", got),
63 ExpectedError::EndOfInput { got, .. } => ParseError::Expected("EOI", Some(got)),
64 },
65 ParserError::Numeric(inner) => match inner {
66 NumericError::LeadingZero => ParseError::LeadingZero,
67 NumericError::Overflow => ParseError::NumberOverflow,
68 },
69 })
70 }
71}
72
73impl fmt::Display for RustVersion {
74 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
75 write!(f, "{}", self.version)
76 }
77}
78
79#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
80pub enum ParseError {
81 #[error("Expected '{0}' but got '{got}'", got = .1.map(|c| c.to_string()).unwrap_or_default())]
82 Expected(&'static str, Option<char>),
83
84 #[error("expected token 1-9, but got '0' (leading zero is not permitted)")]
85 LeadingZero,
86
87 #[error("unable to parse number (overflow occurred)")]
88 NumberOverflow,
89}
90
91#[cfg(test)]
92mod tests {
93 use super::*;
94 use std::cmp::Ordering;
95
96 #[test]
97 fn create_rust_version() {
98 let version = RustVersion::new(1, 2, 3);
99
100 assert_eq!(version.major(), 1);
101 assert_eq!(version.minor(), 2);
102 assert_eq!(version.patch(), 3);
103 }
104
105 #[test]
106 fn display() {
107 let version = RustVersion::new(12, 2, 24);
108
109 assert_eq!(&format!("{version}"), "12.2.24");
110 }
111
112 #[test]
113 fn partial_eq() {
114 let left = RustVersion::new(1, 2, 3);
115 let right = RustVersion::new(1, 2, 3);
116
117 assert_eq!(left, right);
118 }
119
120 #[test]
121 fn eq() {
122 let left = RustVersion::new(1, 2, 3);
123 let right = RustVersion::new(1, 2, 3);
124
125 assert!(left.eq(&right));
126 }
127
128 #[yare::parameterized(
129 on_major = { RustVersion::new(1, 0, 0), RustVersion::new(0, 0, 0), Ordering::Greater },
130 on_minor = { RustVersion::new(1, 1, 0), RustVersion::new(1, 0, 0), Ordering::Greater },
131 on_patch = { RustVersion::new(1, 1, 1), RustVersion::new(1, 1, 0), Ordering::Greater },
132 eq = { RustVersion::new(1, 1, 1), RustVersion::new(1, 1, 1), Ordering::Equal },
133 )]
134 fn ordering(left: RustVersion, right: RustVersion, expected_ord: Ordering) {
135 assert_eq!(left.partial_cmp(&right), Some(expected_ord));
136 assert_eq!(left.cmp(&right), expected_ord);
137 }
138
139 mod partial_eq {
140 use super::*;
141
142 #[test]
143 fn symmetric() {
144 let left = RustVersion::new(1, 2, 3);
145 let right = RustVersion::new(1, 2, 3);
146
147 assert_eq!(
148 left, right,
149 "PartialEq should be symmetric: 'left == right' must hold"
150 );
151 assert_eq!(
152 right, left,
153 "PartialEq should be symmetric: 'right == left' must hold"
154 );
155 }
156
157 #[test]
158 fn transitive() {
159 let a = RustVersion::new(1, 2, 3);
160 let b = RustVersion::new(1, 2, 3);
161 let c = RustVersion::new(1, 2, 3);
162
163 assert_eq!(
164 a, b,
165 "PartialEq should be transitive: 'a == b' must hold, by symmetric property"
166 );
167 assert_eq!(
168 b, c,
169 "PartialEq should be transitive: 'b == c' must hold, by symmetric property"
170 );
171
172 assert_eq!(a, c, "PartialEq should be transitive: 'a == c' must hold, given a == b (prior) and b == c (prior)");
173 }
174 }
175
176 mod partial_ord {
177 use super::*;
178
179 #[test]
180 fn equality() {
181 let a = RustVersion::new(1, 2, 3);
182 let b = RustVersion::new(1, 2, 3);
183
184 assert_eq!(
185 a, b,
186 "PartialOrd should hold for equality: 'a == b' must hold"
187 );
188 assert_eq!(a.partial_cmp(&b), Some(Ordering::Equal), "PartialOrd should hold for equality: 'a.partial_cmp(&b) == Ordering::Equal' must hold");
189 }
190
191 #[test]
192 fn transitive_lt() {
193 let a = RustVersion::new(1, 2, 1);
194 let b = RustVersion::new(1, 2, 2);
195 let c = RustVersion::new(1, 2, 3);
196
197 assert!(a < b, "PartialOrd should be transitive: 'a < b' must hold");
198 assert!(b < c, "PartialOrd should be transitive: 'b < c' must hold");
199 assert!(a < c, "PartialOrd should be transitive: 'a < c' must hold, given a < b (prior) and b < c (prior)");
200 }
201
202 #[test]
203 fn transitive_gt() {
204 let a = RustVersion::new(1, 2, 3);
205 let b = RustVersion::new(1, 2, 2);
206 let c = RustVersion::new(1, 2, 1);
207
208 assert!(a > b, "PartialOrd should be transitive: 'a > b' must hold");
209 assert!(b > c, "PartialOrd should be transitive: 'b > c' must hold");
210 assert!(a > c, "PartialOrd should be transitive: 'a > c' must hold, given a > b (prior) and b > c (prior)");
211 }
212 }
213}