/src/semver-parser/src/version.rs
Line | Count | Source (jump to first uncovered line) |
1 | | //! Version data and functions. |
2 | | //! |
3 | | //! This module contains [`Version`] struct, [`parse`] function for building |
4 | | //! [`Version`] struct from string and some helper data structures and functions. |
5 | | //! |
6 | | //! # Examples |
7 | | //! |
8 | | //! Parsing `Version` from string and checking its fields: |
9 | | //! |
10 | | //! ``` |
11 | | //! use semver_parser::version; |
12 | | //! |
13 | | //! # fn try_main() -> Result<(), String> { |
14 | | //! let version = version::parse("1.2.3-alpha1")?; |
15 | | //! |
16 | | //! assert_eq!(version.major, 1); |
17 | | //! assert_eq!(version.minor, 2); |
18 | | //! assert_eq!(version.patch, 3); |
19 | | //! |
20 | | //! let expected_pre = vec![ |
21 | | //! version::Identifier::AlphaNumeric(String::from("alpha1")), |
22 | | //! ]; |
23 | | //! |
24 | | //! assert_eq!(expected_pre, version.pre); |
25 | | //! # Ok(()) |
26 | | //! # } |
27 | | //! # |
28 | | //! # try_main().unwrap(); |
29 | | //! ``` |
30 | | //! [`Version`]: ./struct.Version.html |
31 | | //! [`parse`]: ./fn.parse.html |
32 | | |
33 | | use crate::parser::{self, Parser}; |
34 | | use std::fmt; |
35 | | |
36 | | /// Structure representing version data. |
37 | | /// |
38 | | /// `Version` struct has some public fields representing version data, like major/minor version |
39 | | /// string, patch number and vectors of prefix and build identifiers. |
40 | | /// |
41 | | /// # Examples |
42 | | /// |
43 | | /// Parsing `Version` from string and checking its fields: |
44 | | /// |
45 | | /// ``` |
46 | | /// use semver_parser::version; |
47 | | /// |
48 | | /// # fn try_main() -> Result<(), String> { |
49 | | /// let version = version::parse("0.1.2-alpha1")?; |
50 | | /// assert_eq!(version.major, 0); |
51 | | /// assert_eq!(version.minor, 1); |
52 | | /// assert_eq!(version.patch, 2); |
53 | | /// let expected_pre = vec![version::Identifier::AlphaNumeric(String::from("alpha1"))]; |
54 | | /// assert_eq!(expected_pre, version.pre); |
55 | | /// # Ok(()) |
56 | | /// # } |
57 | | /// # |
58 | | /// # try_main().unwrap(); |
59 | | /// ``` |
60 | | #[derive(Clone, PartialOrd, Ord, Hash, Debug, PartialEq, Eq)] |
61 | | pub struct Version { |
62 | | /// Major version as number (`0` in `"0.1.2"`). |
63 | | pub major: u64, |
64 | | /// Minor version as number (`1` in `"0.1.2"`). |
65 | | pub minor: u64, |
66 | | /// Patch version as number (`2` in `"0.1.2"`). |
67 | | pub patch: u64, |
68 | | /// Pre-release metadata as a vector of `Identifier` (`"alpha1"` in `"0.1.2-alpha1"` |
69 | | /// or `7` (numeric) in `"0.1.2-7"`, `"pre"` and `0` (numeric) in `"0.1.2-pre.0"`). |
70 | | pub pre: Vec<Identifier>, |
71 | | /// Build metadata as a vector of `Identifier` (`"build1"` in `"0.1.2+build1"` |
72 | | /// or `7` (numeric) in `"0.1.2+7"`, `"build"` and `0` (numeric) in `"0.1.2+pre.0"`). |
73 | | pub build: Vec<Identifier>, |
74 | | } |
75 | | |
76 | | /// Helper enum for holding data of alphanumeric or numeric suffix identifiers. |
77 | | /// |
78 | | /// This enum is used to hold suffix parts of `pre` and `build` fields of |
79 | | /// [`Version`] struct. Theses suffixes may be either numeric or alphanumeric. |
80 | | /// |
81 | | /// # Examples |
82 | | /// |
83 | | /// Parsing [`Version`] with pre-release part composed of two `Identifier`s: |
84 | | /// |
85 | | /// ``` |
86 | | /// use semver_parser::version; |
87 | | /// |
88 | | /// # fn try_main() -> Result<(), String> { |
89 | | /// let version = version::parse("0.1.2-alpha1.0")?; |
90 | | /// |
91 | | /// let expected_pre = vec![ |
92 | | /// version::Identifier::AlphaNumeric(String::from("alpha1")), |
93 | | /// version::Identifier::Numeric(0), |
94 | | /// ]; |
95 | | /// |
96 | | /// assert_eq!(expected_pre, version.pre); |
97 | | /// # Ok(()) |
98 | | /// # } |
99 | | /// # |
100 | | /// # try_main().unwrap(); |
101 | | /// ``` |
102 | | /// [`Version`]: ./struct.Version.html |
103 | | #[derive(Clone, PartialOrd, Ord, Hash, Debug, PartialEq, Eq)] |
104 | | pub enum Identifier { |
105 | | /// An identifier that's solely numbers. |
106 | | Numeric(u64), |
107 | | /// An identifier with letters and numbers. |
108 | | AlphaNumeric(String), |
109 | | } |
110 | | |
111 | | impl Identifier { |
112 | 387k | pub fn concat(self, add_str: &str) -> Identifier { |
113 | 387k | match self { |
114 | 192k | Identifier::Numeric(n) => Identifier::AlphaNumeric(format!("{}{}", n, add_str)), |
115 | 194k | Identifier::AlphaNumeric(s) => Identifier::AlphaNumeric(format!("{}{}", s, add_str)), |
116 | | } |
117 | 387k | } |
118 | | } |
119 | | |
120 | | /// Function for parsing version string to [`Version`]. |
121 | | /// |
122 | | /// Returns `Result<`[`Version`]`, String>`, where `String` represents an error while parsing. |
123 | | /// |
124 | | /// # Examples |
125 | | /// |
126 | | /// Parsing [`Version`] from string and checking its fields: |
127 | | /// |
128 | | /// ``` |
129 | | /// use semver_parser::version; |
130 | | /// |
131 | | /// # fn try_main() -> Result<(), String> { |
132 | | /// let version = version::parse("0.1.2-alpha1")?; |
133 | | /// assert_eq!(version.major, 0); |
134 | | /// assert_eq!(version.minor, 1); |
135 | | /// assert_eq!(version.patch, 2); |
136 | | /// let expected_pre = vec![version::Identifier::AlphaNumeric(String::from("alpha1"))]; |
137 | | /// assert_eq!(expected_pre, version.pre); |
138 | | /// # Ok(()) |
139 | | /// # } |
140 | | /// # |
141 | | /// # try_main().unwrap(); |
142 | | /// ``` |
143 | | /// [`Version`]: ./struct.Version.html |
144 | 0 | pub fn parse(input: &str) -> Result<Version, parser::Error> { |
145 | 0 | let mut parser = Parser::new(input)?; |
146 | 0 | let version = parser.version()?; |
147 | | |
148 | 0 | if !parser.is_eof() { |
149 | 0 | return Err(parser::Error::MoreInput(parser.tail()?)); |
150 | 0 | } |
151 | 0 |
|
152 | 0 | Ok(version) |
153 | 0 | } |
154 | | |
155 | | impl fmt::Display for Version { |
156 | 0 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
157 | 0 | write!(f, "{}.{}.{}", self.major, self.minor, self.patch).expect("write failed"); |
158 | 0 | if !self.pre.is_empty() { |
159 | 0 | let strs: Vec<_> = self.pre.iter().map(ToString::to_string).collect(); |
160 | 0 | write!(f, "-{}", strs.join(".")).expect("write failed"); |
161 | 0 | } |
162 | 0 | if !self.build.is_empty() { |
163 | 0 | let strs: Vec<_> = self.build.iter().map(ToString::to_string).collect(); |
164 | 0 | write!(f, "+{}", strs.join(".")).expect("write failed"); |
165 | 0 | } |
166 | 0 | Ok(()) |
167 | 0 | } |
168 | | } |
169 | | |
170 | | impl fmt::Display for Identifier { |
171 | 193k | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
172 | 193k | match *self { |
173 | 149k | Identifier::Numeric(ref id) => id.fmt(f), |
174 | 43.7k | Identifier::AlphaNumeric(ref id) => id.fmt(f), |
175 | | } |
176 | 193k | } |
177 | | } |
178 | | |
179 | | #[cfg(test)] |
180 | | mod tests { |
181 | | use super::*; |
182 | | use crate::version; |
183 | | |
184 | | #[test] |
185 | | fn parse_empty() { |
186 | | let version = ""; |
187 | | |
188 | | let parsed = version::parse(version); |
189 | | |
190 | | assert!( |
191 | | parsed.is_err(), |
192 | | "empty string incorrectly considered a valid parse" |
193 | | ); |
194 | | } |
195 | | |
196 | | #[test] |
197 | | fn parse_blank() { |
198 | | let version = " "; |
199 | | |
200 | | let parsed = version::parse(version); |
201 | | |
202 | | assert!( |
203 | | parsed.is_err(), |
204 | | "blank string incorrectly considered a valid parse" |
205 | | ); |
206 | | } |
207 | | |
208 | | #[test] |
209 | | fn parse_no_minor_patch() { |
210 | | let version = "1"; |
211 | | |
212 | | let parsed = version::parse(version); |
213 | | |
214 | | assert!( |
215 | | parsed.is_err(), |
216 | | "'{}' incorrectly considered a valid parse", version |
217 | | ); |
218 | | } |
219 | | |
220 | | #[test] |
221 | | fn parse_no_patch() { |
222 | | let version = "1.2"; |
223 | | |
224 | | let parsed = version::parse(version); |
225 | | |
226 | | assert!( |
227 | | parsed.is_err(), |
228 | | "'{}' incorrectly considered a valid parse", version |
229 | | ); |
230 | | } |
231 | | |
232 | | #[test] |
233 | | fn parse_empty_pre() { |
234 | | let version = "1.2.3-"; |
235 | | |
236 | | let parsed = version::parse(version); |
237 | | |
238 | | assert!( |
239 | | parsed.is_err(), |
240 | | "'{}' incorrectly considered a valid parse", version |
241 | | ); |
242 | | } |
243 | | |
244 | | #[test] |
245 | | fn parse_letters() { |
246 | | let version = "a.b.c"; |
247 | | |
248 | | let parsed = version::parse(version); |
249 | | |
250 | | assert!( |
251 | | parsed.is_err(), |
252 | | "'{}' incorrectly considered a valid parse", version |
253 | | ); |
254 | | } |
255 | | |
256 | | #[test] |
257 | | fn parse_with_letters() { |
258 | | let version = "1.2.3 a.b.c"; |
259 | | |
260 | | let parsed = version::parse(version); |
261 | | |
262 | | assert!( |
263 | | parsed.is_err(), |
264 | | "'{}' incorrectly considered a valid parse", version |
265 | | ); |
266 | | } |
267 | | |
268 | | #[test] |
269 | | fn parse_basic_version() { |
270 | | let version = "1.2.3"; |
271 | | |
272 | | let parsed = version::parse(version).unwrap(); |
273 | | |
274 | | assert_eq!(1, parsed.major); |
275 | | assert_eq!(2, parsed.minor); |
276 | | assert_eq!(3, parsed.patch); |
277 | | } |
278 | | |
279 | | #[test] |
280 | | fn parse_trims_input() { |
281 | | let version = " 1.2.3 "; |
282 | | |
283 | | let parsed = version::parse(version).unwrap(); |
284 | | |
285 | | assert_eq!(1, parsed.major); |
286 | | assert_eq!(2, parsed.minor); |
287 | | assert_eq!(3, parsed.patch); |
288 | | } |
289 | | |
290 | | #[test] |
291 | | fn parse_no_major_leading_zeroes() { |
292 | | let version = "01.0.0"; |
293 | | |
294 | | let parsed = version::parse(version); |
295 | | |
296 | | assert!( |
297 | | parsed.is_err(), |
298 | | "01 incorrectly considered a valid major version" |
299 | | ); |
300 | | } |
301 | | |
302 | | #[test] |
303 | | fn parse_no_minor_leading_zeroes() { |
304 | | let version = "0.01.0"; |
305 | | |
306 | | let parsed = version::parse(version); |
307 | | |
308 | | assert!( |
309 | | parsed.is_err(), |
310 | | "01 incorrectly considered a valid minor version" |
311 | | ); |
312 | | } |
313 | | |
314 | | #[test] |
315 | | fn parse_no_patch_leading_zeroes() { |
316 | | let version = "0.0.01"; |
317 | | |
318 | | let parsed = version::parse(version); |
319 | | |
320 | | assert!( |
321 | | parsed.is_err(), |
322 | | "01 incorrectly considered a valid patch version" |
323 | | ); |
324 | | } |
325 | | |
326 | | #[test] |
327 | | fn parse_no_major_overflow() { |
328 | | let version = "98765432109876543210.0.0"; |
329 | | |
330 | | let parsed = version::parse(version); |
331 | | |
332 | | assert!( |
333 | | parsed.is_err(), |
334 | | "98765432109876543210 incorrectly considered a valid major version" |
335 | | ); |
336 | | } |
337 | | |
338 | | #[test] |
339 | | fn parse_no_minor_overflow() { |
340 | | let version = "0.98765432109876543210.0"; |
341 | | |
342 | | let parsed = version::parse(version); |
343 | | |
344 | | assert!( |
345 | | parsed.is_err(), |
346 | | "98765432109876543210 incorrectly considered a valid minor version" |
347 | | ); |
348 | | } |
349 | | |
350 | | #[test] |
351 | | fn parse_no_patch_overflow() { |
352 | | let version = "0.0.98765432109876543210"; |
353 | | |
354 | | let parsed = version::parse(version); |
355 | | |
356 | | assert!( |
357 | | parsed.is_err(), |
358 | | "98765432109876543210 incorrectly considered a valid patch version" |
359 | | ); |
360 | | } |
361 | | |
362 | | #[test] |
363 | | fn parse_basic_prerelease() { |
364 | | let version = "1.2.3-pre"; |
365 | | |
366 | | let parsed = version::parse(version).unwrap(); |
367 | | |
368 | | let expected_pre = vec![Identifier::AlphaNumeric(String::from("pre"))]; |
369 | | assert_eq!(expected_pre, parsed.pre); |
370 | | } |
371 | | |
372 | | #[test] |
373 | | fn parse_prerelease_alphanumeric() { |
374 | | let version = "1.2.3-alpha1"; |
375 | | |
376 | | let parsed = version::parse(version).unwrap(); |
377 | | |
378 | | let expected_pre = vec![Identifier::AlphaNumeric(String::from("alpha1"))]; |
379 | | assert_eq!(expected_pre, parsed.pre); |
380 | | } |
381 | | |
382 | | #[test] |
383 | | fn parse_prerelease_zero() { |
384 | | let version = "1.2.3-pre.0"; |
385 | | |
386 | | let parsed = version::parse(version).unwrap(); |
387 | | |
388 | | let expected_pre = vec![ |
389 | | Identifier::AlphaNumeric(String::from("pre")), |
390 | | Identifier::Numeric(0), |
391 | | ]; |
392 | | assert_eq!(expected_pre, parsed.pre); |
393 | | } |
394 | | |
395 | | #[test] |
396 | | fn parse_basic_build() { |
397 | | let version = "1.2.3+build"; |
398 | | |
399 | | let parsed = version::parse(version).unwrap(); |
400 | | |
401 | | let expected_build = vec![Identifier::AlphaNumeric(String::from("build"))]; |
402 | | assert_eq!(expected_build, parsed.build); |
403 | | } |
404 | | |
405 | | #[test] |
406 | | fn parse_build_alphanumeric() { |
407 | | let version = "1.2.3+build5"; |
408 | | |
409 | | let parsed = version::parse(version).unwrap(); |
410 | | |
411 | | let expected_build = vec![Identifier::AlphaNumeric(String::from("build5"))]; |
412 | | assert_eq!(expected_build, parsed.build); |
413 | | } |
414 | | |
415 | | #[test] |
416 | | fn parse_pre_and_build() { |
417 | | let version = "1.2.3-alpha1+build5"; |
418 | | |
419 | | let parsed = version::parse(version).unwrap(); |
420 | | |
421 | | let expected_pre = vec![Identifier::AlphaNumeric(String::from("alpha1"))]; |
422 | | assert_eq!(expected_pre, parsed.pre); |
423 | | |
424 | | let expected_build = vec![Identifier::AlphaNumeric(String::from("build5"))]; |
425 | | assert_eq!(expected_build, parsed.build); |
426 | | } |
427 | | |
428 | | #[test] |
429 | | fn parse_complex_metadata_01() { |
430 | | let version = "1.2.3-1.alpha1.9+build5.7.3aedf "; |
431 | | |
432 | | let parsed = version::parse(version).unwrap(); |
433 | | |
434 | | let expected_pre = vec![ |
435 | | Identifier::Numeric(1), |
436 | | Identifier::AlphaNumeric(String::from("alpha1")), |
437 | | Identifier::Numeric(9), |
438 | | ]; |
439 | | assert_eq!(expected_pre, parsed.pre); |
440 | | |
441 | | let expected_build = vec![ |
442 | | Identifier::AlphaNumeric(String::from("build5")), |
443 | | Identifier::Numeric(7), |
444 | | Identifier::AlphaNumeric(String::from("3aedf")), |
445 | | ]; |
446 | | assert_eq!(expected_build, parsed.build); |
447 | | } |
448 | | |
449 | | #[test] |
450 | | fn parse_complex_metadata_02() { |
451 | | let version = "0.4.0-beta.1+0851523"; |
452 | | |
453 | | let parsed = version::parse(version).unwrap(); |
454 | | |
455 | | let expected_pre = vec![ |
456 | | Identifier::AlphaNumeric(String::from("beta")), |
457 | | Identifier::Numeric(1), |
458 | | ]; |
459 | | assert_eq!(expected_pre, parsed.pre); |
460 | | |
461 | | let expected_build = vec![Identifier::AlphaNumeric(String::from("0851523"))]; |
462 | | assert_eq!(expected_build, parsed.build); |
463 | | } |
464 | | |
465 | | #[test] |
466 | | fn parse_metadata_overflow() { |
467 | | let version = "0.4.0-beta.1+98765432109876543210"; |
468 | | |
469 | | let parsed = version::parse(version).unwrap(); |
470 | | |
471 | | let expected_pre = vec![ |
472 | | Identifier::AlphaNumeric(String::from("beta")), |
473 | | Identifier::Numeric(1), |
474 | | ]; |
475 | | assert_eq!(expected_pre, parsed.pre); |
476 | | |
477 | | let expected_build = vec![Identifier::AlphaNumeric(String::from( |
478 | | "98765432109876543210", |
479 | | ))]; |
480 | | assert_eq!(expected_build, parsed.build); |
481 | | } |
482 | | |
483 | | #[test] |
484 | | fn parse_regression_01() { |
485 | | let version = "0.0.0-WIP"; |
486 | | |
487 | | let parsed = version::parse(version).unwrap(); |
488 | | |
489 | | assert_eq!(0, parsed.major); |
490 | | assert_eq!(0, parsed.minor); |
491 | | assert_eq!(0, parsed.patch); |
492 | | |
493 | | let expected_pre = vec![Identifier::AlphaNumeric(String::from("WIP"))]; |
494 | | assert_eq!(expected_pre, parsed.pre); |
495 | | } |
496 | | |
497 | | #[test] |
498 | | fn parse_regression_02() { |
499 | | // this is used by really old versions of npm, and is valid according to semver.org |
500 | | let version = "1.2.3-beta-1"; |
501 | | |
502 | | let parsed = version::parse(version).unwrap(); |
503 | | |
504 | | assert_eq!(1, parsed.major); |
505 | | assert_eq!(2, parsed.minor); |
506 | | assert_eq!(3, parsed.patch); |
507 | | |
508 | | let expected_pre = vec![Identifier::AlphaNumeric(String::from("beta-1"))]; |
509 | | assert_eq!(expected_pre, parsed.pre); |
510 | | } |
511 | | } |