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 {
concat(self, add_str: &str) -> Identifier112     pub fn concat(self, add_str: &str) -> Identifier {
113         match self {
114             Identifier::Numeric(n) => Identifier::AlphaNumeric(format!("{}{}", n, add_str)),
115             Identifier::AlphaNumeric(s) => Identifier::AlphaNumeric(format!("{}{}", s, add_str)),
116         }
117     }
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
parse(input: &str) -> Result<Version, parser::Error>144 pub fn parse(input: &str) -> Result<Version, parser::Error> {
145     let mut parser = Parser::new(input)?;
146     let version = parser.version()?;
147 
148     if !parser.is_eof() {
149         return Err(parser::Error::MoreInput(parser.tail()?));
150     }
151 
152     Ok(version)
153 }
154 
155 impl fmt::Display for Version {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result156     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
157         write!(f, "{}.{}.{}", self.major, self.minor, self.patch).expect("write failed");
158         if !self.pre.is_empty() {
159             let strs: Vec<_> = self.pre.iter().map(ToString::to_string).collect();
160             write!(f, "-{}", strs.join(".")).expect("write failed");
161         }
162         if !self.build.is_empty() {
163             let strs: Vec<_> = self.build.iter().map(ToString::to_string).collect();
164             write!(f, "+{}", strs.join(".")).expect("write failed");
165         }
166         Ok(())
167     }
168 }
169 
170 impl fmt::Display for Identifier {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result171     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
172         match *self {
173             Identifier::Numeric(ref id) => id.fmt(f),
174             Identifier::AlphaNumeric(ref id) => id.fmt(f),
175         }
176     }
177 }
178 
179 #[cfg(test)]
180 mod tests {
181     use super::*;
182     use crate::version;
183 
184     #[test]
parse_empty()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]
parse_blank()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]
parse_no_minor_patch()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             format!("'{}' incorrectly considered a valid parse", version)
217         );
218     }
219 
220     #[test]
parse_no_patch()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             format!("'{}' incorrectly considered a valid parse", version)
229         );
230     }
231 
232     #[test]
parse_empty_pre()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             format!("'{}' incorrectly considered a valid parse", version)
241         );
242     }
243 
244     #[test]
parse_letters()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             format!("'{}' incorrectly considered a valid parse", version)
253         );
254     }
255 
256     #[test]
parse_with_letters()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             format!("'{}' incorrectly considered a valid parse", version)
265         );
266     }
267 
268     #[test]
parse_basic_version()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]
parse_trims_input()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]
parse_no_major_leading_zeroes()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]
parse_no_minor_leading_zeroes()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]
parse_no_patch_leading_zeroes()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]
parse_no_major_overflow()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]
parse_no_minor_overflow()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]
parse_no_patch_overflow()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]
parse_basic_prerelease()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]
parse_prerelease_alphanumeric()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]
parse_prerelease_zero()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]
parse_basic_build()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]
parse_build_alphanumeric()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]
parse_pre_and_build()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]
parse_complex_metadata_01()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]
parse_complex_metadata_02()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]
parse_metadata_overflow()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]
parse_regression_01()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]
parse_regression_02()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 }
512