nodejs_semver/
lib.rs

1#![doc = include_str!("../README.md")]
2#![allow(clippy::needless_doctest_main)]
3
4#[cfg(feature = "serde")]
5use serde::{de::Deserializer, ser::Serializer, Deserialize, Serialize};
6
7use std::cmp::{self, Ordering};
8use std::fmt;
9use std::num::ParseIntError;
10
11use miette::{Diagnostic, SourceSpan};
12use thiserror::Error;
13
14use winnow::ascii::{digit1, space0};
15use winnow::combinator::{alt, opt, preceded, separated};
16use winnow::error::{AddContext, ErrMode, FromExternalError, ParserError};
17use winnow::stream::{AsChar, Stream};
18use winnow::token::{literal, take_while};
19use winnow::{ModalResult, Parser};
20
21pub use range::*;
22
23mod range;
24
25/// JavaScript's
26/// [MAX_SAFE_INTEGER](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER).
27/// This is used to determine the maximum value for integer components in a
28/// JS-compatible way.
29pub const MAX_SAFE_INTEGER: u64 = 900_719_925_474_099;
30
31/// Maximum length of a semver string.
32pub const MAX_LENGTH: usize = 256;
33
34/**
35Semver version or range parsing error wrapper.
36
37This wrapper is used to hold some parsing-related metadata, as well as
38a more specific [SemverErrorKind].
39*/
40#[derive(Debug, Clone, Error, Eq, PartialEq)]
41#[error("{kind}")]
42pub struct SemverError {
43    input: String,
44    span: SourceSpan,
45    kind: SemverErrorKind,
46}
47
48impl Diagnostic for SemverError {
49    fn code<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
50        self.kind().code()
51    }
52
53    fn severity(&self) -> Option<miette::Severity> {
54        self.kind().severity()
55    }
56
57    fn help<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
58        self.kind().help()
59    }
60
61    fn url<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
62        self.kind().url()
63    }
64
65    fn source_code(&self) -> Option<&dyn miette::SourceCode> {
66        Some(&self.input)
67    }
68
69    fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
70        Some(Box::new(std::iter::once(
71            miette::LabeledSpan::new_with_span(Some("here".into()), *self.span()),
72        )))
73    }
74}
75
76impl SemverError {
77    /// Returns the input that was given to the parser.
78    pub fn input(&self) -> &str {
79        &self.input
80    }
81
82    /// Returns the SourceSpan of the error.
83    pub fn span(&self) -> &SourceSpan {
84        &self.span
85    }
86
87    /// Returns the (0-based) byte offset where the parsing error happened.
88    pub fn offset(&self) -> usize {
89        self.span.offset()
90    }
91
92    /// Returns the more specific [SemverErrorKind] for this error.
93    ///
94    /// This value can also be fetched through [std::error::Error::source],
95    /// but that would require downcasting to match types.
96    pub fn kind(&self) -> &SemverErrorKind {
97        &self.kind
98    }
99
100    /// Returns the (0-indexed) line and column number where the parsing error
101    /// happened.
102    pub fn location(&self) -> (usize, usize) {
103        // Taken partially from winnow.
104        let prefix = &self.input.as_bytes()[..self.offset()];
105
106        // Count the number of newlines in the first `offset` bytes of input
107        let line_number = bytecount::count(prefix, b'\n');
108
109        // Find the line that includes the subslice:
110        // Find the *last* newline before the substring starts
111        let line_begin = prefix
112            .iter()
113            .rev()
114            .position(|&b| b == b'\n')
115            .map(|pos| self.offset() - pos)
116            .unwrap_or(0);
117
118        // Find the full line after that newline
119        let line = self.input[line_begin..]
120            .lines()
121            .next()
122            .unwrap_or(&self.input[line_begin..])
123            .trim_end();
124
125        // The (0-indexed) column number is the offset of our substring into that line
126        let column_number = self.input[self.offset()..].as_ptr() as usize - line.as_ptr() as usize;
127
128        (line_number, column_number)
129    }
130}
131
132/**
133The specific kind of error that occurred. Usually wrapped in a [SemverError].
134*/
135#[derive(Debug, Clone, Error, Eq, PartialEq, Diagnostic)]
136pub enum SemverErrorKind {
137    /**
138    Semver strings overall can't be longer than [MAX_LENGTH]. This is a
139    restriction coming from the JavaScript `nodejs-semver`.
140    */
141    #[error("Semver string can't be longer than {} characters.", MAX_LENGTH)]
142    #[diagnostic(code(nodejs_semver::too_long), url(docsrs))]
143    MaxLengthError,
144
145    /**
146    Input to `nodejs-semver` must be "complete". That is, a version must be
147    composed of major, minor, and patch segments, with optional prerelease
148    and build metadata. If you're looking for alternative syntaxes, like `1.2`,
149    that are meant for defining semver ranges, use [Range] instead.
150    */
151    #[error("Incomplete input to semver parser.")]
152    #[diagnostic(code(nodejs_semver::incomplete_input), url(docsrs))]
153    IncompleteInput,
154
155    /**
156    Components of a semver string (major, minor, patch, integer sections of
157    build and prerelease) must all be valid, parseable integers. This error
158    occurs when Rust's own integer parsing failed.
159    */
160    #[error("Failed to parse an integer component of a semver string: {0}")]
161    #[diagnostic(code(nodejs_semver::parse_int_error), url(docsrs))]
162    ParseIntError(ParseIntError),
163
164    /**
165    `nodejs-semver` inherits the JavaScript implementation's limitation on
166    limiting integer component sizes to [MAX_SAFE_INTEGER].
167    */
168    #[error("Integer component of semver string is larger than JavaScript's Number.MAX_SAFE_INTEGER: {0}")]
169    #[diagnostic(code(nodejs_semver::integer_too_large), url(docsrs))]
170    MaxIntError(u64),
171
172    /**
173    This is a generic error that a certain component of the semver string
174    failed to parse.
175    */
176    #[error("Failed to parse {0}.")]
177    #[diagnostic(code(nodejs_semver::parse_component_error), url(docsrs))]
178    Context(&'static str),
179
180    #[error("No valid ranges could be parsed")]
181    #[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."))]
182    NoValidRanges,
183
184    /**
185    This error is mostly nondescript. Feel free to file an issue if you run
186    into it.
187    */
188    #[error("An unspecified error occurred.")]
189    #[diagnostic(code(nodejs_semver::other), url(docsrs))]
190    Other,
191}
192
193#[derive(Debug)]
194struct SemverParseError<I> {
195    pub(crate) input: I,
196    pub(crate) context: Option<&'static str>,
197    pub(crate) kind: Option<SemverErrorKind>,
198}
199
200impl<I: Clone + Stream> ParserError<I> for SemverParseError<I> {
201    type Inner = Self;
202
203    fn from_input(input: &I) -> Self {
204        Self {
205            input: input.clone(),
206            context: None,
207            kind: None,
208        }
209    }
210
211    fn append(self, input: &I, _token_start: &<I as Stream>::Checkpoint) -> Self {
212        Self {
213            input: input.clone(),
214            context: self.context,
215            kind: self.kind,
216        }
217    }
218
219    fn into_inner(self) -> Result<Self::Inner, Self> {
220        Ok(self)
221    }
222}
223
224impl<I: Stream> AddContext<I> for SemverParseError<I> {
225    fn add_context(
226        self,
227        _input: &I,
228        _token_start: &<I as Stream>::Checkpoint,
229        ctx: &'static str,
230    ) -> Self {
231        Self {
232            input: self.input,
233            context: Some(ctx),
234            kind: self.kind,
235        }
236    }
237}
238
239impl<'a> FromExternalError<&'a str, SemverParseError<&'a str>> for SemverParseError<&'a str> {
240    fn from_external_error(_input: &&'a str, e: SemverParseError<&'a str>) -> Self {
241        e
242    }
243}
244
245/**
246An Identifier type for build and prerelease metadata.
247*/
248#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
249pub enum Identifier {
250    /// An identifier that's solely numbers.
251    Numeric(u64),
252    /// An identifier with letters and numbers.
253    AlphaNumeric(String),
254}
255
256impl fmt::Display for Identifier {
257    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
258        match self {
259            Identifier::Numeric(n) => write!(f, "{}", n),
260            Identifier::AlphaNumeric(s) => write!(f, "{}", s),
261        }
262    }
263}
264
265/// difference between two versions by the release type
266#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
267pub enum VersionDiff {
268    Major,
269    Minor,
270    Patch,
271    PreMajor,
272    PreMinor,
273    PrePatch,
274    PreRelease,
275}
276
277/// difference between two versions
278pub type ReleaseType = VersionDiff;
279
280#[derive(Debug, Clone, Copy, PartialEq, Eq)]
281pub enum IdentifierBase {
282    /// Mirrors `identifierBase === false` in node-semver.
283    False,
284    /// Any other truthy/falsy value. Only the zero/non-zero nature matters.
285    Value(u64),
286}
287
288impl Default for IdentifierBase {
289    fn default() -> Self {
290        IdentifierBase::Value(0)
291    }
292}
293
294impl IdentifierBase {
295    fn base_value(self) -> u64 {
296        match self {
297            IdentifierBase::False => 0,
298            IdentifierBase::Value(v) => {
299                if v == 0 {
300                    0
301                } else {
302                    1
303                }
304            }
305        }
306    }
307}
308
309impl From<bool> for IdentifierBase {
310    fn from(value: bool) -> Self {
311        if value {
312            IdentifierBase::Value(1)
313        } else {
314            IdentifierBase::False
315        }
316    }
317}
318
319impl From<u64> for IdentifierBase {
320    fn from(value: u64) -> Self {
321        IdentifierBase::Value(value)
322    }
323}
324
325impl From<usize> for IdentifierBase {
326    fn from(value: usize) -> Self {
327        IdentifierBase::Value(value as u64)
328    }
329}
330
331#[derive(Debug, Clone, PartialEq, Eq, Error)]
332pub enum IncrementError {
333    #[error("invalid identifier: {0}")]
334    InvalidIdentifier(String),
335    #[error("invalid increment argument: {0}")]
336    InvalidIncrementArgument(String),
337    #[error("version {0} is not a prerelease")]
338    NotAPrerelease(String),
339    #[error("increment would overflow a version component")]
340    Overflow,
341}
342
343impl fmt::Display for VersionDiff {
344    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
345        match self {
346            VersionDiff::Major => write!(f, "major"),
347            VersionDiff::Minor => write!(f, "minor"),
348            VersionDiff::Patch => write!(f, "patch"),
349            VersionDiff::PreMajor => write!(f, "premajor"),
350            VersionDiff::PreMinor => write!(f, "preminor"),
351            VersionDiff::PrePatch => write!(f, "prepatch"),
352            VersionDiff::PreRelease => write!(f, "prerelease"),
353        }
354    }
355}
356
357/**
358A semantic version, conformant to the [semver spec](https://semver.org/spec/v2.0.0.html).
359*/
360#[derive(Clone, Debug)]
361pub struct Version {
362    pub major: u64,
363    pub minor: u64,
364    pub patch: u64,
365    pub build: Vec<Identifier>,
366    pub pre_release: Vec<Identifier>,
367}
368
369#[cfg(feature = "serde")]
370impl Serialize for Version {
371    fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
372        s.collect_str(self)
373    }
374}
375
376#[cfg(feature = "serde")]
377impl<'de> Deserialize<'de> for Version {
378    fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
379        let s = String::deserialize(d)?;
380        s.parse().map_err(serde::de::Error::custom)
381    }
382}
383
384impl Version {
385    /// True if this [Version] satisfies the given [Range].
386    pub fn satisfies(&self, range: &Range) -> bool {
387        range.satisfies(self)
388    }
389
390    /// True if this [Version] satisfies the given [Range], treating prerelease
391    /// versions as valid matches even when the range does not explicitly
392    /// mention one.
393    pub fn satisfies_with_prerelease(&self, range: &Range, include_prerelease: bool) -> bool {
394        range.satisfies_with_prerelease(self, include_prerelease)
395    }
396
397    /// True is this [Version] has a prerelease component.
398    pub fn is_prerelease(&self) -> bool {
399        !self.pre_release.is_empty()
400    }
401
402    /// Increment this [Version] according to the given release type, returning a new [Version].
403    ///
404    /// Mirrors the behavior of the `SemVer.inc` method in node-semver.
405    pub fn inc(
406        &self,
407        release: &str,
408        identifier: Option<&str>,
409        identifier_base: Option<IdentifierBase>,
410    ) -> Result<Version, IncrementError> {
411        let mut cloned = self.clone();
412        cloned.inc_mut(release, identifier, identifier_base)?;
413        Ok(cloned)
414    }
415
416    fn inc_mut(
417        &mut self,
418        release: &str,
419        identifier: Option<&str>,
420        identifier_base: Option<IdentifierBase>,
421    ) -> Result<&mut Self, IncrementError> {
422        let identifier_base = identifier_base.unwrap_or_default();
423        let identifier_base_is_false = identifier_base == IdentifierBase::False;
424        let identifier = identifier.and_then(|id| {
425            if id.is_empty() {
426                None
427            } else {
428                Some(id.to_string())
429            }
430        });
431
432        if release.starts_with("pre") {
433            if identifier.is_none() && identifier_base_is_false {
434                return Err(IncrementError::InvalidIncrementArgument(
435                    "identifier is empty".into(),
436                ));
437            }
438            if let Some(id) = identifier.as_deref() {
439                if !is_valid_prerelease_identifier(id) {
440                    return Err(IncrementError::InvalidIdentifier(id.to_string()));
441                }
442            }
443        }
444
445        match release {
446            "premajor" => {
447                self.pre_release.clear();
448                self.patch = 0;
449                self.minor = 0;
450                self.major = self.major.checked_add(1).ok_or(IncrementError::Overflow)?;
451                self.inc_mut("pre", identifier.as_deref(), Some(identifier_base))?;
452            }
453            "preminor" => {
454                self.pre_release.clear();
455                self.patch = 0;
456                self.minor = self.minor.checked_add(1).ok_or(IncrementError::Overflow)?;
457                self.inc_mut("pre", identifier.as_deref(), Some(identifier_base))?;
458            }
459            "prepatch" => {
460                self.pre_release.clear();
461                self.inc_mut("patch", identifier.as_deref(), Some(identifier_base))?;
462                self.inc_mut("pre", identifier.as_deref(), Some(identifier_base))?;
463            }
464            "prerelease" => {
465                if self.pre_release.is_empty() {
466                    self.inc_mut("patch", identifier.as_deref(), Some(identifier_base))?;
467                }
468                self.inc_mut("pre", identifier.as_deref(), Some(identifier_base))?;
469            }
470            "release" => {
471                if self.pre_release.is_empty() {
472                    return Err(IncrementError::NotAPrerelease(self.to_string()));
473                }
474                self.pre_release.clear();
475            }
476            "major" => {
477                if self.minor != 0 || self.patch != 0 || self.pre_release.is_empty() {
478                    self.major = self.major.checked_add(1).ok_or(IncrementError::Overflow)?;
479                }
480                self.minor = 0;
481                self.patch = 0;
482                self.pre_release.clear();
483            }
484            "minor" => {
485                if self.patch != 0 || self.pre_release.is_empty() {
486                    self.minor = self.minor.checked_add(1).ok_or(IncrementError::Overflow)?;
487                }
488                self.patch = 0;
489                self.pre_release.clear();
490            }
491            "patch" => {
492                if self.pre_release.is_empty() {
493                    self.patch = self.patch.checked_add(1).ok_or(IncrementError::Overflow)?;
494                }
495                self.pre_release.clear();
496            }
497            "pre" => {
498                self.apply_pre_increment(identifier.as_deref(), identifier_base)?;
499            }
500            _ => {
501                return Err(IncrementError::InvalidIncrementArgument(
502                    release.to_string(),
503                ));
504            }
505        }
506
507        Ok(self)
508    }
509
510    fn apply_pre_increment(
511        &mut self,
512        identifier: Option<&str>,
513        identifier_base: IdentifierBase,
514    ) -> Result<(), IncrementError> {
515        let base = identifier_base.base_value();
516        let identifier_base_is_false = identifier_base == IdentifierBase::False;
517        let identifier = identifier.map(|id| id.to_string());
518
519        if self.pre_release.is_empty() {
520            self.pre_release.push(Identifier::Numeric(base));
521        } else {
522            let mut incremented = false;
523            for ident in self.pre_release.iter_mut().rev() {
524                if let Identifier::Numeric(num) = ident {
525                    *num = num.checked_add(1).ok_or(IncrementError::Overflow)?;
526                    incremented = true;
527                    break;
528                }
529            }
530
531            if !incremented {
532                if identifier_base_is_false {
533                    if let Some(id) = identifier.as_deref() {
534                        if id == join_prerelease_components(&self.pre_release) {
535                            return Err(IncrementError::InvalidIncrementArgument(
536                                "identifier already exists".into(),
537                            ));
538                        }
539                    }
540                }
541                self.pre_release.push(Identifier::Numeric(base));
542            }
543        }
544
545        if let Some(id) = identifier {
546            let prerelease = if identifier_base_is_false {
547                vec![Identifier::AlphaNumeric(id.clone())]
548            } else {
549                vec![
550                    Identifier::AlphaNumeric(id.clone()),
551                    Identifier::Numeric(base),
552                ]
553            };
554
555            if let Some(first) = self.pre_release.first() {
556                if compare_identifier_and_str(first, &id) == Ordering::Equal {
557                    if !matches!(self.pre_release.get(1), Some(Identifier::Numeric(_))) {
558                        self.pre_release = prerelease;
559                    }
560                } else {
561                    self.pre_release = prerelease;
562                }
563            } else {
564                self.pre_release = prerelease;
565            }
566        }
567
568        Ok(())
569    }
570
571    /// Parse a semver string into a [Version].
572    ///
573    /// ```rust
574    #[doc = include_str!("../examples/parse.rs")]
575    /// ```
576    pub fn parse<S: AsRef<str>>(input: S) -> Result<Version, SemverError> {
577        let mut input = input.as_ref();
578
579        if input.len() > MAX_LENGTH {
580            return Err(SemverError {
581                input: input.into(),
582                span: (input.len() - 1, 0).into(),
583                kind: SemverErrorKind::MaxLengthError,
584            });
585        }
586
587        match version.parse_next(&mut input) {
588            Ok(arg) => Ok(arg),
589            Err(err) => Err(match err {
590                ErrMode::Backtrack(e) | ErrMode::Cut(e) => SemverError {
591                    input: input.into(),
592                    span: (e.input.as_ptr() as usize - input.as_ptr() as usize, 0).into(),
593                    kind: if let Some(kind) = e.kind {
594                        kind
595                    } else if let Some(ctx) = e.context {
596                        SemverErrorKind::Context(ctx)
597                    } else {
598                        SemverErrorKind::Other
599                    },
600                },
601                ErrMode::Incomplete(_) => SemverError {
602                    input: input.into(),
603                    span: (input.len() - 1, 0).into(),
604                    kind: SemverErrorKind::IncompleteInput,
605                },
606            }),
607        }
608    }
609
610    /// difference between two [Version]s by the release type,
611    /// or `None` if the [Version]s are the same.
612    ///
613    /// ```rust
614    #[doc = include_str!("../examples/diff.rs")]
615    /// ```
616    pub fn diff(&self, other: &Self) -> Option<VersionDiff> {
617        let cmp_result = self.cmp(other);
618
619        if cmp_result == Ordering::Equal {
620            return None;
621        }
622
623        let self_higher = cmp_result == Ordering::Greater;
624        let high_version = if self_higher { self } else { other };
625        let low_version = if self_higher { other } else { self };
626        let high_has_pre = high_version.is_prerelease();
627        let low_has_pre = low_version.is_prerelease();
628
629        if low_has_pre && !high_has_pre {
630            // Going from prerelease -> no prerelease requires some special casing
631
632            // If the low version has only a major, then it will always be a major
633            // Some examples:
634            // 1.0.0-1 -> 1.0.0
635            // 1.0.0-1 -> 1.1.1
636            // 1.0.0-1 -> 2.0.0
637            if low_version.patch == 0 && low_version.minor == 0 {
638                return Some(VersionDiff::Major);
639            }
640
641            // Otherwise it can be determined by checking the high version
642            if high_version.patch != 0 {
643                // anything higher than a patch bump would result in the wrong version
644                return Some(VersionDiff::Patch);
645            }
646
647            if high_version.minor != 0 {
648                // anything higher than a minor bump would result in the wrong version
649                return Some(VersionDiff::Minor);
650            }
651
652            // bumping major/minor/patch all have same result
653            return Some(VersionDiff::Major);
654        }
655
656        if self.major != other.major {
657            if high_has_pre {
658                return Some(VersionDiff::PreMajor);
659            }
660
661            return Some(VersionDiff::Major);
662        }
663
664        if self.minor != other.minor {
665            if high_has_pre {
666                return Some(VersionDiff::PreMinor);
667            }
668
669            return Some(VersionDiff::Minor);
670        }
671
672        if self.patch != other.patch {
673            if high_has_pre {
674                return Some(VersionDiff::PrePatch);
675            }
676
677            return Some(VersionDiff::Patch);
678        }
679
680        // high and low are preleases
681        Some(VersionDiff::PreRelease)
682    }
683}
684
685impl PartialEq for Version {
686    fn eq(&self, other: &Self) -> bool {
687        self.major == other.major
688            && self.minor == other.minor
689            && self.patch == other.patch
690            && self.pre_release == other.pre_release
691    }
692}
693
694impl Eq for Version {}
695
696impl std::hash::Hash for Version {
697    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
698        self.major.hash(state);
699        self.minor.hash(state);
700        self.patch.hash(state);
701        self.pre_release.hash(state);
702    }
703}
704
705impl fmt::Display for Version {
706    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
707        write!(f, "{}.{}.{}", self.major, self.minor, self.patch)?;
708
709        for (i, ident) in self.pre_release.iter().enumerate() {
710            if i == 0 {
711                write!(f, "-")?;
712            } else {
713                write!(f, ".")?;
714            }
715            write!(f, "{}", ident)?;
716        }
717
718        for (i, ident) in self.build.iter().enumerate() {
719            if i == 0 {
720                write!(f, "+")?;
721            } else {
722                write!(f, ".")?;
723            }
724            write!(f, "{}", ident)?;
725        }
726
727        Ok(())
728    }
729}
730
731macro_rules! impl_from_unsigned_for_version {
732    ($($t:ident),+) => {
733        $(
734            impl ::std::convert::From<($t, $t, $t)> for Version {
735                fn from((major, minor, patch): ($t, $t, $t)) -> Self {
736                    Version {
737                        major: major as u64,
738                        minor: minor as u64,
739                        patch: patch as u64,
740                        build: Vec::new(),
741                        pre_release: Vec::new(),
742                    }
743                }
744            }
745
746            impl ::std::convert::From<($t, $t, $t, $t)> for Version {
747                fn from((major, minor, patch, pre_release): ($t, $t, $t, $t)) -> Self {
748                    Version {
749                        major: major as u64,
750                        minor: minor as u64,
751                        patch: patch as u64,
752                        build: Vec::new(),
753                        pre_release: vec![Identifier::Numeric(pre_release as u64)],
754                    }
755                }
756            }
757        )+
758    }
759}
760
761macro_rules! impl_from_signed_for_version {
762    ($($t:ident),+) => {
763        $(
764            impl ::std::convert::From<($t, $t, $t)> for Version {
765                fn from((major, minor, patch): ($t, $t, $t)) -> Self {
766                    debug_assert!(major >= 0, "Version major must be non-negative, got {}", major);
767                    debug_assert!(minor >= 0, "Version minor must be non-negative, got {}", minor);
768                    debug_assert!(patch >= 0, "Version patch must be non-negative, got {}", patch);
769
770                    Version {
771                        major: major as u64,
772                        minor: minor as u64,
773                        patch: patch as u64,
774                        build: Vec::new(),
775                        pre_release: Vec::new(),
776                    }
777                }
778            }
779
780            impl ::std::convert::From<($t, $t, $t, $t)> for Version {
781                fn from((major, minor, patch, pre_release): ($t, $t, $t, $t)) -> Self {
782                    debug_assert!(major >= 0, "Version major must be non-negative, got {}", major);
783                    debug_assert!(minor >= 0, "Version minor must be non-negative, got {}", minor);
784                    debug_assert!(patch >= 0, "Version patch must be non-negative, got {}", patch);
785                    debug_assert!(pre_release >= 0, "Version pre-release must be non-negative, got {}", pre_release);
786
787                    Version {
788                        major: major as u64,
789                        minor: minor as u64,
790                        patch: patch as u64,
791                        build: Vec::new(),
792                        pre_release: vec![Identifier::Numeric(pre_release as u64)],
793                    }
794                }
795            }
796        )+
797    }
798}
799
800impl_from_unsigned_for_version!(u8, u16, u32, u64, usize);
801impl_from_signed_for_version!(i8, i16, i32, i64, isize);
802
803impl std::str::FromStr for Version {
804    type Err = SemverError;
805    fn from_str(s: &str) -> Result<Self, Self::Err> {
806        Version::parse(s)
807    }
808}
809
810impl cmp::PartialOrd for Version {
811    fn partial_cmp(&self, other: &Version) -> Option<Ordering> {
812        Some(self.cmp(other))
813    }
814}
815
816impl cmp::Ord for Version {
817    fn cmp(&self, other: &Version) -> cmp::Ordering {
818        match self.major.cmp(&other.major) {
819            Ordering::Equal => {}
820            //if difference in major version, just return result
821            order_result => return order_result,
822        }
823
824        match self.minor.cmp(&other.minor) {
825            Ordering::Equal => {}
826            //if difference in minor version, just return result
827            order_result => return order_result,
828        }
829
830        match self.patch.cmp(&other.patch) {
831            Ordering::Equal => {}
832            //if difference in patch version, just return result
833            order_result => return order_result,
834        }
835
836        match (self.pre_release.len(), other.pre_release.len()) {
837            //if no pre_release string, they're equal
838            (0, 0) => Ordering::Equal,
839            //if other has a pre-release string, but this doesn't, this one is greater
840            (0, _) => Ordering::Greater,
841            //if this one has a pre-release string, but other doesn't this one is less than
842            (_, 0) => Ordering::Less,
843            // if both have pre_release strings, compare the strings and return the result
844            (_, _) => self.pre_release.cmp(&other.pre_release),
845        }
846    }
847}
848
849enum Extras {
850    Build(Vec<Identifier>),
851    Release(Vec<Identifier>),
852    ReleaseAndBuild((Vec<Identifier>, Vec<Identifier>)),
853}
854
855impl Extras {
856    fn values(self) -> (Vec<Identifier>, Vec<Identifier>) {
857        use Extras::*;
858        match self {
859            Release(ident) => (ident, Vec::new()),
860            Build(ident) => (Vec::new(), ident),
861            ReleaseAndBuild(ident) => ident,
862        }
863    }
864}
865
866/// <valid semver> ::= <version core>
867///                 | <version core> "-" <pre-release>
868///                 | <version core> "+" <build>
869///                 | <version core> "-" <pre-release> "+" <build>
870fn version<'s>(input: &mut &'s str) -> ModalResult<Version, SemverParseError<&'s str>> {
871    (
872        opt(alt((literal("v"), literal("V")))),
873        space0,
874        version_core,
875        extras,
876    )
877        .map(
878            |(_, _, (major, minor, patch), (pre_release, build))| Version {
879                major,
880                minor,
881                patch,
882                pre_release,
883                build,
884            },
885        )
886        .context("version")
887        .parse_next(input)
888}
889
890fn extras<'s>(
891    input: &mut &'s str,
892) -> ModalResult<(Vec<Identifier>, Vec<Identifier>), SemverParseError<&'s str>> {
893    Parser::map(
894        opt(alt((
895            Parser::map((pre_release, build), Extras::ReleaseAndBuild),
896            Parser::map(pre_release, Extras::Release),
897            Parser::map(build, Extras::Build),
898        ))),
899        |extras| match extras {
900            Some(extras) => extras.values(),
901            _ => Default::default(),
902        },
903    )
904    .parse_next(input)
905}
906
907/// <version core> ::= <major> "." <minor> "." <patch>
908fn version_core<'s>(
909    input: &mut &'s str,
910) -> ModalResult<(u64, u64, u64), SemverParseError<&'s str>> {
911    (number, literal("."), number, literal("."), number)
912        .map(|(major, _, minor, _, patch)| (major, minor, patch))
913        .context("version core")
914        .parse_next(input)
915}
916
917// I believe build, pre_release, and identifier are not 100% spec compliant.
918fn build<'s>(input: &mut &'s str) -> ModalResult<Vec<Identifier>, SemverParseError<&'s str>> {
919    preceded(literal("+"), separated(1.., identifier, literal(".")))
920        .context("build version")
921        .parse_next(input)
922}
923
924fn pre_release<'s>(input: &mut &'s str) -> ModalResult<Vec<Identifier>, SemverParseError<&'s str>> {
925    preceded(opt(literal("-")), separated(1.., identifier, literal(".")))
926        .context("pre_release version")
927        .parse_next(input)
928}
929
930fn identifier<'s>(input: &mut &'s str) -> ModalResult<Identifier, SemverParseError<&'s str>> {
931    Parser::map(
932        take_while(1.., |x: char| AsChar::is_alphanum(x as u8) || x == '-'),
933        |s: &str| {
934            str::parse::<u64>(s)
935                .map(Identifier::Numeric)
936                .unwrap_or_else(|_err| Identifier::AlphaNumeric(s.to_string()))
937        },
938    )
939    .context("identifier")
940    .parse_next(input)
941}
942
943fn is_valid_prerelease_identifier(identifier: &str) -> bool {
944    !identifier.is_empty()
945        && identifier.split('.').all(|segment| {
946            !segment.is_empty()
947                && segment
948                    .chars()
949                    .all(|ch| ch.is_ascii_alphanumeric() || ch == '-')
950        })
951}
952
953fn join_prerelease_components(pre_release: &[Identifier]) -> String {
954    pre_release
955        .iter()
956        .map(|ident| ident.to_string())
957        .collect::<Vec<_>>()
958        .join(".")
959}
960
961fn compare_identifier_and_str(existing: &Identifier, other: &str) -> Ordering {
962    match existing {
963        Identifier::Numeric(value) => {
964            if other.chars().all(|c| c.is_ascii_digit()) {
965                match other.parse::<u128>() {
966                    Ok(other_num) => (*value as u128).cmp(&other_num),
967                    Err(_) => Ordering::Less,
968                }
969            } else {
970                Ordering::Less
971            }
972        }
973        Identifier::AlphaNumeric(value) => {
974            if other.chars().all(|c| c.is_ascii_digit()) {
975                Ordering::Greater
976            } else {
977                value.as_str().cmp(other)
978            }
979        }
980    }
981}
982
983pub(crate) fn number<'s>(input: &mut &'s str) -> ModalResult<u64, SemverParseError<&'s str>> {
984    #[allow(suspicious_double_ref_op)]
985    let copied = input.clone();
986
987    Parser::try_map(Parser::take(digit1), |raw| {
988        let value = str::parse(raw).map_err(|e| SemverParseError {
989            input: copied,
990            context: None,
991            kind: Some(SemverErrorKind::ParseIntError(e)),
992        })?;
993
994        if value > MAX_SAFE_INTEGER {
995            return Err(SemverParseError {
996                input: copied,
997                context: None,
998                kind: Some(SemverErrorKind::MaxIntError(value)),
999            });
1000        }
1001
1002        Ok(value)
1003    })
1004    .context("number component")
1005    .parse_next(input)
1006}
1007
1008#[cfg(test)]
1009mod tests {
1010    use super::Identifier::*;
1011    use super::*;
1012
1013    use pretty_assertions::assert_eq;
1014    use serde_json::Value;
1015
1016    #[derive(Debug)]
1017    struct IncrementCase {
1018        version: String,
1019        release: String,
1020        expected: Option<String>,
1021        identifier: Option<String>,
1022        identifier_base: Option<IdentifierBase>,
1023    }
1024
1025    fn version_without_build(version: &Version) -> String {
1026        let mut output = format!("{}.{}.{}", version.major, version.minor, version.patch);
1027        if !version.pre_release.is_empty() {
1028            output.push('-');
1029            output.push_str(&join_prerelease_components(&version.pre_release));
1030        }
1031        output
1032    }
1033
1034    fn parse_identifier_base_value(value: &Value) -> Option<IdentifierBase> {
1035        match value {
1036            Value::Bool(false) => Some(IdentifierBase::False),
1037            Value::Bool(true) => Some(IdentifierBase::Value(1)),
1038            Value::Number(num) => num.as_u64().map(IdentifierBase::Value),
1039            Value::String(s) => s.parse::<u64>().ok().map(IdentifierBase::Value),
1040            Value::Null => None,
1041            _ => None,
1042        }
1043    }
1044
1045    fn parse_increment_entry(entry: Value) -> IncrementCase {
1046        let arr = entry
1047            .as_array()
1048            .unwrap_or_else(|| panic!("fixture entry must be an array: {entry:?}"));
1049
1050        let version = arr
1051            .get(0)
1052            .and_then(Value::as_str)
1053            .unwrap_or_else(|| panic!("missing version in fixture: {arr:?}"))
1054            .to_string();
1055        let release = arr
1056            .get(1)
1057            .and_then(Value::as_str)
1058            .unwrap_or_else(|| panic!("missing release in fixture: {arr:?}"))
1059            .to_string();
1060        let expected = arr.get(2).and_then(Value::as_str).map(str::to_string);
1061
1062        let options = arr.get(3);
1063        let (identifier, identifier_base_value) = if let Some(Value::String(s)) = options {
1064            (Some(s.to_string()), arr.get(4).cloned())
1065        } else {
1066            (
1067                arr.get(4).and_then(Value::as_str).map(str::to_string),
1068                arr.get(5).cloned(),
1069            )
1070        };
1071
1072        let identifier_base = identifier_base_value
1073            .as_ref()
1074            .and_then(parse_identifier_base_value);
1075
1076        IncrementCase {
1077            version,
1078            release,
1079            expected,
1080            identifier,
1081            identifier_base,
1082        }
1083    }
1084
1085    fn load_increment_cases() -> Vec<IncrementCase> {
1086        let raw = include_str!("../node-semver/test/fixtures/increments.js");
1087        let cleaned = raw
1088            .lines()
1089            .filter(|line| !line.trim_start().starts_with("//"))
1090            .collect::<Vec<_>>()
1091            .join("\n");
1092
1093        let start = cleaned
1094            .find('[')
1095            .expect("expected array start in fixture file");
1096        let end = cleaned
1097            .rfind(']')
1098            .expect("expected array end in fixture file");
1099
1100        let mut jsonish = cleaned[start..=end].to_string();
1101        jsonish = jsonish.replace('\'', "\"");
1102        jsonish = jsonish.replace("loose:", "\"loose\":");
1103        jsonish = jsonish.replace(",\n]", "\n]");
1104        jsonish = jsonish.replace(",]", "]");
1105
1106        let fixtures: Vec<Value> =
1107            serde_json::from_str(&jsonish).expect("failed to parse increment fixtures");
1108        fixtures
1109            .into_iter()
1110            .map(parse_increment_entry)
1111            .collect::<Vec<_>>()
1112    }
1113
1114    #[test]
1115    fn trivial_version_number() {
1116        let v = Version::parse("1.2.34").unwrap();
1117
1118        assert_eq!(
1119            v,
1120            Version {
1121                major: 1,
1122                minor: 2,
1123                patch: 34,
1124                build: Vec::with_capacity(2),
1125                pre_release: Vec::with_capacity(2),
1126            }
1127        );
1128    }
1129
1130    #[test]
1131    fn version_with_build() {
1132        let v = Version::parse("1.2.34+123.456").unwrap();
1133
1134        assert_eq!(
1135            v,
1136            Version {
1137                major: 1,
1138                minor: 2,
1139                patch: 34,
1140                build: vec![Numeric(123), Numeric(456)],
1141                pre_release: Vec::with_capacity(2),
1142            }
1143        );
1144    }
1145
1146    #[test]
1147    fn version_with_pre_release() {
1148        let v = Version::parse("1.2.34-abc.123").unwrap();
1149
1150        assert_eq!(
1151            v,
1152            Version {
1153                major: 1,
1154                minor: 2,
1155                patch: 34,
1156                pre_release: vec![AlphaNumeric("abc".into()), Numeric(123)],
1157                build: Vec::with_capacity(2),
1158            }
1159        );
1160    }
1161
1162    #[test]
1163    fn version_with_pre_release_and_build() {
1164        let v = Version::parse("1.2.34-abc.123+1").unwrap();
1165
1166        assert_eq!(
1167            v,
1168            Version {
1169                major: 1,
1170                minor: 2,
1171                patch: 34,
1172                pre_release: vec![AlphaNumeric("abc".into()), Numeric(123)],
1173                build: vec![Numeric(1),]
1174            }
1175        );
1176    }
1177
1178    #[test]
1179    fn pre_release_that_could_look_numeric_at_first() {
1180        let v = Version::parse("1.0.0-rc.2-migration").unwrap();
1181
1182        assert_eq!(
1183            v,
1184            Version {
1185                major: 1,
1186                minor: 0,
1187                patch: 0,
1188                pre_release: vec![
1189                    Identifier::AlphaNumeric("rc".into()),
1190                    Identifier::AlphaNumeric("2-migration".into())
1191                ],
1192                build: vec![],
1193            }
1194        );
1195    }
1196
1197    #[test]
1198    fn comparison_with_different_major_version() {
1199        let lesser_version = Version {
1200            major: 1,
1201            minor: 2,
1202            patch: 34,
1203            pre_release: vec![AlphaNumeric("abc".into()), Numeric(123)],
1204            build: vec![],
1205        };
1206        let greater_version = Version {
1207            major: 2,
1208            minor: 2,
1209            patch: 34,
1210            pre_release: vec![AlphaNumeric("abc".into()), Numeric(123)],
1211            build: vec![],
1212        };
1213        assert_eq!(lesser_version.cmp(&greater_version), Ordering::Less);
1214        assert_eq!(greater_version.cmp(&lesser_version), Ordering::Greater);
1215    }
1216    #[test]
1217    fn comparison_with_different_minor_version() {
1218        let lesser_version = Version {
1219            major: 1,
1220            minor: 2,
1221            patch: 34,
1222            pre_release: vec![AlphaNumeric("abc".into()), Numeric(123)],
1223            build: vec![],
1224        };
1225        let greater_version = Version {
1226            major: 1,
1227            minor: 3,
1228            patch: 34,
1229            pre_release: vec![AlphaNumeric("abc".into()), Numeric(123)],
1230            build: vec![],
1231        };
1232        assert_eq!(lesser_version.cmp(&greater_version), Ordering::Less);
1233        assert_eq!(greater_version.cmp(&lesser_version), Ordering::Greater);
1234    }
1235
1236    #[test]
1237    fn comparison_with_different_patch_version() {
1238        let lesser_version = Version {
1239            major: 1,
1240            minor: 2,
1241            patch: 34,
1242            pre_release: vec![AlphaNumeric("abc".into()), Numeric(123)],
1243            build: vec![],
1244        };
1245        let greater_version = Version {
1246            major: 1,
1247            minor: 2,
1248            patch: 56,
1249            pre_release: vec![AlphaNumeric("abc".into()), Numeric(123)],
1250            build: vec![],
1251        };
1252        assert_eq!(lesser_version.cmp(&greater_version), Ordering::Less);
1253        assert_eq!(greater_version.cmp(&lesser_version), Ordering::Greater);
1254    }
1255
1256    #[test]
1257    //confirms the comparison matches the pre-release comparison example in the SemVer spec.
1258    //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.
1259    //for simplicity just checks them in order. Assumes that the transitive property holds. So if a < b & b < c then a < c.
1260    fn comparison_with_different_pre_release_version() {
1261        let v1_alpha = Version {
1262            major: 1,
1263            minor: 0,
1264            patch: 0,
1265            pre_release: vec![AlphaNumeric("alpha".into())],
1266            build: vec![],
1267        };
1268        let v1_alpha1 = Version {
1269            major: 1,
1270            minor: 0,
1271            patch: 0,
1272            pre_release: vec![AlphaNumeric("alpha".into()), Numeric(1)],
1273            build: vec![],
1274        };
1275        assert_eq!(v1_alpha.cmp(&v1_alpha1), Ordering::Less);
1276        let v1_alpha_beta = Version {
1277            major: 1,
1278            minor: 0,
1279            patch: 0,
1280            pre_release: vec![AlphaNumeric("alpha".into()), AlphaNumeric("beta".into())],
1281            build: vec![],
1282        };
1283        assert_eq!(v1_alpha1.cmp(&v1_alpha_beta), Ordering::Less);
1284        let v1_beta = Version {
1285            major: 1,
1286            minor: 0,
1287            patch: 0,
1288            pre_release: vec![AlphaNumeric("beta".into())],
1289            build: vec![],
1290        };
1291        assert_eq!(v1_alpha_beta.cmp(&v1_beta), Ordering::Less);
1292        let v1_beta2 = Version {
1293            major: 1,
1294            minor: 0,
1295            patch: 0,
1296            pre_release: vec![AlphaNumeric("beta".into()), Numeric(2)],
1297            build: vec![],
1298        };
1299        assert_eq!(v1_beta.cmp(&v1_beta2), Ordering::Less);
1300        let v1_beta11 = Version {
1301            major: 1,
1302            minor: 0,
1303            patch: 0,
1304            pre_release: vec![AlphaNumeric("beta".into()), Numeric(11)],
1305            build: vec![],
1306        };
1307        assert_eq!(v1_beta2.cmp(&v1_beta11), Ordering::Less);
1308        let v1_rc1 = Version {
1309            major: 1,
1310            minor: 0,
1311            patch: 0,
1312            pre_release: vec![AlphaNumeric("rc".into()), Numeric(1)],
1313            build: vec![],
1314        };
1315        assert_eq!(v1_beta11.cmp(&v1_rc1), Ordering::Less);
1316        let v1 = Version {
1317            major: 1,
1318            minor: 0,
1319            patch: 0,
1320            pre_release: vec![],
1321            build: vec![],
1322        };
1323        assert_eq!(v1_rc1.cmp(&v1), Ordering::Less);
1324    }
1325
1326    #[test]
1327    fn individual_version_component_has_an_upper_bound() {
1328        let out_of_range = MAX_SAFE_INTEGER + 1;
1329        let v = Version::parse(format!("1.2.{}", out_of_range));
1330        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");
1331    }
1332
1333    #[test]
1334    fn version_string_limited_to_256_characters() {
1335        let prebuild = (0..257).map(|_| "X").collect::<Vec<_>>().join("");
1336        let version_string = format!("1.1.1-{}", prebuild);
1337        let v = Version::parse(version_string.clone());
1338
1339        assert_eq!(
1340            v.expect_err("Parse should have failed").to_string(),
1341            "Semver string can't be longer than 256 characters."
1342        );
1343
1344        let ok_version = version_string[0..255].to_string();
1345        let v = Version::parse(ok_version);
1346        assert!(v.is_ok());
1347    }
1348
1349    #[test]
1350    fn version_prefixed_with_v() {
1351        // TODO: This is part of strict parsing for nodejs-semver!
1352        let v = Version::parse("v1.2.3").unwrap();
1353        assert_eq!(
1354            v,
1355            Version {
1356                major: 1,
1357                minor: 2,
1358                patch: 3,
1359                pre_release: vec![],
1360                build: vec![],
1361            }
1362        );
1363    }
1364
1365    #[test]
1366    fn version_prefixed_with_v_space() {
1367        // TODO: Loose parsing supports this, so
1368        let v = Version::parse("v 1.2.3").unwrap();
1369        assert_eq!(
1370            v,
1371            Version {
1372                major: 1,
1373                minor: 2,
1374                patch: 3,
1375                pre_release: vec![],
1376                build: vec![],
1377            }
1378        );
1379    }
1380
1381    fn asset_version_diff(left: &str, right: &str, expected: &str) {
1382        let left = Version::parse(left).unwrap();
1383        let right = Version::parse(right).unwrap();
1384        let expected_diff = match expected {
1385            "major" => Some(VersionDiff::Major),
1386            "minor" => Some(VersionDiff::Minor),
1387            "patch" => Some(VersionDiff::Patch),
1388            "premajor" => Some(VersionDiff::PreMajor),
1389            "preminor" => Some(VersionDiff::PreMinor),
1390            "prepatch" => Some(VersionDiff::PrePatch),
1391            "null" => None,
1392            _ => unreachable!("unexpected version diff"),
1393        };
1394
1395        assert_eq!(
1396            left.diff(&right),
1397            expected_diff,
1398            "left: {}, right: {}",
1399            left,
1400            right
1401        );
1402    }
1403
1404    #[test]
1405    fn version_diffs() {
1406        let cases = vec![
1407            ("1.2.3", "0.2.3", "major"),
1408            ("0.2.3", "1.2.3", "major"),
1409            ("1.4.5", "0.2.3", "major"),
1410            ("1.2.3", "2.0.0-pre", "premajor"),
1411            ("2.0.0-pre", "1.2.3", "premajor"),
1412            ("1.2.3", "1.3.3", "minor"),
1413            ("1.0.1", "1.1.0-pre", "preminor"),
1414            ("1.2.3", "1.2.4", "patch"),
1415            ("1.2.3", "1.2.4-pre", "prepatch"),
1416            ("1.0.0", "1.0.0", "null"),
1417            ("1.0.0-1", "1.0.0-1", "null"),
1418            ("0.0.2-1", "0.0.2", "patch"),
1419            ("0.0.2-1", "0.0.3", "patch"),
1420            ("0.0.2-1", "0.1.0", "minor"),
1421            ("0.0.2-1", "1.0.0", "major"),
1422            ("0.1.0-1", "0.1.0", "minor"),
1423            ("1.0.0-1", "2.0.0-1", "premajor"),
1424            ("1.0.0-1", "1.1.0-1", "preminor"),
1425            ("1.0.0-1", "1.0.1-1", "prepatch"),
1426        ];
1427
1428        for case in cases {
1429            asset_version_diff(case.0, case.1, case.2);
1430        }
1431    }
1432
1433    #[test]
1434    fn increments_match_node_semver_fixture() {
1435        for case in load_increment_cases() {
1436            if let Some(expected) = &case.expected {
1437                let version = Version::parse(&case.version).unwrap_or_else(|e| {
1438                    panic!(
1439                        "expected to parse {} but failed: {}",
1440                        case.version,
1441                        e.to_string()
1442                    )
1443                });
1444                let before = version.to_string();
1445                let build = version.build.clone();
1446                let incremented = version
1447                    .inc(
1448                        &case.release,
1449                        case.identifier.as_deref(),
1450                        case.identifier_base,
1451                    )
1452                    .unwrap_or_else(|e| {
1453                        panic!(
1454                            "expected {} {} to succeed but errored: {}",
1455                            case.version, case.release, e
1456                        )
1457                    });
1458
1459                assert_eq!(
1460                    version_without_build(&incremented),
1461                    expected.as_str(),
1462                    "incrementing {} {} {:?} {:?}",
1463                    case.version,
1464                    case.release,
1465                    case.identifier,
1466                    case.identifier_base
1467                );
1468                assert_eq!(
1469                    incremented.build, build,
1470                    "build metadata should remain unchanged after increment"
1471                );
1472                assert_eq!(
1473                    version.to_string(),
1474                    before,
1475                    "original version should remain unchanged"
1476                );
1477            } else if let Ok(version) = Version::parse(&case.version) {
1478                let before = version.to_string();
1479                assert!(
1480                    version
1481                        .inc(
1482                            &case.release,
1483                            case.identifier.as_deref(),
1484                            case.identifier_base
1485                        )
1486                        .is_err(),
1487                    "expected {} {} to fail",
1488                    case.version,
1489                    case.release
1490                );
1491                assert_eq!(
1492                    version.to_string(),
1493                    before,
1494                    "version should stay unchanged on error"
1495                );
1496            }
1497        }
1498    }
1499
1500    #[test]
1501    fn invalid_increment_errors_match_node_semver() {
1502        let version = Version::parse("1.2.3").unwrap();
1503        let err = version
1504            .inc("prerelease", Some(""), Some(IdentifierBase::False))
1505            .unwrap_err();
1506        assert_eq!(
1507            err.to_string(),
1508            "invalid increment argument: identifier is empty"
1509        );
1510        assert_eq!(version.to_string(), "1.2.3");
1511
1512        let version = Version::parse("1.2.3-dev").unwrap();
1513        let err = version
1514            .inc("prerelease", Some("dev"), Some(IdentifierBase::False))
1515            .unwrap_err();
1516        assert_eq!(
1517            err.to_string(),
1518            "invalid increment argument: identifier already exists"
1519        );
1520        assert_eq!(version.to_string(), "1.2.3-dev");
1521
1522        let version = Version::parse("1.2.3").unwrap();
1523        let err = version
1524            .inc("prerelease", Some("invalid/preid"), None)
1525            .unwrap_err();
1526        assert_eq!(err.to_string(), "invalid identifier: invalid/preid");
1527        assert_eq!(version.to_string(), "1.2.3");
1528    }
1529}
1530
1531#[cfg(feature = "serde")]
1532#[cfg(test)]
1533mod serde_tests {
1534    use super::Identifier::*;
1535    use super::*;
1536
1537    #[test]
1538    fn version_serde() {
1539        let v = Version {
1540            major: 1,
1541            minor: 2,
1542            patch: 3,
1543            pre_release: vec![AlphaNumeric("abc".into()), Numeric(123)],
1544            build: vec![AlphaNumeric("build".into())],
1545        };
1546
1547        let serialized = serde_json::to_string(&v).unwrap();
1548        let deserialized: Version = serde_json::from_str(&serialized).unwrap();
1549
1550        assert_eq!(v, deserialized);
1551    }
1552}