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