1#![cfg_attr(docsrs, feature(doc_cfg))]
2#![cfg_attr(not(feature = "std"), no_std)]
3
4extern crate alloc;
84
85#[cfg(feature = "serde")]
86mod serde;
87
88use alloc::vec::Vec;
89use core::{cmp::Ordering, fmt};
90
91#[derive(Debug, Clone, PartialEq, Eq)]
93pub enum ParseError {
94 EmptyInput,
95 InvalidCharacter,
96 LeadingZero,
97 EmptyIdentifier,
98 InvalidNumber,
99 InvalidFormat,
100 TrailingData,
101}
102
103impl fmt::Display for ParseError {
104 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105 match self {
106 ParseError::EmptyInput => f.write_str("empty input"),
107 ParseError::InvalidCharacter => f.write_str("invalid character in version string"),
108 ParseError::LeadingZero => f.write_str("numeric identifier contains leading zero"),
109 ParseError::EmptyIdentifier => f.write_str("empty identifier"),
110 ParseError::InvalidNumber => f.write_str("invalid numeric identifier"),
111 ParseError::InvalidFormat => f.write_str("invalid version format"),
112 ParseError::TrailingData => f.write_str("unexpected trailing data"),
113 }
114 }
115}
116
117#[cfg(feature = "std")]
118impl std::error::Error for ParseError {}
119
120#[derive(Debug, Clone, Copy, PartialEq, Eq)]
122pub enum Identifier<'a> {
123 Numeric(u64),
124 Alpha(&'a str),
125}
126
127impl<'a> Identifier<'a> {
128 fn parse(input: &'a str) -> Result<Self, ParseError> {
129 if input.is_empty() {
130 return Err(ParseError::EmptyIdentifier);
131 }
132 for c in input.chars() {
133 if !c.is_ascii_alphanumeric() && c != '-' {
134 return Err(ParseError::InvalidCharacter);
135 }
136 }
137 let all_digits = input.chars().all(|c| c.is_ascii_digit());
138 if all_digits {
139 if input.len() > 1 && input.starts_with('0') {
140 return Err(ParseError::LeadingZero);
141 }
142 let n = input.parse::<u64>().map_err(|_| ParseError::InvalidNumber)?;
143 Ok(Identifier::Numeric(n))
144 } else {
145 Ok(Identifier::Alpha(input))
146 }
147 }
148}
149
150impl<'a> PartialOrd for Identifier<'a> {
151 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
152 Some(self.cmp(other))
153 }
154}
155
156impl<'a> Ord for Identifier<'a> {
157 fn cmp(&self, other: &Self) -> Ordering {
158 match (self, other) {
159 (Identifier::Numeric(_), Identifier::Alpha(_)) => Ordering::Less,
160 (Identifier::Alpha(_), Identifier::Numeric(_)) => Ordering::Greater,
161 (Identifier::Numeric(a), Identifier::Numeric(b)) => a.cmp(b),
162 (Identifier::Alpha(a), Identifier::Alpha(b)) => a.cmp(b),
163 }
164 }
165}
166
167#[derive(Debug, Clone, PartialEq, Eq)]
169pub struct PreRelease<'a> {
170 identifiers: Vec<Identifier<'a>>,
171}
172
173impl<'a> PreRelease<'a> {
174 fn parse(input: &'a str) -> Result<Self, ParseError> {
175 if input.is_empty() {
176 return Err(ParseError::EmptyIdentifier);
177 }
178 let identifiers = input.split('.').map(Identifier::parse).collect::<Result<Vec<_>, _>>()?;
179 Ok(PreRelease {
180 identifiers,
181 })
182 }
183
184 pub fn identifiers(&self) -> &[Identifier<'a>] {
185 &self.identifiers
186 }
187}
188
189impl<'a> fmt::Display for PreRelease<'a> {
190 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
191 for (i, ident) in self.identifiers.iter().enumerate() {
192 if i > 0 {
193 f.write_str(".")?;
194 }
195 match ident {
196 Identifier::Numeric(n) => write!(f, "{n}"),
197 Identifier::Alpha(s) => f.write_str(s),
198 }?;
199 }
200 Ok(())
201 }
202}
203
204impl<'a> PartialOrd for PreRelease<'a> {
205 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
206 Some(self.cmp(other))
207 }
208}
209
210impl<'a> Ord for PreRelease<'a> {
211 fn cmp(&self, other: &Self) -> Ordering {
212 for (a, b) in self.identifiers.iter().zip(other.identifiers.iter()) {
213 match a.cmp(b) {
214 Ordering::Equal => continue,
215 non_eq => return non_eq,
216 }
217 }
218 self.identifiers.len().cmp(&other.identifiers.len())
219 }
220}
221
222#[derive(Debug, Clone, Copy, PartialEq, Eq)]
224pub struct BuildMetadata<'a> {
225 raw: &'a str,
226}
227
228impl<'a> BuildMetadata<'a> {
229 fn parse(input: &'a str) -> Result<Self, ParseError> {
230 if input.is_empty() {
231 return Err(ParseError::EmptyIdentifier);
232 }
233 for part in input.split('.') {
234 if part.is_empty() {
235 return Err(ParseError::EmptyIdentifier);
236 }
237 for c in part.chars() {
238 if !c.is_ascii_alphanumeric() && c != '-' {
239 return Err(ParseError::InvalidCharacter);
240 }
241 }
242 }
243 Ok(BuildMetadata {
244 raw: input,
245 })
246 }
247
248 pub fn as_str(&self) -> &'a str {
249 self.raw
250 }
251}
252
253impl<'a> fmt::Display for BuildMetadata<'a> {
254 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
255 f.write_str(self.raw)
256 }
257}
258
259#[derive(Debug, Clone)]
282pub struct Version<'a> {
283 pub major: u64,
284 pub minor: u64,
285 pub patch: u64,
286 pub pre_release: Option<PreRelease<'a>>,
287 pub build: Option<BuildMetadata<'a>>,
288}
289
290pub fn parse(input: &str) -> Result<Version<'_>, ParseError> {
306 if input.is_empty() {
307 return Err(ParseError::EmptyInput);
308 }
309
310 let after_major = check_digits(input)?;
311 let (major_str, rest) = input.split_at(after_major);
312 let major = parse_number(major_str)?;
313 let rest = expect_dot(rest)?;
314
315 let after_minor = check_digits(rest)?;
316 let (minor_str, rest) = rest.split_at(after_minor);
317 let minor = parse_number(minor_str)?;
318 let rest = expect_dot(rest)?;
319
320 let after_patch = check_digits(rest)?;
321 let (patch_str, rest) = rest.split_at(after_patch);
322 let patch = parse_number(patch_str)?;
323
324 let (pre_release, after_pre_release) = match rest.strip_prefix('-') {
325 Some(r) => {
326 let end = r.find('+').unwrap_or(r.len());
327 let pre_str = &r[..end];
328 (Some(PreRelease::parse(pre_str)?), &r[end..])
329 }
330 None => (None, rest),
331 };
332
333 let build = match after_pre_release.strip_prefix('+') {
334 Some(r) => Some(BuildMetadata::parse(r)?),
335 None => {
336 if !after_pre_release.is_empty() {
337 return Err(ParseError::TrailingData);
338 }
339 None
340 }
341 };
342
343 Ok(Version {
344 major,
345 minor,
346 patch,
347 pre_release,
348 build,
349 })
350}
351
352fn check_digits(s: &str) -> Result<usize, ParseError> {
353 if s.is_empty() {
354 return Err(ParseError::InvalidCharacter);
355 }
356 match s.find(|c: char| !c.is_ascii_digit()) {
357 Some(0) => Err(ParseError::InvalidCharacter),
358 Some(pos) => Ok(pos),
359 None => Ok(s.len()),
360 }
361}
362
363fn parse_number(s: &str) -> Result<u64, ParseError> {
364 if s.len() > 1 && s.starts_with('0') {
365 return Err(ParseError::LeadingZero);
366 }
367 s.parse::<u64>().map_err(|_| ParseError::InvalidNumber)
368}
369
370fn expect_dot(s: &str) -> Result<&str, ParseError> {
371 match s.as_bytes().first() {
372 Some(b'.') => Ok(&s[1..]),
373 _ => Err(ParseError::InvalidFormat),
374 }
375}
376
377impl<'a> fmt::Display for Version<'a> {
378 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
379 write!(f, "{}.{}.{}", self.major, self.minor, self.patch)?;
380 if let Some(ref pre_release) = self.pre_release {
381 write!(f, "-{pre_release}")?;
382 }
383 if let Some(ref build) = self.build {
384 write!(f, "+{build}")?;
385 }
386 Ok(())
387 }
388}
389
390impl<'a> PartialEq for Version<'a> {
391 fn eq(&self, other: &Self) -> bool {
392 self.major == other.major
393 && self.minor == other.minor
394 && self.patch == other.patch
395 && self.pre_release == other.pre_release
396 }
397}
398
399impl<'a> Eq for Version<'a> {}
400
401impl<'a> PartialOrd for Version<'a> {
402 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
403 Some(self.cmp(other))
404 }
405}
406
407impl<'a> Ord for Version<'a> {
408 fn cmp(&self, other: &Self) -> Ordering {
409 match self.major.cmp(&other.major) {
410 Ordering::Equal => {}
411 non_eq => return non_eq,
412 }
413 match self.minor.cmp(&other.minor) {
414 Ordering::Equal => {}
415 non_eq => return non_eq,
416 }
417 match self.patch.cmp(&other.patch) {
418 Ordering::Equal => {}
419 non_eq => return non_eq,
420 }
421 match (&self.pre_release, &other.pre_release) {
422 (None, Some(_)) => Ordering::Greater,
423 (Some(_), None) => Ordering::Less,
424 (None, None) => Ordering::Equal,
425 (Some(a), Some(b)) => a.cmp(b),
426 }
427 }
428}
429
430#[cfg(test)]
431mod tests {
432 use alloc::{string::ToString, vec};
433
434 use super::*;
435
436 #[test]
437 fn parse_basic() {
438 let v = parse("1.2.3").unwrap();
439 assert_eq!(v.major, 1);
440 assert_eq!(v.minor, 2);
441 assert_eq!(v.patch, 3);
442 assert!(v.pre_release.is_none());
443 assert!(v.build.is_none());
444 }
445
446 #[test]
447 fn parse_zero() {
448 let v = parse("0.0.0").unwrap();
449 assert_eq!(v.major, 0);
450 assert_eq!(v.minor, 0);
451 assert_eq!(v.patch, 0);
452 }
453
454 #[test]
455 fn parse_large_numbers() {
456 let v = parse("999999999999999999.0.0").unwrap();
457 assert_eq!(v.major, 999999999999999999);
458 }
459
460 #[test]
461 fn parse_prerelease() {
462 let v = parse("1.0.0-alpha").unwrap();
463 assert_eq!(v.pre_release.unwrap().identifiers(), &[Identifier::Alpha("alpha")]);
464 }
465
466 #[test]
467 fn parse_prerelease_dotted() {
468 let v = parse("1.0.0-alpha.1").unwrap();
469 assert_eq!(
470 v.pre_release.as_ref().unwrap().identifiers(),
471 &[Identifier::Alpha("alpha"), Identifier::Numeric(1)]
472 );
473 }
474
475 #[test]
476 fn parse_prerelease_numeric() {
477 let v = parse("1.0.0-1.2.3").unwrap();
478 assert_eq!(
479 v.pre_release.as_ref().unwrap().identifiers(),
480 &[Identifier::Numeric(1), Identifier::Numeric(2), Identifier::Numeric(3)]
481 );
482 }
483
484 #[test]
485 fn parse_build_metadata() {
486 let v = parse("1.0.0+001").unwrap();
487 assert_eq!(v.build.unwrap().as_str(), "001");
488 }
489
490 #[test]
491 fn parse_prerelease_and_build() {
492 let v = parse("1.0.0-alpha.1+build.123").unwrap();
493 assert!(v.pre_release.is_some());
494 assert_eq!(v.build.unwrap().as_str(), "build.123");
495 }
496
497 #[test]
498 fn parse_complex_prerelease() {
499 let v = parse("1.0.0-0.3.7").unwrap();
500 assert_eq!(
501 v.pre_release.as_ref().unwrap().identifiers(),
502 &[Identifier::Numeric(0), Identifier::Numeric(3), Identifier::Numeric(7)]
503 );
504 }
505
506 #[test]
507 fn parse_prerelease_with_hyphens() {
508 let v = parse("1.0.0-x-y-z.--").unwrap();
509 let idents = v.pre_release.unwrap().identifiers().to_vec();
510 assert_eq!(idents.len(), 2);
511 assert_eq!(idents[0], Identifier::Alpha("x-y-z"));
512 assert_eq!(idents[1], Identifier::Alpha("--"));
513 }
514
515 #[test]
516 fn parse_build_with_dots() {
517 let v = parse("1.0.0+21AF26D3----117B344092BD").unwrap();
518 assert_eq!(v.build.unwrap().as_str(), "21AF26D3----117B344092BD");
519 }
520
521 #[test]
522 fn parse_build_with_multiple_dots() {
523 let v = parse("1.0.0+20130313144700").unwrap();
524 assert_eq!(v.build.unwrap().as_str(), "20130313144700");
525 }
526
527 #[test]
528 fn parse_build_after_prerelease() {
529 let v = parse("1.0.0-beta+exp.sha.5114f85").unwrap();
530 assert_eq!(v.build.unwrap().as_str(), "exp.sha.5114f85");
531 }
532
533 #[test]
534 fn error_empty_input() {
535 assert_eq!(parse(""), Err(ParseError::EmptyInput));
536 }
537
538 #[test]
539 fn error_leading_zero_major() {
540 assert_eq!(parse("01.2.3"), Err(ParseError::LeadingZero));
541 }
542
543 #[test]
544 fn error_leading_zero_minor() {
545 assert_eq!(parse("1.02.3"), Err(ParseError::LeadingZero));
546 }
547
548 #[test]
549 fn error_leading_zero_patch() {
550 assert_eq!(parse("1.2.03"), Err(ParseError::LeadingZero));
551 }
552
553 #[test]
554 fn error_leading_zero_in_prerelease() {
555 assert_eq!(parse("1.0.0-01"), Err(ParseError::LeadingZero));
556 }
557
558 #[test]
559 fn error_invalid_char() {
560 assert!(parse("1.2.3!").is_err());
561 }
562
563 #[test]
564 fn error_invalid_char_in_prerelease() {
565 assert!(parse("1.0.0-alpha$").is_err());
566 }
567
568 #[test]
569 fn error_empty_prerelease() {
570 assert_eq!(parse("1.0.0-"), Err(ParseError::EmptyIdentifier));
571 }
572
573 #[test]
574 fn error_empty_build() {
575 assert_eq!(parse("1.0.0+"), Err(ParseError::EmptyIdentifier));
576 }
577
578 #[test]
579 fn error_missing_dot() {
580 assert_eq!(parse("1.2"), Err(ParseError::InvalidFormat));
581 }
582
583 #[test]
584 fn error_non_digit_major() {
585 assert_eq!(parse("a.2.3"), Err(ParseError::InvalidCharacter));
586 }
587
588 #[test]
589 fn display_basic() {
590 assert_eq!(parse("1.2.3").unwrap().to_string(), "1.2.3");
591 }
592
593 #[test]
594 fn display_with_prerelease() {
595 assert_eq!(parse("1.0.0-alpha.1").unwrap().to_string(), "1.0.0-alpha.1");
596 }
597
598 #[test]
599 fn display_with_build() {
600 assert_eq!(parse("1.0.0+build.42").unwrap().to_string(), "1.0.0+build.42");
601 }
602
603 #[test]
604 fn display_with_both() {
605 assert_eq!(parse("1.0.0-rc.2+sha.abc123").unwrap().to_string(), "1.0.0-rc.2+sha.abc123");
606 }
607
608 #[test]
609 fn eq_ignores_build_metadata() {
610 let a = parse("1.0.0+build1").unwrap();
611 let b = parse("1.0.0+build2").unwrap();
612 assert_eq!(a, b);
613 }
614
615 #[test]
616 fn eq_respects_prerelease() {
617 let a = parse("1.0.0-alpha").unwrap();
618 let b = parse("1.0.0-beta").unwrap();
619 assert_ne!(a, b);
620 }
621
622 #[test]
623 fn precedence_spec_examples() {
624 let versions: Vec<Version> = vec!["1.0.0-alpha", "2.0.0", "2.1.0", "2.1.1"]
626 .into_iter()
627 .map(|s| parse(s).unwrap())
628 .collect();
629 for i in 0..versions.len() - 1 {
630 assert!(versions[i] < versions[i + 1], "{} < {}", versions[i], versions[i + 1]);
631 }
632 }
633
634 #[test]
635 fn precedence_prerelease_vs_release() {
636 assert!(parse("1.0.0-alpha").unwrap() < parse("1.0.0").unwrap());
637 }
638
639 #[test]
640 fn precedence_complex_prerelease() {
641 let spec_order = [
642 "1.0.0-alpha",
643 "1.0.0-alpha.1",
644 "1.0.0-alpha.beta",
645 "1.0.0-beta",
646 "1.0.0-beta.2",
647 "1.0.0-beta.11",
648 "1.0.0-rc.1",
649 "1.0.0",
650 ];
651 let versions: Vec<Version> = spec_order.iter().map(|s| parse(s).unwrap()).collect();
652 for i in 0..versions.len() - 1 {
653 assert!(
654 versions[i] < versions[i + 1],
655 "expected {} < {}",
656 spec_order[i],
657 spec_order[i + 1]
658 );
659 }
660 }
661
662 #[test]
663 fn precedence_build_ignored() {
664 assert_eq!(parse("1.0.0+1").unwrap().cmp(&parse("1.0.0+2").unwrap()), Ordering::Equal);
665 }
666
667 #[test]
668 fn precedence_numeric_before_alpha() {
669 let a = parse("1.0.0-1").unwrap();
670 let b = parse("1.0.0-alpha").unwrap();
671 assert!(a < b);
672 }
673
674 #[test]
677 fn error_leading_non_digit_major() {
678 assert_eq!(parse("a.0.0"), Err(ParseError::InvalidCharacter));
679 }
680
681 #[test]
682 fn error_leading_non_digit_minor() {
683 assert_eq!(parse("1.a.0"), Err(ParseError::InvalidCharacter));
684 }
685
686 #[test]
687 fn error_leading_non_digit_patch() {
688 assert_eq!(parse("1.0.a"), Err(ParseError::InvalidCharacter));
689 }
690
691 #[test]
694 fn error_empty_identifier_middle_prerelease() {
695 assert_eq!(parse("1.0.0-alpha..1"), Err(ParseError::EmptyIdentifier));
696 }
697
698 #[test]
699 fn error_empty_identifier_middle_build() {
700 assert_eq!(parse("1.0.0+a..b"), Err(ParseError::EmptyIdentifier));
701 }
702
703 #[test]
704 fn error_trailing_dot_build() {
705 assert_eq!(parse("1.0.0+build."), Err(ParseError::EmptyIdentifier));
706 }
707
708 #[test]
709 fn parse_prerelease_double_dash() {
710 let v = parse("1.0.0--alpha").unwrap();
712 assert_eq!(v.pre_release.as_ref().unwrap().identifiers(), &[Identifier::Alpha("-alpha")]);
713 }
714
715 #[test]
718 fn error_trailing_dot_after_patch() {
719 assert_eq!(parse("1.2.3."), Err(ParseError::TrailingData));
720 }
721
722 #[test]
723 fn error_dot_only_prerelease() {
724 assert_eq!(parse("1.0.0-."), Err(ParseError::EmptyIdentifier));
725 }
726
727 #[test]
728 fn error_trailing_plus_with_prerelease() {
729 assert_eq!(parse("1.0.0-alpha+"), Err(ParseError::EmptyIdentifier));
730 }
731
732 #[test]
733 fn error_space_in_prerelease() {
734 assert_eq!(parse("1.0.0-alpha beta"), Err(ParseError::InvalidCharacter));
735 }
736
737 #[test]
738 fn error_unicode_in_prerelease() {
739 assert_eq!(parse("1.0.0-α"), Err(ParseError::InvalidCharacter));
740 }
741
742 #[test]
743 fn error_space_in_build() {
744 assert_eq!(parse("1.0.0+build extra"), Err(ParseError::InvalidCharacter));
745 }
746
747 #[test]
748 fn error_double_plus_build() {
749 assert_eq!(parse("1.0.0+build+extra"), Err(ParseError::InvalidCharacter));
750 }
751
752 #[test]
753 fn error_unicode_in_build() {
754 assert_eq!(parse("1.0.0+é"), Err(ParseError::InvalidCharacter));
755 }
756
757 #[test]
758 fn error_prerelease_numeric_overflow() {
759 assert_eq!(parse("1.0.0-18446744073709551616"), Err(ParseError::InvalidNumber));
760 }
761
762 #[test]
765 fn parse_prerelease_alpha_starting_with_zero() {
766 let v = parse("1.0.0-0abc").unwrap();
767 assert_eq!(v.pre_release.as_ref().unwrap().identifiers(), &[Identifier::Alpha("0abc")]);
768 }
769
770 #[test]
771 fn parse_prerelease_hyphens_only() {
772 let v = parse("1.0.0-----").unwrap();
773 assert_eq!(v.pre_release.as_ref().unwrap().identifiers(), &[Identifier::Alpha("----")]);
774 }
775
776 #[test]
777 fn parse_prerelease_single_zero() {
778 let v = parse("1.0.0-0").unwrap();
779 assert_eq!(v.pre_release.as_ref().unwrap().identifiers(), &[Identifier::Numeric(0)]);
780 }
781
782 #[test]
783 fn parse_prerelease_with_plus_split() {
784 let v = parse("1.0.0-alpha+beta").unwrap();
785 assert_eq!(v.pre_release.as_ref().unwrap().identifiers(), &[Identifier::Alpha("alpha")]);
786 assert_eq!(v.build.unwrap().as_str(), "beta");
787 }
788
789 #[test]
790 fn parse_u64_max_major() {
791 let v = parse("18446744073709551615.0.0").unwrap();
792 assert_eq!(v.major, u64::MAX);
793 }
794
795 #[test]
796 fn parse_build_with_hyphen() {
797 let v = parse("1.0.0+build-id").unwrap();
798 assert_eq!(v.build.unwrap().as_str(), "build-id");
799 }
800
801 #[test]
804 fn precedence_prerelease_length_tiebreak() {
805 assert!(parse("1.0.0-1").unwrap() < parse("1.0.0-1.0").unwrap());
806 }
807
808 #[test]
809 fn precedence_prerelease_length_tiebreak_alpha() {
810 assert!(parse("1.0.0-alpha").unwrap() < parse("1.0.0-alpha.0").unwrap());
811 }
812
813 #[test]
814 fn precedence_prerelease_zero_length() {
815 assert!(parse("1.0.0-0").unwrap() < parse("1.0.0-0.0").unwrap());
816 }
817
818 #[test]
819 fn precedence_alpha_case_ascii() {
820 assert!(parse("1.0.0-A").unwrap() < parse("1.0.0-a").unwrap());
821 }
822
823 #[test]
824 fn precedence_hyphen_in_alpha() {
825 assert!(parse("1.0.0--a").unwrap() < parse("1.0.0-a").unwrap());
826 }
827
828 #[test]
831 fn display_roundtrip() {
832 let inputs = [
833 "1.2.3",
834 "0.0.0",
835 "1.0.0-alpha",
836 "1.0.0-alpha.1",
837 "1.0.0-0.3.7",
838 "1.0.0-x-y-z.--",
839 "1.0.0+001",
840 "1.0.0+21AF26D3----117B344092BD",
841 "1.0.0+20130313144700",
842 "1.0.0-beta+exp.sha.5114f85",
843 "1.0.0-rc.2+sha.abc123",
844 "1.0.0----",
845 "1.0.0-0abc",
846 "1.0.0+build-id",
847 ];
848 for input in &inputs {
849 let v = parse(input).unwrap();
850 assert_eq!(v.to_string(), *input, "round-trip failed for: {input}");
851 }
852 }
853
854 #[test]
855 fn display_direct_construction() {
856 let v = Version {
857 major: 2,
858 minor: 5,
859 patch: 1,
860 pre_release: Some(PreRelease {
861 identifiers: vec![Identifier::Alpha("rc"), Identifier::Numeric(3)],
862 }),
863 build: Some(BuildMetadata {
864 raw: "sha.abc",
865 }),
866 };
867 assert_eq!(v.to_string(), "2.5.1-rc.3+sha.abc");
868 }
869
870 #[test]
873 fn direct_version_eq() {
874 let a = Version {
875 major: 1,
876 minor: 2,
877 patch: 3,
878 pre_release: None,
879 build: None,
880 };
881 let b = Version {
882 major: 1,
883 minor: 2,
884 patch: 3,
885 pre_release: None,
886 build: Some(BuildMetadata {
887 raw: "x",
888 }),
889 };
890 assert_eq!(a, b);
891 }
892
893 #[test]
894 fn direct_version_ord() {
895 let a = Version {
896 major: 1,
897 minor: 0,
898 patch: 0,
899 pre_release: None,
900 build: None,
901 };
902 let b = Version {
903 major: 1,
904 minor: 0,
905 patch: 0,
906 pre_release: Some(PreRelease {
907 identifiers: vec![Identifier::Alpha("alpha")],
908 }),
909 build: None,
910 };
911 assert!(b < a);
912 }
913
914 #[test]
917 fn identifier_cmp_numeric_vs_alpha() {
918 assert!(Identifier::Numeric(1) < Identifier::Alpha("a"));
919 assert!(Identifier::Alpha("a") > Identifier::Numeric(1));
920 }
921
922 #[test]
923 fn identifier_cmp_numeric_values() {
924 assert!(Identifier::Numeric(1) < Identifier::Numeric(2));
925 assert!(Identifier::Numeric(5) == Identifier::Numeric(5));
926 }
927
928 #[test]
929 fn identifier_cmp_alpha_values() {
930 assert!(Identifier::Alpha("alpha") < Identifier::Alpha("beta"));
931 assert!(Identifier::Alpha("a") < Identifier::Alpha("b"));
932 assert!(Identifier::Alpha("--") < Identifier::Alpha("a"));
933 }
934
935 #[test]
938 fn prerelease_cmp_length() {
939 let a = PreRelease {
940 identifiers: vec![Identifier::Numeric(1)],
941 };
942 let b = PreRelease {
943 identifiers: vec![Identifier::Numeric(1), Identifier::Numeric(0)],
944 };
945 assert!(a < b);
946
947 let c = PreRelease {
948 identifiers: vec![Identifier::Alpha("alpha")],
949 };
950 let d = PreRelease {
951 identifiers: vec![Identifier::Alpha("alpha"), Identifier::Numeric(1)],
952 };
953 assert!(c < d);
954 }
955
956 #[cfg(feature = "serde")]
959 #[test]
960 fn serde_roundtrip() {
961 use serde_json;
962 let v = parse("1.2.3-alpha+build").unwrap();
963 let json = serde_json::to_string(&v).unwrap();
964 let v2: Version = serde_json::from_str(&json).unwrap();
965 assert_eq!(v, v2);
966 }
967
968 #[cfg(feature = "serde")]
969 #[test]
970 fn serde_deserialize_invalid_errors() {
971 use serde_json;
972 let result: Result<Version, _> = serde_json::from_str("\"01.2.3\"");
973 assert!(result.is_err());
974 }
975}