Coverage Report

Created: 2025-08-26 06:21

/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
}