1use std::{
2 cmp::Ordering,
3 fmt::{Display, Formatter},
4 iter::Peekable,
5 num::NonZeroUsize,
6 str::{CharIndices, Chars, FromStr},
7};
8
9use alpm_parsers::{iter_char_context, iter_str_context};
10use semver::Version as SemverVersion;
11use serde::{Deserialize, Serialize};
12use strum::VariantNames;
13use winnow::{
14 ModalResult,
15 Parser,
16 ascii::{dec_uint, digit1},
17 combinator::{Repeat, alt, cut_err, eof, fail, opt, preceded, repeat, seq, terminated},
18 error::{StrContext, StrContextValue},
19 token::{one_of, take_till, take_while},
20};
21
22use crate::{Architecture, error::Error};
23
24#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize)]
42pub struct BuildToolVersion {
43 version: Version,
44 architecture: Option<Architecture>,
45}
46
47impl BuildToolVersion {
48 pub fn new(version: Version, architecture: Option<Architecture>) -> Self {
50 BuildToolVersion {
51 version,
52 architecture,
53 }
54 }
55
56 pub fn architecture(&self) -> &Option<Architecture> {
58 &self.architecture
59 }
60
61 pub fn version(&self) -> &Version {
63 &self.version
64 }
65}
66
67impl FromStr for BuildToolVersion {
68 type Err = Error;
69 fn from_str(s: &str) -> Result<Self, Self::Err> {
71 const VERSION_DELIMITER: char = '-';
72 match s.rsplit_once(VERSION_DELIMITER) {
73 Some((version, architecture)) => match Architecture::from_str(architecture) {
74 Ok(architecture) => Ok(BuildToolVersion {
75 version: Version::with_pkgrel(version)?,
76 architecture: Some(architecture),
77 }),
78 Err(err) => Err(err.into()),
79 },
80 None => Ok(BuildToolVersion {
81 version: Version::from_str(s)?,
82 architecture: None,
83 }),
84 }
85 }
86}
87
88impl Display for BuildToolVersion {
89 fn fmt(&self, fmt: &mut Formatter) -> std::fmt::Result {
90 if let Some(architecture) = &self.architecture {
91 write!(fmt, "{}-{}", self.version, architecture)
92 } else {
93 write!(fmt, "{}", self.version)
94 }
95 }
96}
97
98#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
119pub struct Epoch(pub NonZeroUsize);
120
121impl Epoch {
122 pub fn new(epoch: NonZeroUsize) -> Self {
124 Epoch(epoch)
125 }
126
127 pub fn parser(input: &mut &str) -> ModalResult<Self> {
135 terminated(dec_uint, eof)
136 .verify_map(NonZeroUsize::new)
137 .context(StrContext::Label("package epoch"))
138 .context(StrContext::Expected(StrContextValue::Description(
139 "positive non-zero decimal integer",
140 )))
141 .map(Self)
142 .parse_next(input)
143 }
144}
145
146impl FromStr for Epoch {
147 type Err = Error;
148 fn from_str(s: &str) -> Result<Self, Self::Err> {
150 Ok(Self::parser.parse(s)?)
151 }
152}
153
154impl Display for Epoch {
155 fn fmt(&self, fmt: &mut Formatter) -> std::fmt::Result {
156 write!(fmt, "{}", self.0)
157 }
158}
159
160#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
184pub struct PackageRelease {
185 pub major: usize,
187 pub minor: Option<usize>,
189}
190
191impl PackageRelease {
192 pub fn new(major: usize, minor: Option<usize>) -> Self {
204 PackageRelease { major, minor }
205 }
206
207 pub fn parser(input: &mut &str) -> ModalResult<Self> {
215 seq!(Self {
216 major: digit1.try_map(FromStr::from_str)
217 .context(StrContext::Label("package release"))
218 .context(StrContext::Expected(StrContextValue::Description(
219 "positive decimal integer",
220 ))),
221 minor: opt(preceded('.', cut_err(digit1.try_map(FromStr::from_str))))
222 .context(StrContext::Label("package release"))
223 .context(StrContext::Expected(StrContextValue::Description(
224 "single '.' followed by positive decimal integer",
225 ))),
226 _: eof.context(StrContext::Expected(StrContextValue::Description(
227 "end of package release value",
228 ))),
229 })
230 .parse_next(input)
231 }
232}
233
234impl FromStr for PackageRelease {
235 type Err = Error;
236 fn from_str(s: &str) -> Result<Self, Self::Err> {
244 Ok(Self::parser.parse(s)?)
245 }
246}
247
248impl Display for PackageRelease {
249 fn fmt(&self, fmt: &mut Formatter) -> std::fmt::Result {
250 write!(fmt, "{}", self.major)?;
251 if let Some(minor) = self.minor {
252 write!(fmt, ".{minor}")?;
253 }
254 Ok(())
255 }
256}
257
258impl PartialOrd for PackageRelease {
259 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
260 Some(self.cmp(other))
261 }
262}
263
264impl Ord for PackageRelease {
265 fn cmp(&self, other: &Self) -> Ordering {
266 let major_order = self.major.cmp(&other.major);
267 if major_order != Ordering::Equal {
268 return major_order;
269 }
270
271 match (self.minor, other.minor) {
272 (None, None) => Ordering::Equal,
273 (None, Some(_)) => Ordering::Less,
274 (Some(_), None) => Ordering::Greater,
275 (Some(minor), Some(other_minor)) => minor.cmp(&other_minor),
276 }
277 }
278}
279
280#[derive(Clone, Debug, Deserialize, Eq, Serialize)]
307pub struct PackageVersion(pub(crate) String);
308
309impl PackageVersion {
310 pub fn new(pkgver: String) -> Result<Self, Error> {
312 PackageVersion::from_str(pkgver.as_str())
313 }
314
315 pub fn inner(&self) -> &str {
317 &self.0
318 }
319
320 pub fn segments(&self) -> VersionSegments {
322 VersionSegments::new(&self.0)
323 }
324
325 pub fn parser(input: &mut &str) -> ModalResult<Self> {
333 let alnum = |c: char| c.is_ascii_alphanumeric();
334
335 let first_character = one_of(alnum)
336 .context(StrContext::Label("first pkgver character"))
337 .context(StrContext::Expected(StrContextValue::Description(
338 "ASCII alphanumeric character",
339 )));
340 let special_tail_character = ['_', '+', '.'];
341 let tail_character = one_of((alnum, special_tail_character));
342
343 let tail: Repeat<_, _, _, (), _> = repeat(0.., tail_character);
346
347 (
348 first_character,
349 tail,
350 eof.context(StrContext::Label("pkgver character"))
351 .context(StrContext::Expected(StrContextValue::Description(
352 "ASCII alphanumeric character",
353 )))
354 .context_with(iter_char_context!(special_tail_character)),
355 )
356 .take()
357 .map(|s: &str| Self(s.to_string()))
358 .parse_next(input)
359 }
360}
361
362impl FromStr for PackageVersion {
363 type Err = Error;
364 fn from_str(s: &str) -> Result<Self, Self::Err> {
366 Ok(Self::parser.parse(s)?)
367 }
368}
369
370impl Display for PackageVersion {
371 fn fmt(&self, fmt: &mut Formatter) -> std::fmt::Result {
372 write!(fmt, "{}", self.inner())
373 }
374}
375
376#[derive(Debug, Clone, Eq, PartialEq)]
427pub enum VersionSegment<'a> {
428 Segment {
435 text: &'a str,
437 delimiter_count: usize,
439 },
440 SubSegment {
447 text: &'a str,
449 },
450}
451
452impl<'a> VersionSegment<'a> {
453 pub fn text(&self) -> &str {
455 match self {
456 VersionSegment::Segment { text, .. } | VersionSegment::SubSegment { text } => text,
457 }
458 }
459
460 pub fn is_empty(&self) -> bool {
462 match self {
463 VersionSegment::Segment { text, .. } | VersionSegment::SubSegment { text } => {
464 text.is_empty()
465 }
466 }
467 }
468
469 pub fn chars(&self) -> Chars<'a> {
471 match self {
472 VersionSegment::Segment { text, .. } | VersionSegment::SubSegment { text } => {
473 text.chars()
474 }
475 }
476 }
477
478 pub fn parse<T: FromStr>(&self) -> Result<T, T::Err> {
481 match self {
482 VersionSegment::Segment { text, .. } | VersionSegment::SubSegment { text } => {
483 FromStr::from_str(text)
484 }
485 }
486 }
487
488 pub fn str_cmp(&self, other: &VersionSegment) -> Ordering {
490 match self {
491 VersionSegment::Segment { text, .. } | VersionSegment::SubSegment { text } => {
492 text.cmp(&other.text())
493 }
494 }
495 }
496}
497
498pub struct VersionSegments<'a> {
507 version: &'a str,
510 version_chars: Peekable<CharIndices<'a>>,
512 in_segment: bool,
516}
517
518impl<'a> VersionSegments<'a> {
519 pub fn new(version: &'a str) -> Self {
521 VersionSegments {
522 version,
523 version_chars: version.char_indices().peekable(),
524 in_segment: false,
525 }
526 }
527}
528
529impl<'a> Iterator for VersionSegments<'a> {
530 type Item = VersionSegment<'a>;
531
532 fn next(&mut self) -> Option<VersionSegment<'a>> {
534 let mut delimiter_count = 0;
536
537 while let Some((_, char)) = self.version_chars.peek() {
540 if char.is_alphanumeric() {
542 break;
543 }
544
545 self.version_chars.next();
546 delimiter_count += 1;
547
548 self.in_segment = false;
550 continue;
551 }
552
553 let Some((first_index, first_char)) = self.version_chars.next() else {
555 if delimiter_count == 0 {
560 return None;
561 }
562
563 return Some(VersionSegment::Segment {
567 text: "",
568 delimiter_count,
569 });
570 };
571
572 let mut last_char = first_char;
575 let mut last_char_index = first_index;
576
577 let is_numeric = first_char.is_numeric();
582
583 if is_numeric {
584 #[allow(clippy::while_let_on_iterator)]
586 while let Some((index, next_char)) =
587 self.version_chars.next_if(|(_, peek)| peek.is_numeric())
588 {
589 last_char_index = index;
590 last_char = next_char;
591 }
592 } else {
593 #[allow(clippy::while_let_on_iterator)]
595 while let Some((index, next_char)) =
596 self.version_chars.next_if(|(_, peek)| peek.is_alphabetic())
597 {
598 last_char_index = index;
599 last_char = next_char;
600 }
601 }
602
603 let segment_slice = &self.version[first_index..(last_char_index + last_char.len_utf8())];
606
607 if !self.in_segment {
608 self.in_segment = true;
611 Some(VersionSegment::Segment {
612 text: segment_slice,
613 delimiter_count,
614 })
615 } else {
616 Some(VersionSegment::SubSegment {
617 text: segment_slice,
618 })
619 }
620 }
621}
622
623impl Ord for PackageVersion {
624 fn cmp(&self, other: &Self) -> Ordering {
631 if self.inner() == other.inner() {
633 return Ordering::Equal;
634 }
635
636 let mut self_segments = self.segments();
637 let mut other_segments = other.segments();
638
639 loop {
641 let self_segment = self_segments.next();
643 let other_segment = other_segments.next();
644
645 let (self_segment, other_segment) = match (self_segment, other_segment) {
647 (Some(self_seg), Some(other_seg)) => (self_seg, other_seg),
649
650 (None, None) => return Ordering::Equal,
652
653 (Some(seg), None) => {
683 let text = match seg {
686 VersionSegment::Segment { .. } => return Ordering::Greater,
687 VersionSegment::SubSegment { text } => text,
688 };
689
690 if !text.is_empty() && text.chars().all(char::is_alphabetic) {
693 return Ordering::Less;
694 }
695
696 return Ordering::Greater;
697 }
698
699 (None, Some(seg)) => {
701 let text = match seg {
702 VersionSegment::Segment { .. } => return Ordering::Less,
703 VersionSegment::SubSegment { text } => text,
704 };
705 if !text.is_empty() && text.chars().all(char::is_alphabetic) {
706 return Ordering::Greater;
707 }
708 if !text.is_empty() && text.chars().all(char::is_alphabetic) {
709 return Ordering::Greater;
710 }
711
712 return Ordering::Less;
713 }
714 };
715
716 if other_segment.is_empty() && self_segment.is_empty() {
725 return Ordering::Equal;
731 } else if self_segment.is_empty() {
732 if other_segment.chars().all(char::is_alphabetic) {
743 return Ordering::Greater;
744 }
745
746 return Ordering::Less;
750 } else if other_segment.is_empty() {
751 if self_segment.chars().all(char::is_alphabetic) {
753 return Ordering::Less;
754 }
755
756 return Ordering::Greater;
757 }
758
759 let (self_text, other_text) = match (self_segment, other_segment) {
771 (
772 VersionSegment::Segment {
773 delimiter_count: self_count,
774 text: self_text,
775 },
776 VersionSegment::Segment {
777 delimiter_count: other_count,
778 text: other_text,
779 },
780 ) => {
781 if self_count != other_count {
789 return self_count.cmp(&other_count);
790 }
791 (self_text, other_text)
792 }
793 (VersionSegment::Segment { .. }, VersionSegment::SubSegment { .. }) => {
800 return Ordering::Greater;
801 }
802 (VersionSegment::SubSegment { .. }, VersionSegment::Segment { .. }) => {
803 return Ordering::Less;
804 }
805 (
806 VersionSegment::SubSegment { text: self_text },
807 VersionSegment::SubSegment { text: other_text },
808 ) => (self_text, other_text),
809 };
810
811 let self_is_numeric = !self_text.is_empty() && self_text.chars().all(char::is_numeric);
820 let other_is_numeric =
821 !other_text.is_empty() && other_text.chars().all(char::is_numeric);
822
823 if self_is_numeric && !other_is_numeric {
824 return Ordering::Greater;
825 } else if !self_is_numeric && other_is_numeric {
826 return Ordering::Less;
827 } else if self_is_numeric && other_is_numeric {
828 let ordering = self_text
837 .parse::<usize>()
838 .unwrap()
839 .cmp(&other_text.parse::<usize>().unwrap());
840
841 match ordering {
842 Ordering::Less => return Ordering::Less,
843 Ordering::Greater => return Ordering::Greater,
844 Ordering::Equal => continue,
846 }
847 }
848
849 match self_text.cmp(other_text) {
852 Ordering::Less => return Ordering::Less,
853 Ordering::Greater => return Ordering::Greater,
854 Ordering::Equal => continue,
856 }
857 }
858 }
859}
860
861impl PartialOrd for PackageVersion {
862 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
863 Some(self.cmp(other))
864 }
865}
866
867impl PartialEq for PackageVersion {
868 fn eq(&self, other: &Self) -> bool {
869 self.cmp(other).is_eq()
870 }
871}
872
873#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
898pub struct SchemaVersion(SemverVersion);
899
900impl SchemaVersion {
901 pub fn new(version: SemverVersion) -> Self {
903 SchemaVersion(version)
904 }
905
906 pub fn inner(&self) -> &SemverVersion {
908 &self.0
909 }
910}
911
912impl FromStr for SchemaVersion {
913 type Err = Error;
914 fn from_str(s: &str) -> Result<SchemaVersion, Self::Err> {
919 if !s.contains('.') {
920 match s.parse() {
921 Ok(major) => Ok(SchemaVersion(SemverVersion::new(major, 0, 0))),
922 Err(e) => Err(Error::InvalidInteger {
923 kind: e.kind().clone(),
924 }),
925 }
926 } else {
927 match SemverVersion::parse(s) {
928 Ok(version) => Ok(SchemaVersion(version)),
929 Err(e) => Err(Error::InvalidSemver {
930 kind: e.to_string(),
931 }),
932 }
933 }
934 }
935}
936
937impl Display for SchemaVersion {
938 fn fmt(&self, fmt: &mut Formatter) -> std::fmt::Result {
939 write!(fmt, "{}", self.0)
940 }
941}
942
943#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
966pub struct Version {
967 pub pkgver: PackageVersion,
969 pub epoch: Option<Epoch>,
971 pub pkgrel: Option<PackageRelease>,
973}
974
975impl Version {
976 pub fn new(
978 pkgver: PackageVersion,
979 epoch: Option<Epoch>,
980 pkgrel: Option<PackageRelease>,
981 ) -> Self {
982 Version {
983 pkgver,
984 epoch,
985 pkgrel,
986 }
987 }
988
989 pub fn with_pkgrel(version: &str) -> Result<Self, Error> {
991 let version = Version::from_str(version)?;
992 if version.pkgrel.is_some() {
993 Ok(version)
994 } else {
995 Err(Error::MissingComponent {
996 component: "pkgrel",
997 })
998 }
999 }
1000
1001 pub fn vercmp(a: &Version, b: &Version) -> i8 {
1033 match a.cmp(b) {
1034 Ordering::Less => -1,
1035 Ordering::Equal => 0,
1036 Ordering::Greater => 1,
1037 }
1038 }
1039
1040 pub fn parser(input: &mut &str) -> ModalResult<Self> {
1048 let mut epoch = opt(terminated(take_till(1.., ':'), ':').and_then(
1049 cut_err(Epoch::parser),
1051 ))
1052 .context(StrContext::Expected(StrContextValue::Description(
1053 "followed by a ':'",
1054 )));
1055
1056 seq!(Self {
1057 epoch: epoch,
1058 pkgver: take_till(1.., '-')
1059 .context(StrContext::Expected(StrContextValue::Description("pkgver string")))
1061 .and_then(PackageVersion::parser),
1062 pkgrel: opt(preceded('-', cut_err(PackageRelease::parser))),
1063 _: eof.context(StrContext::Expected(StrContextValue::Description("end of version string"))),
1064 })
1065 .parse_next(input)
1066 }
1067}
1068
1069impl FromStr for Version {
1070 type Err = Error;
1071 fn from_str(s: &str) -> Result<Version, Self::Err> {
1079 Ok(Self::parser.parse(s)?)
1080 }
1081}
1082
1083impl Display for Version {
1084 fn fmt(&self, fmt: &mut Formatter) -> std::fmt::Result {
1085 if let Some(epoch) = self.epoch {
1086 write!(fmt, "{}:", epoch)?;
1087 }
1088
1089 write!(fmt, "{}", self.pkgver)?;
1090
1091 if let Some(pkgrel) = &self.pkgrel {
1092 write!(fmt, "-{}", pkgrel)?;
1093 }
1094
1095 Ok(())
1096 }
1097}
1098
1099impl Ord for Version {
1100 fn cmp(&self, other: &Self) -> Ordering {
1101 match (self.epoch, other.epoch) {
1102 (Some(self_epoch), Some(other_epoch)) if self_epoch.cmp(&other_epoch).is_ne() => {
1103 return self_epoch.cmp(&other_epoch);
1104 }
1105 (Some(_), None) => return Ordering::Greater,
1106 (None, Some(_)) => return Ordering::Less,
1107 (_, _) => {}
1108 }
1109
1110 let pkgver_cmp = self.pkgver.cmp(&other.pkgver);
1111 if pkgver_cmp.is_ne() {
1112 return pkgver_cmp;
1113 }
1114
1115 self.pkgrel.cmp(&other.pkgrel)
1116 }
1117}
1118
1119impl PartialOrd for Version {
1120 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1121 Some(self.cmp(other))
1122 }
1123}
1124
1125#[derive(
1148 strum::AsRefStr,
1149 Clone,
1150 Copy,
1151 Debug,
1152 strum::Display,
1153 strum::EnumIter,
1154 PartialEq,
1155 Eq,
1156 strum::VariantNames,
1157 Serialize,
1158 Deserialize,
1159)]
1160pub enum VersionComparison {
1161 #[strum(to_string = "<=")]
1163 LessOrEqual,
1164
1165 #[strum(to_string = ">=")]
1167 GreaterOrEqual,
1168
1169 #[strum(to_string = "=")]
1171 Equal,
1172
1173 #[strum(to_string = "<")]
1175 Less,
1176
1177 #[strum(to_string = ">")]
1179 Greater,
1180}
1181
1182impl VersionComparison {
1183 fn is_compatible_with(self, ord: Ordering) -> bool {
1186 match (self, ord) {
1187 (VersionComparison::Less, Ordering::Less)
1188 | (VersionComparison::LessOrEqual, Ordering::Less | Ordering::Equal)
1189 | (VersionComparison::Equal, Ordering::Equal)
1190 | (VersionComparison::GreaterOrEqual, Ordering::Greater | Ordering::Equal)
1191 | (VersionComparison::Greater, Ordering::Greater) => true,
1192
1193 (VersionComparison::Less, Ordering::Equal | Ordering::Greater)
1194 | (VersionComparison::LessOrEqual, Ordering::Greater)
1195 | (VersionComparison::Equal, Ordering::Less | Ordering::Greater)
1196 | (VersionComparison::GreaterOrEqual, Ordering::Less)
1197 | (VersionComparison::Greater, Ordering::Less | Ordering::Equal) => false,
1198 }
1199 }
1200
1201 pub fn parser(input: &mut &str) -> ModalResult<Self> {
1209 alt((
1210 ("<=", eof).value(Self::LessOrEqual),
1212 (">=", eof).value(Self::GreaterOrEqual),
1213 ("=", eof).value(Self::Equal),
1214 ("<", eof).value(Self::Less),
1215 (">", eof).value(Self::Greater),
1216 fail.context(StrContext::Label("comparison operator"))
1217 .context_with(iter_str_context!([VersionComparison::VARIANTS])),
1218 ))
1219 .parse_next(input)
1220 }
1221}
1222
1223impl FromStr for VersionComparison {
1224 type Err = Error;
1225
1226 fn from_str(s: &str) -> Result<Self, Self::Err> {
1234 Ok(Self::parser.parse(s)?)
1235 }
1236}
1237
1238#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1262pub struct VersionRequirement {
1263 pub comparison: VersionComparison,
1265 pub version: Version,
1267}
1268
1269impl VersionRequirement {
1270 pub fn new(comparison: VersionComparison, version: Version) -> Self {
1272 VersionRequirement {
1273 comparison,
1274 version,
1275 }
1276 }
1277
1278 pub fn is_satisfied_by(&self, ver: &Version) -> bool {
1299 self.comparison.is_compatible_with(ver.cmp(&self.version))
1300 }
1301
1302 pub fn parser(input: &mut &str) -> ModalResult<Self> {
1310 seq!(Self {
1311 comparison: take_while(1.., ('<', '>', '='))
1312 .context(StrContext::Expected(StrContextValue::Description(
1314 "version comparison operator"
1315 )))
1316 .and_then(VersionComparison::parser),
1317 version: Version::parser,
1318 })
1319 .parse_next(input)
1320 }
1321}
1322
1323impl Display for VersionRequirement {
1324 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1325 write!(f, "{}{}", self.comparison, self.version)
1326 }
1327}
1328
1329impl FromStr for VersionRequirement {
1330 type Err = Error;
1331
1332 fn from_str(s: &str) -> Result<Self, Self::Err> {
1340 Ok(Self::parser.parse(s)?)
1341 }
1342}
1343
1344#[cfg(test)]
1345mod tests {
1346 use rstest::rstest;
1347
1348 use super::*;
1349
1350 #[rstest]
1351 #[case("1.0.0", Ok(SchemaVersion(SemverVersion::new(1, 0, 0))))]
1352 #[case("1", Ok(SchemaVersion(SemverVersion::new(1, 0, 0))))]
1353 #[case("-1.0.0", Err(Error::InvalidSemver { kind: String::from("unexpected character '-' while parsing major version number") }))]
1354 fn schema_version(#[case] version: &str, #[case] result: Result<SchemaVersion, Error>) {
1355 assert_eq!(result, SchemaVersion::from_str(version))
1356 }
1357
1358 #[rstest]
1360 #[case(
1361 "1.0.0-1-any",
1362 BuildToolVersion::new(Version::from_str("1.0.0-1").unwrap(), Some(Architecture::from_str("any").unwrap())),
1363 )]
1364 #[case(
1365 "1:1.0.0-1-any",
1366 BuildToolVersion::new(Version::from_str("1:1.0.0-1").unwrap(), Some(Architecture::from_str("any").unwrap())),
1367 )]
1368 #[case(
1369 "1.0.0",
1370 BuildToolVersion::new(Version::from_str("1.0.0").unwrap(), None),
1371 )]
1372 fn valid_buildtoolver_new(#[case] buildtoolver: &str, #[case] expected: BuildToolVersion) {
1373 assert_eq!(
1374 BuildToolVersion::from_str(buildtoolver),
1375 Ok(expected),
1376 "Expected valid parse of buildtoolver '{buildtoolver}'"
1377 );
1378 }
1379
1380 #[rstest]
1382 #[case("1.0.0-any", Error::MissingComponent { component: "pkgrel" })]
1383 #[case("1.0.0-1-foo", strum::ParseError::VariantNotFound.into())]
1384 fn invalid_buildtoolver_new(#[case] buildtoolver: &str, #[case] expected: Error) {
1385 assert_eq!(
1386 BuildToolVersion::from_str(buildtoolver),
1387 Err(expected),
1388 "Expected error during parse of buildtoolver '{buildtoolver}'"
1389 );
1390 }
1391
1392 #[rstest]
1393 #[case(".1.0.0-1-any", "invalid first pkgver character")]
1394 fn invalid_buildtoolver_badpkgver(#[case] buildtoolver: &str, #[case] err_snippet: &str) {
1395 let Err(Error::ParseError(err_msg)) = BuildToolVersion::from_str(buildtoolver) else {
1396 panic!("'{buildtoolver}' erroneously parsed as BuildToolVersion")
1397 };
1398 assert!(
1399 err_msg.contains(err_snippet),
1400 "Error:\n=====\n{err_msg}\n=====\nshould contain snippet:\n\n{err_snippet}"
1401 );
1402 }
1403
1404 #[rstest]
1405 #[case(
1406 SchemaVersion(SemverVersion::new(1, 0, 0)),
1407 SchemaVersion(SemverVersion::new(0, 1, 0))
1408 )]
1409 fn compare_schema_version(#[case] version_a: SchemaVersion, #[case] version_b: SchemaVersion) {
1410 assert!(version_a > version_b);
1411 }
1412
1413 #[rstest]
1415 #[case(
1416 "foo",
1417 Version {
1418 epoch: None,
1419 pkgver: PackageVersion::new("foo".to_string()).unwrap(),
1420 pkgrel: None
1421 },
1422 )]
1423 #[case(
1424 "1:foo-1",
1425 Version {
1426 pkgver: PackageVersion::new("foo".to_string()).unwrap(),
1427 epoch: Some(Epoch::new(NonZeroUsize::new(1).unwrap())),
1428 pkgrel: Some(PackageRelease::new(1, None))
1429 },
1430 )]
1431 #[case(
1432 "1:foo",
1433 Version {
1434 pkgver: PackageVersion::new("foo".to_string()).unwrap(),
1435 epoch: Some(Epoch::new(NonZeroUsize::new(1).unwrap())),
1436 pkgrel: None,
1437 },
1438 )]
1439 #[case(
1440 "foo-1",
1441 Version {
1442 pkgver: PackageVersion::new("foo".to_string()).unwrap(),
1443 epoch: None,
1444 pkgrel: Some(PackageRelease::new(1, None))
1445 }
1446 )]
1447 fn valid_version_from_string(#[case] version: &str, #[case] expected: Version) {
1448 assert_eq!(
1449 Version::from_str(version),
1450 Ok(expected),
1451 "Expected valid parsing for version {version}"
1452 )
1453 }
1454
1455 #[rstest]
1457 #[case::two_pkgrel("1:foo-1-1", "expected end of package release value")]
1458 #[case::two_epoch("1:1:foo-1", "invalid pkgver character")]
1459 #[case::no_version("", "expected pkgver string")]
1460 #[case::no_version(":", "invalid first pkgver character")]
1461 #[case::no_version(".", "invalid first pkgver character")]
1462 #[case::invalid_integer(
1463 "-1foo:1",
1464 "invalid package epoch\nexpected positive non-zero decimal integer, followed by a ':'"
1465 )]
1466 #[case::invalid_integer(
1467 "1-foo:1",
1468 "invalid package epoch\nexpected positive non-zero decimal integer, followed by a ':'"
1469 )]
1470 fn parse_error_in_version_from_string(#[case] version: &str, #[case] err_snippet: &str) {
1471 let Err(Error::ParseError(err_msg)) = Version::from_str(version) else {
1472 panic!("parsing '{version}' did not fail as expected")
1473 };
1474 assert!(
1475 err_msg.contains(err_snippet),
1476 "Error:\n=====\n{err_msg}\n=====\nshould contain snippet:\n\n{err_snippet}"
1477 );
1478 }
1479
1480 #[rstest]
1483 #[case(
1484 "1.0.0-1",
1485 Ok(Version{
1486 pkgver: PackageVersion::new("1.0.0".to_string()).unwrap(),
1487 pkgrel: Some(PackageRelease::new(1, None)),
1488 epoch: None,
1489 })
1490 )]
1491 #[case("1.0.0", Err(Error::MissingComponent { component: "pkgrel" }))]
1492 fn version_with_pkgrel(#[case] version: &str, #[case] result: Result<Version, Error>) {
1493 assert_eq!(result, Version::with_pkgrel(version));
1494 }
1495
1496 #[rstest]
1497 #[case("1", Ok(Epoch(NonZeroUsize::new(1).unwrap())))]
1498 fn epoch(#[case] version: &str, #[case] result: Result<Epoch, Error>) {
1499 assert_eq!(result, Epoch::from_str(version));
1500 }
1501
1502 #[rstest]
1503 #[case("0", "expected positive non-zero decimal integer")]
1504 #[case("-0", "expected positive non-zero decimal integer")]
1505 #[case("z", "expected positive non-zero decimal integer")]
1506 fn epoch_parse_failure(#[case] input: &str, #[case] err_snippet: &str) {
1507 let Err(Error::ParseError(err_msg)) = Epoch::from_str(input) else {
1508 panic!("'{input}' erroneously parsed as Epoch")
1509 };
1510 assert!(
1511 err_msg.contains(err_snippet),
1512 "Error:\n=====\n{err_msg}\n=====\nshould contain snippet:\n\n{err_snippet}"
1513 );
1514 }
1515
1516 #[rstest]
1518 #[case("foo")]
1519 #[case("1.0.0")]
1520 fn valid_pkgver(#[case] pkgver: &str) {
1521 let parsed = PackageVersion::new(pkgver.to_string());
1522 assert!(parsed.is_ok(), "Expected pkgver {pkgver} to be valid.");
1523 assert_eq!(
1524 parsed.as_ref().unwrap().to_string(),
1525 pkgver,
1526 "Expected parsed PackageVersion representation '{}' to be identical to input '{}'",
1527 parsed.unwrap(),
1528 pkgver
1529 );
1530 }
1531
1532 #[rstest]
1534 #[case("1:foo", "invalid pkgver character")]
1535 #[case("foo-1", "invalid pkgver character")]
1536 #[case("foo,1", "invalid pkgver character")]
1537 #[case(".foo", "invalid first pkgver character")]
1538 #[case("_foo", "invalid first pkgver character")]
1539 #[case("ß", "invalid first pkgver character")]
1541 #[case("1.ß", "invalid pkgver character")]
1542 fn invalid_pkgver(#[case] pkgver: &str, #[case] err_snippet: &str) {
1543 let Err(Error::ParseError(err_msg)) = PackageVersion::new(pkgver.to_string()) else {
1544 panic!("Expected pkgver {pkgver} to be invalid.")
1545 };
1546 assert!(
1547 err_msg.contains(err_snippet),
1548 "Error:\n=====\n{err_msg}\n=====\nshould contain snippet:\n\n{err_snippet}"
1549 );
1550 }
1551
1552 #[rstest]
1554 #[case("0")]
1555 #[case("1")]
1556 #[case("10")]
1557 #[case("1.0")]
1558 #[case("10.5")]
1559 #[case("0.1")]
1560 fn valid_pkgrel(#[case] pkgrel: &str) {
1561 let parsed = PackageRelease::from_str(pkgrel);
1562 assert!(parsed.is_ok(), "Expected pkgrel {pkgrel} to be valid.");
1563 assert_eq!(
1564 parsed.as_ref().unwrap().to_string(),
1565 pkgrel,
1566 "Expected parsed PackageRelease representation '{}' to be identical to input '{}'",
1567 parsed.unwrap(),
1568 pkgrel
1569 );
1570 }
1571
1572 #[rstest]
1574 #[case(".1", "expected positive decimal integer")]
1575 #[case("1.", "expected single '.' followed by positive decimal integer")]
1576 #[case("1..1", "expected single '.' followed by positive decimal integer")]
1577 #[case("-1", "expected positive decimal integer")]
1578 #[case("a", "expected positive decimal integer")]
1579 #[case("1.a", "expected single '.' followed by positive decimal integer")]
1580 #[case("1.0.0", "expected end of package release")]
1581 #[case("", "expected positive decimal integer")]
1582 fn invalid_pkgrel(#[case] pkgrel: &str, #[case] err_snippet: &str) {
1583 let Err(Error::ParseError(err_msg)) = PackageRelease::from_str(pkgrel) else {
1584 panic!("'{pkgrel}' erroneously parsed as PackageRelease")
1585 };
1586 assert!(
1587 err_msg.contains(err_snippet),
1588 "Error:\n=====\n{err_msg}\n=====\nshould contain snippet:\n\n{err_snippet}"
1589 );
1590 }
1591
1592 #[rstest]
1594 #[case("1", "1.0", Ordering::Less)]
1595 #[case("1.0", "2", Ordering::Less)]
1596 #[case("1", "1.1", Ordering::Less)]
1597 #[case("1.0", "1.1", Ordering::Less)]
1598 #[case("0", "1.1", Ordering::Less)]
1599 #[case("1", "11", Ordering::Less)]
1600 #[case("1", "1", Ordering::Equal)]
1601 #[case("1.2", "1.2", Ordering::Equal)]
1602 #[case("2.0", "2.0", Ordering::Equal)]
1603 #[case("2", "1.0", Ordering::Greater)]
1604 #[case("1.1", "1", Ordering::Greater)]
1605 #[case("1.1", "1.0", Ordering::Greater)]
1606 #[case("1.1", "0", Ordering::Greater)]
1607 #[case("11", "1", Ordering::Greater)]
1608 fn pkgrel_cmp(#[case] first: &str, #[case] second: &str, #[case] order: Ordering) {
1609 let first = PackageRelease::from_str(first).unwrap();
1610 let second = PackageRelease::from_str(second).unwrap();
1611 assert_eq!(
1612 first.cmp(&second),
1613 order,
1614 "{first} should be {order:?} to {second}"
1615 );
1616 }
1617
1618 #[rstest]
1620 #[case(Version::from_str("1:1-1").unwrap(), "1:1-1")]
1621 #[case(Version::from_str("1-1").unwrap(), "1-1")]
1622 #[case(Version::from_str("1").unwrap(), "1")]
1623 #[case(Version::from_str("1:1").unwrap(), "1:1")]
1624 fn version_to_string(#[case] version: Version, #[case] to_str: &str) {
1625 assert_eq!(format!("{}", version), to_str);
1626 }
1627
1628 #[rstest]
1629 #[case(Version::from_str("1"), Version::from_str("1"), Ordering::Equal)]
1631 #[case(Version::from_str("1"), Version::from_str("2"), Ordering::Less)]
1632 #[case(
1633 Version::from_str("20220102"),
1634 Version::from_str("20220202"),
1635 Ordering::Less
1636 )]
1637 #[case(Version::from_str("1"), Version::from_str("1.1"), Ordering::Less)]
1639 #[case(Version::from_str("01"), Version::from_str("1"), Ordering::Equal)]
1640 #[case(Version::from_str("001a"), Version::from_str("1a"), Ordering::Equal)]
1641 #[case(Version::from_str("a1a"), Version::from_str("a1b"), Ordering::Less)]
1642 #[case(Version::from_str("foo"), Version::from_str("1.1"), Ordering::Less)]
1643 #[case(Version::from_str("1.0"), Version::from_str("1..0"), Ordering::Less)]
1645 #[case(Version::from_str("1.1"), Version::from_str("1.1"), Ordering::Equal)]
1646 #[case(Version::from_str("1.1"), Version::from_str("1.2"), Ordering::Less)]
1647 #[case(Version::from_str("1..0"), Version::from_str("1..0"), Ordering::Equal)]
1648 #[case(Version::from_str("1..0"), Version::from_str("1..1"), Ordering::Less)]
1649 #[case(Version::from_str("1+0"), Version::from_str("1.0"), Ordering::Equal)]
1650 #[case(Version::from_str("1+1"), Version::from_str("1+2"), Ordering::Less)]
1651 #[case(Version::from_str("1.1"), Version::from_str("1.1.a"), Ordering::Less)]
1653 #[case(Version::from_str("1.1"), Version::from_str("1.11a"), Ordering::Less)]
1654 #[case(Version::from_str("1.1"), Version::from_str("1.1_a"), Ordering::Less)]
1655 #[case(Version::from_str("1.1a"), Version::from_str("1.1"), Ordering::Less)]
1656 #[case(Version::from_str("1.1a1"), Version::from_str("1.1"), Ordering::Less)]
1657 #[case(Version::from_str("1.a"), Version::from_str("1.1"), Ordering::Less)]
1658 #[case(Version::from_str("1.a"), Version::from_str("1.alpha"), Ordering::Less)]
1659 #[case(Version::from_str("1.a1"), Version::from_str("1.1"), Ordering::Less)]
1660 #[case(Version::from_str("1.a11"), Version::from_str("1.1"), Ordering::Less)]
1661 #[case(Version::from_str("1.a1a"), Version::from_str("1.a1"), Ordering::Less)]
1662 #[case(Version::from_str("1.alpha"), Version::from_str("1.b"), Ordering::Less)]
1663 #[case(Version::from_str("a.1"), Version::from_str("1.1"), Ordering::Less)]
1664 #[case(
1665 Version::from_str("1.alpha0.0"),
1666 Version::from_str("1.alpha.0"),
1667 Ordering::Less
1668 )]
1669 #[case(Version::from_str("1.0"), Version::from_str("1.0."), Ordering::Less)]
1671 #[case(Version::from_str("1.0."), Version::from_str("1.0.0"), Ordering::Less)]
1673 #[case(Version::from_str("1.0.."), Version::from_str("1.0."), Ordering::Equal)]
1674 #[case(
1675 Version::from_str("1.0.alpha.0"),
1676 Version::from_str("1.0."),
1677 Ordering::Less
1678 )]
1679 #[case(
1680 Version::from_str("1.a001a.1"),
1681 Version::from_str("1.a1a.1"),
1682 Ordering::Equal
1683 )]
1684 fn version_cmp(
1685 #[case] version_a: Result<Version, Error>,
1686 #[case] version_b: Result<Version, Error>,
1687 #[case] expected: Ordering,
1688 ) {
1689 let version_a = version_a.unwrap();
1691 let version_b = version_b.unwrap();
1692
1693 let vercmp_result = match &expected {
1695 Ordering::Equal => 0,
1696 Ordering::Greater => 1,
1697 Ordering::Less => -1,
1698 };
1699
1700 let ordering = version_a.cmp(&version_b);
1701 assert_eq!(
1702 ordering, expected,
1703 "Failed to compare '{version_a}' and '{version_b}'. Expected {expected:?} got {ordering:?}"
1704 );
1705
1706 assert_eq!(Version::vercmp(&version_a, &version_b), vercmp_result);
1707
1708 #[cfg(feature = "compatibility_tests")]
1710 {
1711 let output = std::process::Command::new("vercmp")
1712 .arg(version_a.to_string())
1713 .arg(version_b.to_string())
1714 .output()
1715 .unwrap();
1716 let result = String::from_utf8_lossy(&output.stdout);
1717 assert_eq!(result.trim(), vercmp_result.to_string());
1718 }
1719
1720 let reverse_vercmp_result = match &expected {
1722 Ordering::Equal => 0,
1723 Ordering::Greater => -1,
1724 Ordering::Less => 1,
1725 };
1726 let reverse_expected = match &expected {
1727 Ordering::Equal => Ordering::Equal,
1728 Ordering::Greater => Ordering::Less,
1729 Ordering::Less => Ordering::Greater,
1730 };
1731
1732 let reverse_ordering = version_b.cmp(&version_a);
1733 assert_eq!(
1734 reverse_ordering, reverse_expected,
1735 "Failed to compare '{version_a}' and '{version_b}'. Expected {expected:?} got {ordering:?}"
1736 );
1737
1738 assert_eq!(
1739 Version::vercmp(&version_b, &version_a),
1740 reverse_vercmp_result
1741 );
1742 }
1743
1744 #[rstest]
1746 #[case("<", VersionComparison::Less)]
1747 #[case("<=", VersionComparison::LessOrEqual)]
1748 #[case("=", VersionComparison::Equal)]
1749 #[case(">=", VersionComparison::GreaterOrEqual)]
1750 #[case(">", VersionComparison::Greater)]
1751 fn valid_version_comparison(#[case] comparison: &str, #[case] expected: VersionComparison) {
1752 assert_eq!(comparison.parse(), Ok(expected));
1753 }
1754
1755 #[rstest]
1757 #[case("", "invalid comparison operator")]
1758 #[case("<<", "invalid comparison operator")]
1759 #[case("==", "invalid comparison operator")]
1760 #[case("!=", "invalid comparison operator")]
1761 #[case(" =", "invalid comparison operator")]
1762 #[case("= ", "invalid comparison operator")]
1763 #[case("<1", "invalid comparison operator")]
1764 fn invalid_version_comparison(#[case] comparison: &str, #[case] err_snippet: &str) {
1765 let Err(Error::ParseError(err_msg)) = VersionComparison::from_str(comparison) else {
1766 panic!("'{comparison}' did not fail as expected")
1767 };
1768 assert!(
1769 err_msg.contains(err_snippet),
1770 "Error:\n=====\n{err_msg}\n=====\nshould contain snippet:\n\n{err_snippet}"
1771 );
1772 }
1773
1774 #[rstest]
1776 #[case("=1", VersionRequirement {
1777 comparison: VersionComparison::Equal,
1778 version: Version::from_str("1").unwrap(),
1779 })]
1780 #[case("<=42:abcd-2.4", VersionRequirement {
1781 comparison: VersionComparison::LessOrEqual,
1782 version: Version::from_str("42:abcd-2.4").unwrap(),
1783 })]
1784 #[case(">3.1", VersionRequirement {
1785 comparison: VersionComparison::Greater,
1786 version: Version::from_str("3.1").unwrap(),
1787 })]
1788 fn valid_version_requirement(#[case] requirement: &str, #[case] expected: VersionRequirement) {
1789 assert_eq!(
1790 requirement.parse(),
1791 Ok(expected),
1792 "Expected successful parse for version requirement '{requirement}'"
1793 );
1794 }
1795
1796 #[rstest]
1797 #[case::bad_operator("<>3.1", "invalid comparison operator")]
1798 #[case::no_operator("3.1", "expected version comparison operator")]
1799 #[case::arrow_operator("=>3.1", "invalid comparison operator")]
1800 #[case::no_version("<=", "expected pkgver string")]
1801 fn invalid_version_requirement(#[case] requirement: &str, #[case] err_snippet: &str) {
1802 let Err(Error::ParseError(err_msg)) = VersionRequirement::from_str(requirement) else {
1803 panic!("'{requirement}' erroneously parsed as VersionRequirement")
1804 };
1805 assert!(
1806 err_msg.contains(err_snippet),
1807 "Error:\n=====\n{err_msg}\n=====\nshould contain snippet:\n\n{err_snippet}"
1808 );
1809 }
1810
1811 #[rstest]
1812 #[case("<3.1>3.2", "invalid pkgver character")]
1813 fn invalid_version_requirement_pkgver_parse(
1814 #[case] requirement: &str,
1815 #[case] err_snippet: &str,
1816 ) {
1817 let Err(Error::ParseError(err_msg)) = VersionRequirement::from_str(requirement) else {
1818 panic!("'{requirement}' erroneously parsed as VersionRequirement")
1819 };
1820 assert!(
1821 err_msg.contains(err_snippet),
1822 "Error:\n=====\n{err_msg}\n=====\nshould contain snippet:\n\n{err_snippet}"
1823 );
1824 }
1825
1826 #[rstest]
1828 #[case("=1", "1", true)]
1829 #[case("=1", "1.0", false)]
1830 #[case("=1", "1-1", false)]
1831 #[case("=1", "1:1", false)]
1832 #[case("=1", "0.9", false)]
1833 #[case("<42", "41", true)]
1834 #[case("<42", "42", false)]
1835 #[case("<42", "43", false)]
1836 #[case("<=42", "41", true)]
1837 #[case("<=42", "42", true)]
1838 #[case("<=42", "43", false)]
1839 #[case(">42", "41", false)]
1840 #[case(">42", "42", false)]
1841 #[case(">42", "43", true)]
1842 #[case(">=42", "41", false)]
1843 #[case(">=42", "42", true)]
1844 #[case(">=42", "43", true)]
1845 fn version_requirement_satisfied(
1846 #[case] requirement: &str,
1847 #[case] version: &str,
1848 #[case] result: bool,
1849 ) {
1850 let requirement = VersionRequirement::from_str(requirement).unwrap();
1851 let version = Version::from_str(version).unwrap();
1852 assert_eq!(requirement.is_satisfied_by(&version), result);
1853 }
1854
1855 #[rstest]
1856 #[case("1.0.0", vec![
1857 VersionSegment::Segment{ text:"1", delimiter_count: 0},
1858 VersionSegment::Segment{ text:"0", delimiter_count: 1},
1859 VersionSegment::Segment{ text:"0", delimiter_count: 1},
1860 ])]
1861 #[case("1..0", vec![
1862 VersionSegment::Segment{ text:"1", delimiter_count: 0},
1863 VersionSegment::Segment{ text:"0", delimiter_count: 2},
1864 ])]
1865 #[case("1.0.", vec![
1866 VersionSegment::Segment{ text:"1", delimiter_count: 0},
1867 VersionSegment::Segment{ text:"0", delimiter_count: 1},
1868 VersionSegment::Segment{ text:"", delimiter_count: 1},
1869 ])]
1870 #[case("1..", vec![
1871 VersionSegment::Segment{ text:"1", delimiter_count: 0},
1872 VersionSegment::Segment{ text:"", delimiter_count: 2},
1873 ])]
1874 #[case("1...", vec![
1875 VersionSegment::Segment{ text:"1", delimiter_count: 0},
1876 VersionSegment::Segment{ text:"", delimiter_count: 3},
1877 ])]
1878 #[case("1.🗻lol.0", vec![
1879 VersionSegment::Segment{ text:"1", delimiter_count: 0},
1880 VersionSegment::Segment{ text:"lol", delimiter_count: 2},
1881 VersionSegment::Segment{ text:"0", delimiter_count: 1},
1882 ])]
1883 #[case("1.🗻lol.", vec![
1884 VersionSegment::Segment{ text:"1", delimiter_count: 0},
1885 VersionSegment::Segment{ text:"lol", delimiter_count: 2},
1886 VersionSegment::Segment{ text:"", delimiter_count: 1},
1887 ])]
1888 #[case("20220202", vec![
1889 VersionSegment::Segment{ text:"20220202", delimiter_count: 0},
1890 ])]
1891 #[case("some_string", vec![
1892 VersionSegment::Segment{ text:"some", delimiter_count: 0},
1893 VersionSegment::Segment{ text:"string", delimiter_count: 1}
1894 ])]
1895 #[case("alpha7654numeric321", vec![
1896 VersionSegment::Segment{ text:"alpha", delimiter_count: 0},
1897 VersionSegment::SubSegment{ text:"7654"},
1898 VersionSegment::SubSegment{ text:"numeric"},
1899 VersionSegment::SubSegment{ text:"321"},
1900 ])]
1901 fn version_segment_iterator(
1902 #[case] version: &str,
1903 #[case] expected_segments: Vec<VersionSegment>,
1904 ) {
1905 let version = PackageVersion(version.to_string());
1906 let mut segments_iter = version.segments();
1908 let mut expected_iter = expected_segments.clone().into_iter();
1909
1910 loop {
1913 let next_segment = segments_iter.next();
1914 assert_eq!(
1915 next_segment,
1916 expected_iter.next(),
1917 "Failed for segment {next_segment:?} in version string {version}:\nsegments: {:?}\n expected: {:?}",
1918 version.segments().collect::<Vec<VersionSegment>>(),
1919 expected_segments,
1920 );
1921 if next_segment.is_none() {
1922 break;
1923 }
1924 }
1925 }
1926}