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