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
25pub const MAX_SAFE_INTEGER: u64 = 900_719_925_474_099;
30
31pub const MAX_LENGTH: usize = 256;
33
34#[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 pub fn input(&self) -> &str {
79 &self.input
80 }
81
82 pub fn span(&self) -> &SourceSpan {
84 &self.span
85 }
86
87 pub fn offset(&self) -> usize {
89 self.span.offset()
90 }
91
92 pub fn kind(&self) -> &SemverErrorKind {
97 &self.kind
98 }
99
100 pub fn location(&self) -> (usize, usize) {
103 let prefix = &self.input.as_bytes()[..self.offset()];
105
106 let line_number = bytecount::count(prefix, b'\n');
108
109 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 let line = self.input[line_begin..]
120 .lines()
121 .next()
122 .unwrap_or(&self.input[line_begin..])
123 .trim_end();
124
125 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#[derive(Debug, Clone, Error, Eq, PartialEq, Diagnostic)]
136pub enum SemverErrorKind {
137 #[error("Semver string can't be longer than {} characters.", MAX_LENGTH)]
142 #[diagnostic(code(nodejs_semver::too_long), url(docsrs))]
143 MaxLengthError,
144
145 #[error("Incomplete input to semver parser.")]
152 #[diagnostic(code(nodejs_semver::incomplete_input), url(docsrs))]
153 IncompleteInput,
154
155 #[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 #[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 #[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 #[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#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
249pub enum Identifier {
250 Numeric(u64),
252 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#[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
277pub type ReleaseType = VersionDiff;
279
280#[derive(Debug, Clone, Copy, PartialEq, Eq)]
281pub enum IdentifierBase {
282 False,
284 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#[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 pub fn satisfies(&self, range: &Range) -> bool {
387 range.satisfies(self)
388 }
389
390 pub fn satisfies_with_prerelease(&self, range: &Range, include_prerelease: bool) -> bool {
394 range.satisfies_with_prerelease(self, include_prerelease)
395 }
396
397 pub fn is_prerelease(&self) -> bool {
399 !self.pre_release.is_empty()
400 }
401
402 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 #[doc = include_str!("../examples/parse.rs")]
575 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 #[doc = include_str!("../examples/diff.rs")]
615 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 if low_version.patch == 0 && low_version.minor == 0 {
638 return Some(VersionDiff::Major);
639 }
640
641 if high_version.patch != 0 {
643 return Some(VersionDiff::Patch);
645 }
646
647 if high_version.minor != 0 {
648 return Some(VersionDiff::Minor);
650 }
651
652 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 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 order_result => return order_result,
822 }
823
824 match self.minor.cmp(&other.minor) {
825 Ordering::Equal => {}
826 order_result => return order_result,
828 }
829
830 match self.patch.cmp(&other.patch) {
831 Ordering::Equal => {}
832 order_result => return order_result,
834 }
835
836 match (self.pre_release.len(), other.pre_release.len()) {
837 (0, 0) => Ordering::Equal,
839 (0, _) => Ordering::Greater,
841 (_, 0) => Ordering::Less,
843 (_, _) => 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
866fn 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
907fn 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
917fn 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 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 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 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}