semver_parser/
version.rs

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
33use crate::parser::{self, Parser};
34use 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)]
61pub 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)]
104pub enum Identifier {
105    /// An identifier that's solely numbers.
106    Numeric(u64),
107    /// An identifier with letters and numbers.
108    AlphaNumeric(String),
109}
110
111impl Identifier {
112    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
144pub 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
155impl fmt::Display for Version {
156    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
170impl fmt::Display for Identifier {
171    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)]
180mod 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}