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