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