nodejs_semver/
lib.rs

1#![doc = include_str!("../README.md")]
2
3#[cfg(feature = "serde")]
4use serde::{de::Deserializer, ser::Serializer, Deserialize, Serialize};
5
6use std::cmp::{self, Ordering};
7use std::fmt;
8use std::num::ParseIntError;
9
10use miette::{Diagnostic, SourceSpan};
11use thiserror::Error;
12
13use winnow::ascii::{digit1, space0};
14use winnow::combinator::{alt, opt, preceded, separated};
15use winnow::error::{AddContext, ErrMode, ErrorKind, FromExternalError, ParserError};
16use winnow::stream::{AsChar, Stream};
17use winnow::token::{literal, take_while};
18use winnow::{PResult, Parser};
19
20pub use range::*;
21
22mod range;
23
24/// JavaScript's
25/// [MAX_SAFE_INTEGER](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER).
26/// This is used to determine the maximum value for integer components in a
27/// JS-compatible way.
28pub const MAX_SAFE_INTEGER: u64 = 900_719_925_474_099;
29
30/// Maximum length of a semver string.
31pub const MAX_LENGTH: usize = 256;
32
33/**
34Semver version or range parsing error wrapper.
35
36This wrapper is used to hold some parsing-related metadata, as well as
37a more specific [SemverErrorKind].
38*/
39#[derive(Debug, Clone, Error, Eq, PartialEq)]
40#[error("{kind}")]
41pub struct SemverError {
42    input: String,
43    span: SourceSpan,
44    kind: SemverErrorKind,
45}
46
47impl Diagnostic for SemverError {
48    fn code<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
49        self.kind().code()
50    }
51
52    fn severity(&self) -> Option<miette::Severity> {
53        self.kind().severity()
54    }
55
56    fn help<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
57        self.kind().help()
58    }
59
60    fn url<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
61        self.kind().url()
62    }
63
64    fn source_code(&self) -> Option<&dyn miette::SourceCode> {
65        Some(&self.input)
66    }
67
68    fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
69        Some(Box::new(std::iter::once(
70            miette::LabeledSpan::new_with_span(Some("here".into()), *self.span()),
71        )))
72    }
73}
74
75impl SemverError {
76    /// Returns the input that was given to the parser.
77    pub fn input(&self) -> &str {
78        &self.input
79    }
80
81    /// Returns the SourceSpan of the error.
82    pub fn span(&self) -> &SourceSpan {
83        &self.span
84    }
85
86    /// Returns the (0-based) byte offset where the parsing error happened.
87    pub fn offset(&self) -> usize {
88        self.span.offset()
89    }
90
91    /// Returns the more specific [SemverErrorKind] for this error.
92    ///
93    /// This value can also be fetched through [std::error::Error::source],
94    /// but that would require downcasting to match types.
95    pub fn kind(&self) -> &SemverErrorKind {
96        &self.kind
97    }
98
99    /// Returns the (0-indexed) line and column number where the parsing error
100    /// happened.
101    pub fn location(&self) -> (usize, usize) {
102        // Taken partially from winnow.
103        let prefix = &self.input.as_bytes()[..self.offset()];
104
105        // Count the number of newlines in the first `offset` bytes of input
106        let line_number = bytecount::count(prefix, b'\n');
107
108        // Find the line that includes the subslice:
109        // Find the *last* newline before the substring starts
110        let line_begin = prefix
111            .iter()
112            .rev()
113            .position(|&b| b == b'\n')
114            .map(|pos| self.offset() - pos)
115            .unwrap_or(0);
116
117        // Find the full line after that newline
118        let line = self.input[line_begin..]
119            .lines()
120            .next()
121            .unwrap_or(&self.input[line_begin..])
122            .trim_end();
123
124        // The (0-indexed) column number is the offset of our substring into that line
125        let column_number = self.input[self.offset()..].as_ptr() as usize - line.as_ptr() as usize;
126
127        (line_number, column_number)
128    }
129}
130
131/**
132The specific kind of error that occurred. Usually wrapped in a [SemverError].
133*/
134#[derive(Debug, Clone, Error, Eq, PartialEq, Diagnostic)]
135pub enum SemverErrorKind {
136    /**
137    Semver strings overall can't be longer than [MAX_LENGTH]. This is a
138    restriction coming from the JavaScript `nodejs-semver`.
139    */
140    #[error("Semver string can't be longer than {} characters.", MAX_LENGTH)]
141    #[diagnostic(code(nodejs_semver::too_long), url(docsrs))]
142    MaxLengthError,
143
144    /**
145    Input to `nodejs-semver` must be "complete". That is, a version must be
146    composed of major, minor, and patch segments, with optional prerelease
147    and build metadata. If you're looking for alternative syntaxes, like `1.2`,
148    that are meant for defining semver ranges, use [Range] instead.
149    */
150    #[error("Incomplete input to semver parser.")]
151    #[diagnostic(code(nodejs_semver::incomplete_input), url(docsrs))]
152    IncompleteInput,
153
154    /**
155    Components of a semver string (major, minor, patch, integer sections of
156    build and prerelease) must all be valid, parseable integers. This error
157    occurs when Rust's own integer parsing failed.
158    */
159    #[error("Failed to parse an integer component of a semver string: {0}")]
160    #[diagnostic(code(nodejs_semver::parse_int_error), url(docsrs))]
161    ParseIntError(ParseIntError),
162
163    /**
164    `nodejs-semver` inherits the JavaScript implementation's limitation on
165    limiting integer component sizes to [MAX_SAFE_INTEGER].
166    */
167    #[error("Integer component of semver string is larger than JavaScript's Number.MAX_SAFE_INTEGER: {0}")]
168    #[diagnostic(code(nodejs_semver::integer_too_large), url(docsrs))]
169    MaxIntError(u64),
170
171    /**
172    This is a generic error that a certain component of the semver string
173    failed to parse.
174    */
175    #[error("Failed to parse {0}.")]
176    #[diagnostic(code(nodejs_semver::parse_component_error), url(docsrs))]
177    Context(&'static str),
178
179    #[error("No valid ranges could be parsed")]
180    #[diagnostic(code(nodejs_semver::no_valid_ranges), url(docsrs), help("nodejs-semver parses in so-called 'loose' mode. This means that if you have a slightly incorrect semver operator (`>=1.y`, for ex.), it will get thrown away. This error only happens if _all_ your input ranges were invalid semver in this way."))]
181    NoValidRanges,
182
183    /**
184    This error is mostly nondescript. Feel free to file an issue if you run
185    into it.
186    */
187    #[error("An unspecified error occurred.")]
188    #[diagnostic(code(nodejs_semver::other), url(docsrs))]
189    Other,
190}
191
192#[derive(Debug)]
193struct SemverParseError<I> {
194    pub(crate) input: I,
195    pub(crate) context: Option<&'static str>,
196    pub(crate) kind: Option<SemverErrorKind>,
197}
198
199impl<I: Clone + Stream> ParserError<I> for SemverParseError<I> {
200    fn from_error_kind(input: &I, _kind: winnow::error::ErrorKind) -> Self {
201        Self {
202            input: input.clone(),
203            context: None,
204            kind: None,
205        }
206    }
207
208    fn append(
209        self,
210        input: &I,
211        _token_start: &<I as Stream>::Checkpoint,
212        _kind: winnow::error::ErrorKind,
213    ) -> Self {
214        Self {
215            input: input.clone(),
216            context: self.context,
217            kind: self.kind,
218        }
219    }
220}
221
222impl<I: Stream> AddContext<I> for SemverParseError<I> {
223    fn add_context(
224        self,
225        _input: &I,
226        _token_start: &<I as Stream>::Checkpoint,
227        ctx: &'static str,
228    ) -> Self {
229        Self {
230            input: self.input,
231            context: Some(ctx),
232            kind: self.kind,
233        }
234    }
235}
236
237impl<'a> FromExternalError<&'a str, SemverParseError<&'a str>> for SemverParseError<&'a str> {
238    fn from_external_error(
239        _input: &&'a str,
240        _kind: ErrorKind,
241        e: SemverParseError<&'a str>,
242    ) -> Self {
243        e
244    }
245}
246
247/**
248An Identifier type for build and prerelease metadata.
249*/
250#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
251pub enum Identifier {
252    /// An identifier that's solely numbers.
253    Numeric(u64),
254    /// An identifier with letters and numbers.
255    AlphaNumeric(String),
256}
257
258impl fmt::Display for Identifier {
259    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
260        match self {
261            Identifier::Numeric(n) => write!(f, "{}", n),
262            Identifier::AlphaNumeric(s) => write!(f, "{}", s),
263        }
264    }
265}
266
267/// difference between two versions by the release type
268#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
269pub enum VersionDiff {
270    Major,
271    Minor,
272    Patch,
273    PreMajor,
274    PreMinor,
275    PrePatch,
276    PreRelease,
277}
278
279/// difference between two versions
280pub type ReleaseType = VersionDiff;
281
282impl fmt::Display for VersionDiff {
283    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
284        match self {
285            VersionDiff::Major => write!(f, "major"),
286            VersionDiff::Minor => write!(f, "minor"),
287            VersionDiff::Patch => write!(f, "patch"),
288            VersionDiff::PreMajor => write!(f, "premajor"),
289            VersionDiff::PreMinor => write!(f, "preminor"),
290            VersionDiff::PrePatch => write!(f, "prepatch"),
291            VersionDiff::PreRelease => write!(f, "prerelease"),
292        }
293    }
294}
295
296/**
297A semantic version, conformant to the [semver spec](https://semver.org/spec/v2.0.0.html).
298*/
299#[derive(Clone, Debug)]
300pub struct Version {
301    pub major: u64,
302    pub minor: u64,
303    pub patch: u64,
304    pub build: Vec<Identifier>,
305    pub pre_release: Vec<Identifier>,
306}
307
308#[cfg(feature = "serde")]
309impl Serialize for Version {
310    fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
311        s.collect_str(self)
312    }
313}
314
315#[cfg(feature = "serde")]
316impl<'de> Deserialize<'de> for Version {
317    fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
318        let s = String::deserialize(d)?;
319        s.parse().map_err(serde::de::Error::custom)
320    }
321}
322
323impl Version {
324    /// True if this [Version] satisfies the given [Range].
325    pub fn satisfies(&self, range: &Range) -> bool {
326        range.satisfies(self)
327    }
328
329    /// True is this [Version] has a prerelease component.
330    pub fn is_prerelease(&self) -> bool {
331        !self.pre_release.is_empty()
332    }
333
334    /// Parse a semver string into a [Version].
335    ///
336    /// ```rust
337    #[doc = include_str!("../examples/parse.rs")]
338    /// ```
339    pub fn parse<S: AsRef<str>>(input: S) -> Result<Version, SemverError> {
340        let mut input = input.as_ref();
341
342        if input.len() > MAX_LENGTH {
343            return Err(SemverError {
344                input: input.into(),
345                span: (input.len() - 1, 0).into(),
346                kind: SemverErrorKind::MaxLengthError,
347            });
348        }
349
350        match version.parse_next(&mut input) {
351            Ok(arg) => Ok(arg),
352            Err(err) => Err(match err {
353                ErrMode::Backtrack(e) | ErrMode::Cut(e) => SemverError {
354                    input: input.into(),
355                    span: (e.input.as_ptr() as usize - input.as_ptr() as usize, 0).into(),
356                    kind: if let Some(kind) = e.kind {
357                        kind
358                    } else if let Some(ctx) = e.context {
359                        SemverErrorKind::Context(ctx)
360                    } else {
361                        SemverErrorKind::Other
362                    },
363                },
364                ErrMode::Incomplete(_) => SemverError {
365                    input: input.into(),
366                    span: (input.len() - 1, 0).into(),
367                    kind: SemverErrorKind::IncompleteInput,
368                },
369            }),
370        }
371    }
372
373    /// difference between two [Version]s by the release type,
374    /// or `None` if the [Version]s are the same.
375    ///
376    /// ```rust
377    #[doc = include_str!("../examples/diff.rs")]
378    /// ```
379    pub fn diff(&self, other: &Self) -> Option<VersionDiff> {
380        let cmp_result = self.cmp(other);
381
382        if cmp_result == Ordering::Equal {
383            return None;
384        }
385
386        let self_higher = cmp_result == Ordering::Greater;
387        let high_version = if self_higher { self } else { other };
388        let low_version = if self_higher { other } else { self };
389        let high_has_pre = high_version.is_prerelease();
390        let low_has_pre = low_version.is_prerelease();
391
392        if low_has_pre && !high_has_pre {
393            // Going from prerelease -> no prerelease requires some special casing
394
395            // If the low version has only a major, then it will always be a major
396            // Some examples:
397            // 1.0.0-1 -> 1.0.0
398            // 1.0.0-1 -> 1.1.1
399            // 1.0.0-1 -> 2.0.0
400            if low_version.patch == 0 && low_version.minor == 0 {
401                return Some(VersionDiff::Major);
402            }
403
404            // Otherwise it can be determined by checking the high version
405            if high_version.patch != 0 {
406                // anything higher than a patch bump would result in the wrong version
407                return Some(VersionDiff::Patch);
408            }
409
410            if high_version.minor != 0 {
411                // anything higher than a minor bump would result in the wrong version
412                return Some(VersionDiff::Minor);
413            }
414
415            // bumping major/minor/patch all have same result
416            return Some(VersionDiff::Major);
417        }
418
419        if self.major != other.major {
420            if high_has_pre {
421                return Some(VersionDiff::PreMajor);
422            }
423
424            return Some(VersionDiff::Major);
425        }
426
427        if self.minor != other.minor {
428            if high_has_pre {
429                return Some(VersionDiff::PreMinor);
430            }
431
432            return Some(VersionDiff::Minor);
433        }
434
435        if self.patch != other.patch {
436            if high_has_pre {
437                return Some(VersionDiff::PrePatch);
438            }
439
440            return Some(VersionDiff::Patch);
441        }
442
443        // high and low are preleases
444        Some(VersionDiff::PreRelease)
445    }
446}
447
448impl PartialEq for Version {
449    fn eq(&self, other: &Self) -> bool {
450        self.major == other.major
451            && self.minor == other.minor
452            && self.patch == other.patch
453            && self.pre_release == other.pre_release
454    }
455}
456
457impl Eq for Version {}
458
459impl std::hash::Hash for Version {
460    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
461        self.major.hash(state);
462        self.minor.hash(state);
463        self.patch.hash(state);
464        self.pre_release.hash(state);
465    }
466}
467
468impl fmt::Display for Version {
469    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
470        write!(f, "{}.{}.{}", self.major, self.minor, self.patch)?;
471
472        for (i, ident) in self.pre_release.iter().enumerate() {
473            if i == 0 {
474                write!(f, "-")?;
475            } else {
476                write!(f, ".")?;
477            }
478            write!(f, "{}", ident)?;
479        }
480
481        for (i, ident) in self.build.iter().enumerate() {
482            if i == 0 {
483                write!(f, "+")?;
484            } else {
485                write!(f, ".")?;
486            }
487            write!(f, "{}", ident)?;
488        }
489
490        Ok(())
491    }
492}
493
494macro_rules! impl_from_unsigned_for_version {
495    ($($t:ident),+) => {
496        $(
497            impl ::std::convert::From<($t, $t, $t)> for Version {
498                fn from((major, minor, patch): ($t, $t, $t)) -> Self {
499                    Version {
500                        major: major as u64,
501                        minor: minor as u64,
502                        patch: patch as u64,
503                        build: Vec::new(),
504                        pre_release: Vec::new(),
505                    }
506                }
507            }
508
509            impl ::std::convert::From<($t, $t, $t, $t)> for Version {
510                fn from((major, minor, patch, pre_release): ($t, $t, $t, $t)) -> Self {
511                    Version {
512                        major: major as u64,
513                        minor: minor as u64,
514                        patch: patch as u64,
515                        build: Vec::new(),
516                        pre_release: vec![Identifier::Numeric(pre_release as u64)],
517                    }
518                }
519            }
520        )+
521    }
522}
523
524macro_rules! impl_from_signed_for_version {
525    ($($t:ident),+) => {
526        $(
527            impl ::std::convert::From<($t, $t, $t)> for Version {
528                fn from((major, minor, patch): ($t, $t, $t)) -> Self {
529                    debug_assert!(major >= 0, "Version major must be non-negative, got {}", major);
530                    debug_assert!(minor >= 0, "Version minor must be non-negative, got {}", minor);
531                    debug_assert!(patch >= 0, "Version patch must be non-negative, got {}", patch);
532
533                    Version {
534                        major: major as u64,
535                        minor: minor as u64,
536                        patch: patch as u64,
537                        build: Vec::new(),
538                        pre_release: Vec::new(),
539                    }
540                }
541            }
542
543            impl ::std::convert::From<($t, $t, $t, $t)> for Version {
544                fn from((major, minor, patch, pre_release): ($t, $t, $t, $t)) -> Self {
545                    debug_assert!(major >= 0, "Version major must be non-negative, got {}", major);
546                    debug_assert!(minor >= 0, "Version minor must be non-negative, got {}", minor);
547                    debug_assert!(patch >= 0, "Version patch must be non-negative, got {}", patch);
548                    debug_assert!(pre_release >= 0, "Version pre-release must be non-negative, got {}", pre_release);
549
550                    Version {
551                        major: major as u64,
552                        minor: minor as u64,
553                        patch: patch as u64,
554                        build: Vec::new(),
555                        pre_release: vec![Identifier::Numeric(pre_release as u64)],
556                    }
557                }
558            }
559        )+
560    }
561}
562
563impl_from_unsigned_for_version!(u8, u16, u32, u64, usize);
564impl_from_signed_for_version!(i8, i16, i32, i64, isize);
565
566impl std::str::FromStr for Version {
567    type Err = SemverError;
568    fn from_str(s: &str) -> Result<Self, Self::Err> {
569        Version::parse(s)
570    }
571}
572
573impl cmp::PartialOrd for Version {
574    fn partial_cmp(&self, other: &Version) -> Option<Ordering> {
575        Some(self.cmp(other))
576    }
577}
578
579impl cmp::Ord for Version {
580    fn cmp(&self, other: &Version) -> cmp::Ordering {
581        match self.major.cmp(&other.major) {
582            Ordering::Equal => {}
583            //if difference in major version, just return result
584            order_result => return order_result,
585        }
586
587        match self.minor.cmp(&other.minor) {
588            Ordering::Equal => {}
589            //if difference in minor version, just return result
590            order_result => return order_result,
591        }
592
593        match self.patch.cmp(&other.patch) {
594            Ordering::Equal => {}
595            //if difference in patch version, just return result
596            order_result => return order_result,
597        }
598
599        match (self.pre_release.len(), other.pre_release.len()) {
600            //if no pre_release string, they're equal
601            (0, 0) => Ordering::Equal,
602            //if other has a pre-release string, but this doesn't, this one is greater
603            (0, _) => Ordering::Greater,
604            //if this one has a pre-release string, but other doesn't this one is less than
605            (_, 0) => Ordering::Less,
606            // if both have pre_release strings, compare the strings and return the result
607            (_, _) => self.pre_release.cmp(&other.pre_release),
608        }
609    }
610}
611
612enum Extras {
613    Build(Vec<Identifier>),
614    Release(Vec<Identifier>),
615    ReleaseAndBuild((Vec<Identifier>, Vec<Identifier>)),
616}
617
618impl Extras {
619    fn values(self) -> (Vec<Identifier>, Vec<Identifier>) {
620        use Extras::*;
621        match self {
622            Release(ident) => (ident, Vec::new()),
623            Build(ident) => (Vec::new(), ident),
624            ReleaseAndBuild(ident) => ident,
625        }
626    }
627}
628
629/// <valid semver> ::= <version core>
630///                 | <version core> "-" <pre-release>
631///                 | <version core> "+" <build>
632///                 | <version core> "-" <pre-release> "+" <build>
633fn version<'s>(input: &mut &'s str) -> PResult<Version, SemverParseError<&'s str>> {
634    (
635        opt(alt((literal("v"), literal("V")))),
636        space0,
637        version_core,
638        extras,
639    )
640        .map(
641            |(_, _, (major, minor, patch), (pre_release, build))| Version {
642                major,
643                minor,
644                patch,
645                pre_release,
646                build,
647            },
648        )
649        .context("version")
650        .parse_next(input)
651}
652
653fn extras<'s>(
654    input: &mut &'s str,
655) -> PResult<(Vec<Identifier>, Vec<Identifier>), SemverParseError<&'s str>> {
656    Parser::map(
657        opt(alt((
658            Parser::map((pre_release, build), Extras::ReleaseAndBuild),
659            Parser::map(pre_release, Extras::Release),
660            Parser::map(build, Extras::Build),
661        ))),
662        |extras| match extras {
663            Some(extras) => extras.values(),
664            _ => Default::default(),
665        },
666    )
667    .parse_next(input)
668}
669
670/// <version core> ::= <major> "." <minor> "." <patch>
671fn version_core<'s>(input: &mut &'s str) -> PResult<(u64, u64, u64), SemverParseError<&'s str>> {
672    (number, literal("."), number, literal("."), number)
673        .map(|(major, _, minor, _, patch)| (major, minor, patch))
674        .context("version core")
675        .parse_next(input)
676}
677
678// I believe build, pre_release, and identifier are not 100% spec compliant.
679fn build<'s>(input: &mut &'s str) -> PResult<Vec<Identifier>, SemverParseError<&'s str>> {
680    preceded(literal("+"), separated(1.., identifier, literal(".")))
681        .context("build version")
682        .parse_next(input)
683}
684
685fn pre_release<'s>(input: &mut &'s str) -> PResult<Vec<Identifier>, SemverParseError<&'s str>> {
686    preceded(opt(literal("-")), separated(1.., identifier, literal(".")))
687        .context("pre_release version")
688        .parse_next(input)
689}
690
691fn identifier<'s>(input: &mut &'s str) -> PResult<Identifier, SemverParseError<&'s str>> {
692    Parser::map(
693        take_while(1.., |x: char| AsChar::is_alphanum(x as u8) || x == '-'),
694        |s: &str| {
695            str::parse::<u64>(s)
696                .map(Identifier::Numeric)
697                .unwrap_or_else(|_err| Identifier::AlphaNumeric(s.to_string()))
698        },
699    )
700    .context("identifier")
701    .parse_next(input)
702}
703
704pub(crate) fn number<'s>(input: &mut &'s str) -> PResult<u64, SemverParseError<&'s str>> {
705    #[allow(suspicious_double_ref_op)]
706    let copied = input.clone();
707
708    Parser::try_map(Parser::take(digit1), |raw| {
709        let value = str::parse(raw).map_err(|e| SemverParseError {
710            input: copied,
711            context: None,
712            kind: Some(SemverErrorKind::ParseIntError(e)),
713        })?;
714
715        if value > MAX_SAFE_INTEGER {
716            return Err(SemverParseError {
717                input: copied,
718                context: None,
719                kind: Some(SemverErrorKind::MaxIntError(value)),
720            });
721        }
722
723        Ok(value)
724    })
725    .context("number component")
726    .parse_next(input)
727}
728
729#[cfg(test)]
730mod tests {
731    use super::Identifier::*;
732    use super::*;
733
734    use pretty_assertions::assert_eq;
735
736    #[test]
737    fn trivial_version_number() {
738        let v = Version::parse("1.2.34").unwrap();
739
740        assert_eq!(
741            v,
742            Version {
743                major: 1,
744                minor: 2,
745                patch: 34,
746                build: Vec::with_capacity(2),
747                pre_release: Vec::with_capacity(2),
748            }
749        );
750    }
751
752    #[test]
753    fn version_with_build() {
754        let v = Version::parse("1.2.34+123.456").unwrap();
755
756        assert_eq!(
757            v,
758            Version {
759                major: 1,
760                minor: 2,
761                patch: 34,
762                build: vec![Numeric(123), Numeric(456)],
763                pre_release: Vec::with_capacity(2),
764            }
765        );
766    }
767
768    #[test]
769    fn version_with_pre_release() {
770        let v = Version::parse("1.2.34-abc.123").unwrap();
771
772        assert_eq!(
773            v,
774            Version {
775                major: 1,
776                minor: 2,
777                patch: 34,
778                pre_release: vec![AlphaNumeric("abc".into()), Numeric(123)],
779                build: Vec::with_capacity(2),
780            }
781        );
782    }
783
784    #[test]
785    fn version_with_pre_release_and_build() {
786        let v = Version::parse("1.2.34-abc.123+1").unwrap();
787
788        assert_eq!(
789            v,
790            Version {
791                major: 1,
792                minor: 2,
793                patch: 34,
794                pre_release: vec![AlphaNumeric("abc".into()), Numeric(123)],
795                build: vec![Numeric(1),]
796            }
797        );
798    }
799
800    #[test]
801    fn pre_release_that_could_look_numeric_at_first() {
802        let v = Version::parse("1.0.0-rc.2-migration").unwrap();
803
804        assert_eq!(
805            v,
806            Version {
807                major: 1,
808                minor: 0,
809                patch: 0,
810                pre_release: vec![
811                    Identifier::AlphaNumeric("rc".into()),
812                    Identifier::AlphaNumeric("2-migration".into())
813                ],
814                build: vec![],
815            }
816        );
817    }
818
819    #[test]
820    fn comparison_with_different_major_version() {
821        let lesser_version = Version {
822            major: 1,
823            minor: 2,
824            patch: 34,
825            pre_release: vec![AlphaNumeric("abc".into()), Numeric(123)],
826            build: vec![],
827        };
828        let greater_version = Version {
829            major: 2,
830            minor: 2,
831            patch: 34,
832            pre_release: vec![AlphaNumeric("abc".into()), Numeric(123)],
833            build: vec![],
834        };
835        assert_eq!(lesser_version.cmp(&greater_version), Ordering::Less);
836        assert_eq!(greater_version.cmp(&lesser_version), Ordering::Greater);
837    }
838    #[test]
839    fn comparison_with_different_minor_version() {
840        let lesser_version = Version {
841            major: 1,
842            minor: 2,
843            patch: 34,
844            pre_release: vec![AlphaNumeric("abc".into()), Numeric(123)],
845            build: vec![],
846        };
847        let greater_version = Version {
848            major: 1,
849            minor: 3,
850            patch: 34,
851            pre_release: vec![AlphaNumeric("abc".into()), Numeric(123)],
852            build: vec![],
853        };
854        assert_eq!(lesser_version.cmp(&greater_version), Ordering::Less);
855        assert_eq!(greater_version.cmp(&lesser_version), Ordering::Greater);
856    }
857
858    #[test]
859    fn comparison_with_different_patch_version() {
860        let lesser_version = Version {
861            major: 1,
862            minor: 2,
863            patch: 34,
864            pre_release: vec![AlphaNumeric("abc".into()), Numeric(123)],
865            build: vec![],
866        };
867        let greater_version = Version {
868            major: 1,
869            minor: 2,
870            patch: 56,
871            pre_release: vec![AlphaNumeric("abc".into()), Numeric(123)],
872            build: vec![],
873        };
874        assert_eq!(lesser_version.cmp(&greater_version), Ordering::Less);
875        assert_eq!(greater_version.cmp(&lesser_version), Ordering::Greater);
876    }
877
878    #[test]
879    //confirms the comparison matches the pre-release comparison example in the SemVer spec.
880    //ie checks that 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0.
881    //for simplicity just checks them in order. Assumes that the transitive property holds. So if a < b & b < c then a < c.
882    fn comparison_with_different_pre_release_version() {
883        let v1_alpha = Version {
884            major: 1,
885            minor: 0,
886            patch: 0,
887            pre_release: vec![AlphaNumeric("alpha".into())],
888            build: vec![],
889        };
890        let v1_alpha1 = Version {
891            major: 1,
892            minor: 0,
893            patch: 0,
894            pre_release: vec![AlphaNumeric("alpha".into()), Numeric(1)],
895            build: vec![],
896        };
897        assert_eq!(v1_alpha.cmp(&v1_alpha1), Ordering::Less);
898        let v1_alpha_beta = Version {
899            major: 1,
900            minor: 0,
901            patch: 0,
902            pre_release: vec![AlphaNumeric("alpha".into()), AlphaNumeric("beta".into())],
903            build: vec![],
904        };
905        assert_eq!(v1_alpha1.cmp(&v1_alpha_beta), Ordering::Less);
906        let v1_beta = Version {
907            major: 1,
908            minor: 0,
909            patch: 0,
910            pre_release: vec![AlphaNumeric("beta".into())],
911            build: vec![],
912        };
913        assert_eq!(v1_alpha_beta.cmp(&v1_beta), Ordering::Less);
914        let v1_beta2 = Version {
915            major: 1,
916            minor: 0,
917            patch: 0,
918            pre_release: vec![AlphaNumeric("beta".into()), Numeric(2)],
919            build: vec![],
920        };
921        assert_eq!(v1_beta.cmp(&v1_beta2), Ordering::Less);
922        let v1_beta11 = Version {
923            major: 1,
924            minor: 0,
925            patch: 0,
926            pre_release: vec![AlphaNumeric("beta".into()), Numeric(11)],
927            build: vec![],
928        };
929        assert_eq!(v1_beta2.cmp(&v1_beta11), Ordering::Less);
930        let v1_rc1 = Version {
931            major: 1,
932            minor: 0,
933            patch: 0,
934            pre_release: vec![AlphaNumeric("rc".into()), Numeric(1)],
935            build: vec![],
936        };
937        assert_eq!(v1_beta11.cmp(&v1_rc1), Ordering::Less);
938        let v1 = Version {
939            major: 1,
940            minor: 0,
941            patch: 0,
942            pre_release: vec![],
943            build: vec![],
944        };
945        assert_eq!(v1_rc1.cmp(&v1), Ordering::Less);
946    }
947
948    #[test]
949    fn individual_version_component_has_an_upper_bound() {
950        let out_of_range = MAX_SAFE_INTEGER + 1;
951        let v = Version::parse(format!("1.2.{}", out_of_range));
952        assert_eq!(v.expect_err("Parse should have failed.").to_string(), "Integer component of semver string is larger than JavaScript's Number.MAX_SAFE_INTEGER: 900719925474100");
953    }
954
955    #[test]
956    fn version_string_limited_to_256_characters() {
957        let prebuild = (0..257).map(|_| "X").collect::<Vec<_>>().join("");
958        let version_string = format!("1.1.1-{}", prebuild);
959        let v = Version::parse(version_string.clone());
960
961        assert_eq!(
962            v.expect_err("Parse should have failed").to_string(),
963            "Semver string can't be longer than 256 characters."
964        );
965
966        let ok_version = version_string[0..255].to_string();
967        let v = Version::parse(ok_version);
968        assert!(v.is_ok());
969    }
970
971    #[test]
972    fn version_prefixed_with_v() {
973        // TODO: This is part of strict parsing for nodejs-semver!
974        let v = Version::parse("v1.2.3").unwrap();
975        assert_eq!(
976            v,
977            Version {
978                major: 1,
979                minor: 2,
980                patch: 3,
981                pre_release: vec![],
982                build: vec![],
983            }
984        );
985    }
986
987    #[test]
988    fn version_prefixed_with_v_space() {
989        // TODO: Loose parsing supports this, so
990        let v = Version::parse("v 1.2.3").unwrap();
991        assert_eq!(
992            v,
993            Version {
994                major: 1,
995                minor: 2,
996                patch: 3,
997                pre_release: vec![],
998                build: vec![],
999            }
1000        );
1001    }
1002
1003    fn asset_version_diff(left: &str, right: &str, expected: &str) {
1004        let left = Version::parse(left).unwrap();
1005        let right = Version::parse(right).unwrap();
1006        let expected_diff = match expected {
1007            "major" => Some(VersionDiff::Major),
1008            "minor" => Some(VersionDiff::Minor),
1009            "patch" => Some(VersionDiff::Patch),
1010            "premajor" => Some(VersionDiff::PreMajor),
1011            "preminor" => Some(VersionDiff::PreMinor),
1012            "prepatch" => Some(VersionDiff::PrePatch),
1013            "null" => None,
1014            _ => unreachable!("unexpected version diff"),
1015        };
1016
1017        assert_eq!(
1018            left.diff(&right),
1019            expected_diff,
1020            "left: {}, right: {}",
1021            left,
1022            right
1023        );
1024    }
1025
1026    #[test]
1027    fn version_diffs() {
1028        let cases = vec![
1029            ("1.2.3", "0.2.3", "major"),
1030            ("0.2.3", "1.2.3", "major"),
1031            ("1.4.5", "0.2.3", "major"),
1032            ("1.2.3", "2.0.0-pre", "premajor"),
1033            ("2.0.0-pre", "1.2.3", "premajor"),
1034            ("1.2.3", "1.3.3", "minor"),
1035            ("1.0.1", "1.1.0-pre", "preminor"),
1036            ("1.2.3", "1.2.4", "patch"),
1037            ("1.2.3", "1.2.4-pre", "prepatch"),
1038            ("1.0.0", "1.0.0", "null"),
1039            ("1.0.0-1", "1.0.0-1", "null"),
1040            ("0.0.2-1", "0.0.2", "patch"),
1041            ("0.0.2-1", "0.0.3", "patch"),
1042            ("0.0.2-1", "0.1.0", "minor"),
1043            ("0.0.2-1", "1.0.0", "major"),
1044            ("0.1.0-1", "0.1.0", "minor"),
1045            ("1.0.0-1", "2.0.0-1", "premajor"),
1046            ("1.0.0-1", "1.1.0-1", "preminor"),
1047            ("1.0.0-1", "1.0.1-1", "prepatch"),
1048        ];
1049
1050        for case in cases {
1051            asset_version_diff(case.0, case.1, case.2);
1052        }
1053    }
1054}
1055
1056#[cfg(feature = "serde")]
1057#[cfg(test)]
1058mod serde_tests {
1059    use super::Identifier::*;
1060    use super::*;
1061
1062    #[test]
1063    fn version_serde() {
1064        let v = Version {
1065            major: 1,
1066            minor: 2,
1067            patch: 3,
1068            pre_release: vec![AlphaNumeric("abc".into()), Numeric(123)],
1069            build: vec![AlphaNumeric("build".into())],
1070        };
1071
1072        let serialized = serde_json::to_string(&v).unwrap();
1073        let deserialized: Version = serde_json::from_str(&serialized).unwrap();
1074
1075        assert_eq!(v, deserialized);
1076    }
1077}