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(Clone, Debug, 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
498#[derive(Debug)]
507pub struct VersionSegments<'a> {
508 version: &'a str,
511 version_chars: Peekable<CharIndices<'a>>,
513 in_segment: bool,
517}
518
519impl<'a> VersionSegments<'a> {
520 pub fn new(version: &'a str) -> Self {
522 VersionSegments {
523 version,
524 version_chars: version.char_indices().peekable(),
525 in_segment: false,
526 }
527 }
528}
529
530impl<'a> Iterator for VersionSegments<'a> {
531 type Item = VersionSegment<'a>;
532
533 fn next(&mut self) -> Option<VersionSegment<'a>> {
535 let mut delimiter_count = 0;
537
538 while let Some((_, char)) = self.version_chars.peek() {
541 if char.is_alphanumeric() {
543 break;
544 }
545
546 self.version_chars.next();
547 delimiter_count += 1;
548
549 self.in_segment = false;
551 continue;
552 }
553
554 let Some((first_index, first_char)) = self.version_chars.next() else {
556 if delimiter_count == 0 {
561 return None;
562 }
563
564 return Some(VersionSegment::Segment {
568 text: "",
569 delimiter_count,
570 });
571 };
572
573 let mut last_char = first_char;
576 let mut last_char_index = first_index;
577
578 let is_numeric = first_char.is_numeric();
583
584 if is_numeric {
585 #[allow(clippy::while_let_on_iterator)]
587 while let Some((index, next_char)) =
588 self.version_chars.next_if(|(_, peek)| peek.is_numeric())
589 {
590 last_char_index = index;
591 last_char = next_char;
592 }
593 } else {
594 #[allow(clippy::while_let_on_iterator)]
596 while let Some((index, next_char)) =
597 self.version_chars.next_if(|(_, peek)| peek.is_alphabetic())
598 {
599 last_char_index = index;
600 last_char = next_char;
601 }
602 }
603
604 let segment_slice = &self.version[first_index..(last_char_index + last_char.len_utf8())];
607
608 if !self.in_segment {
609 self.in_segment = true;
612 Some(VersionSegment::Segment {
613 text: segment_slice,
614 delimiter_count,
615 })
616 } else {
617 Some(VersionSegment::SubSegment {
618 text: segment_slice,
619 })
620 }
621 }
622}
623
624impl Ord for PackageVersion {
625 fn cmp(&self, other: &Self) -> Ordering {
632 if self.inner() == other.inner() {
634 return Ordering::Equal;
635 }
636
637 let mut self_segments = self.segments();
638 let mut other_segments = other.segments();
639
640 loop {
642 let self_segment = self_segments.next();
644 let other_segment = other_segments.next();
645
646 let (self_segment, other_segment) = match (self_segment, other_segment) {
648 (Some(self_seg), Some(other_seg)) => (self_seg, other_seg),
650
651 (None, None) => return Ordering::Equal,
653
654 (Some(seg), None) => {
684 let text = match seg {
687 VersionSegment::Segment { .. } => return Ordering::Greater,
688 VersionSegment::SubSegment { text } => text,
689 };
690
691 if !text.is_empty() && text.chars().all(char::is_alphabetic) {
694 return Ordering::Less;
695 }
696
697 return Ordering::Greater;
698 }
699
700 (None, Some(seg)) => {
702 let text = match seg {
703 VersionSegment::Segment { .. } => return Ordering::Less,
704 VersionSegment::SubSegment { text } => text,
705 };
706 if !text.is_empty() && text.chars().all(char::is_alphabetic) {
707 return Ordering::Greater;
708 }
709 if !text.is_empty() && text.chars().all(char::is_alphabetic) {
710 return Ordering::Greater;
711 }
712
713 return Ordering::Less;
714 }
715 };
716
717 if other_segment.is_empty() && self_segment.is_empty() {
726 return Ordering::Equal;
732 } else if self_segment.is_empty() {
733 if other_segment.chars().all(char::is_alphabetic) {
744 return Ordering::Greater;
745 }
746
747 return Ordering::Less;
751 } else if other_segment.is_empty() {
752 if self_segment.chars().all(char::is_alphabetic) {
754 return Ordering::Less;
755 }
756
757 return Ordering::Greater;
758 }
759
760 let (self_text, other_text) = match (self_segment, other_segment) {
772 (
773 VersionSegment::Segment {
774 delimiter_count: self_count,
775 text: self_text,
776 },
777 VersionSegment::Segment {
778 delimiter_count: other_count,
779 text: other_text,
780 },
781 ) => {
782 if self_count != other_count {
790 return self_count.cmp(&other_count);
791 }
792 (self_text, other_text)
793 }
794 (VersionSegment::Segment { .. }, VersionSegment::SubSegment { .. }) => {
801 return Ordering::Greater;
802 }
803 (VersionSegment::SubSegment { .. }, VersionSegment::Segment { .. }) => {
804 return Ordering::Less;
805 }
806 (
807 VersionSegment::SubSegment { text: self_text },
808 VersionSegment::SubSegment { text: other_text },
809 ) => (self_text, other_text),
810 };
811
812 let self_is_numeric = !self_text.is_empty() && self_text.chars().all(char::is_numeric);
821 let other_is_numeric =
822 !other_text.is_empty() && other_text.chars().all(char::is_numeric);
823
824 if self_is_numeric && !other_is_numeric {
825 return Ordering::Greater;
826 } else if !self_is_numeric && other_is_numeric {
827 return Ordering::Less;
828 } else if self_is_numeric && other_is_numeric {
829 let ordering = self_text
838 .parse::<usize>()
839 .unwrap()
840 .cmp(&other_text.parse::<usize>().unwrap());
841
842 match ordering {
843 Ordering::Less => return Ordering::Less,
844 Ordering::Greater => return Ordering::Greater,
845 Ordering::Equal => continue,
847 }
848 }
849
850 match self_text.cmp(other_text) {
853 Ordering::Less => return Ordering::Less,
854 Ordering::Greater => return Ordering::Greater,
855 Ordering::Equal => continue,
857 }
858 }
859 }
860}
861
862impl PartialOrd for PackageVersion {
863 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
864 Some(self.cmp(other))
865 }
866}
867
868impl PartialEq for PackageVersion {
869 fn eq(&self, other: &Self) -> bool {
870 self.cmp(other).is_eq()
871 }
872}
873
874#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
899pub struct SchemaVersion(SemverVersion);
900
901impl SchemaVersion {
902 pub fn new(version: SemverVersion) -> Self {
904 SchemaVersion(version)
905 }
906
907 pub fn inner(&self) -> &SemverVersion {
909 &self.0
910 }
911}
912
913impl FromStr for SchemaVersion {
914 type Err = Error;
915 fn from_str(s: &str) -> Result<SchemaVersion, Self::Err> {
920 if !s.contains('.') {
921 match s.parse() {
922 Ok(major) => Ok(SchemaVersion(SemverVersion::new(major, 0, 0))),
923 Err(e) => Err(Error::InvalidInteger {
924 kind: e.kind().clone(),
925 }),
926 }
927 } else {
928 match SemverVersion::parse(s) {
929 Ok(version) => Ok(SchemaVersion(version)),
930 Err(e) => Err(Error::InvalidSemver {
931 kind: e.to_string(),
932 }),
933 }
934 }
935 }
936}
937
938impl Display for SchemaVersion {
939 fn fmt(&self, fmt: &mut Formatter) -> std::fmt::Result {
940 write!(fmt, "{}", self.0)
941 }
942}
943
944#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
967pub struct Version {
968 pub pkgver: PackageVersion,
970 pub epoch: Option<Epoch>,
972 pub pkgrel: Option<PackageRelease>,
974}
975
976impl Version {
977 pub fn new(
979 pkgver: PackageVersion,
980 epoch: Option<Epoch>,
981 pkgrel: Option<PackageRelease>,
982 ) -> Self {
983 Version {
984 pkgver,
985 epoch,
986 pkgrel,
987 }
988 }
989
990 pub fn with_pkgrel(version: &str) -> Result<Self, Error> {
992 let version = Version::from_str(version)?;
993 if version.pkgrel.is_some() {
994 Ok(version)
995 } else {
996 Err(Error::MissingComponent {
997 component: "pkgrel",
998 })
999 }
1000 }
1001
1002 pub fn vercmp(a: &Version, b: &Version) -> i8 {
1034 match a.cmp(b) {
1035 Ordering::Less => -1,
1036 Ordering::Equal => 0,
1037 Ordering::Greater => 1,
1038 }
1039 }
1040
1041 pub fn parser(input: &mut &str) -> ModalResult<Self> {
1049 let mut epoch = opt(terminated(take_till(1.., ':'), ':').and_then(
1050 cut_err(Epoch::parser),
1052 ))
1053 .context(StrContext::Expected(StrContextValue::Description(
1054 "followed by a ':'",
1055 )));
1056
1057 seq!(Self {
1058 epoch: epoch,
1059 pkgver: take_till(1.., '-')
1060 .context(StrContext::Expected(StrContextValue::Description("pkgver string")))
1062 .and_then(PackageVersion::parser),
1063 pkgrel: opt(preceded('-', cut_err(PackageRelease::parser))),
1064 _: eof.context(StrContext::Expected(StrContextValue::Description("end of version string"))),
1065 })
1066 .parse_next(input)
1067 }
1068}
1069
1070impl FromStr for Version {
1071 type Err = Error;
1072 fn from_str(s: &str) -> Result<Version, Self::Err> {
1080 Ok(Self::parser.parse(s)?)
1081 }
1082}
1083
1084impl Display for Version {
1085 fn fmt(&self, fmt: &mut Formatter) -> std::fmt::Result {
1086 if let Some(epoch) = self.epoch {
1087 write!(fmt, "{epoch}:")?;
1088 }
1089
1090 write!(fmt, "{}", self.pkgver)?;
1091
1092 if let Some(pkgrel) = &self.pkgrel {
1093 write!(fmt, "-{pkgrel}")?;
1094 }
1095
1096 Ok(())
1097 }
1098}
1099
1100impl Ord for Version {
1101 fn cmp(&self, other: &Self) -> Ordering {
1102 match (self.epoch, other.epoch) {
1103 (Some(self_epoch), Some(other_epoch)) if self_epoch.cmp(&other_epoch).is_ne() => {
1104 return self_epoch.cmp(&other_epoch);
1105 }
1106 (Some(_), None) => return Ordering::Greater,
1107 (None, Some(_)) => return Ordering::Less,
1108 (_, _) => {}
1109 }
1110
1111 let pkgver_cmp = self.pkgver.cmp(&other.pkgver);
1112 if pkgver_cmp.is_ne() {
1113 return pkgver_cmp;
1114 }
1115
1116 self.pkgrel.cmp(&other.pkgrel)
1117 }
1118}
1119
1120impl PartialOrd for Version {
1121 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1122 Some(self.cmp(other))
1123 }
1124}
1125
1126#[derive(
1149 strum::AsRefStr,
1150 Clone,
1151 Copy,
1152 Debug,
1153 strum::Display,
1154 strum::EnumIter,
1155 PartialEq,
1156 Eq,
1157 strum::VariantNames,
1158 Serialize,
1159 Deserialize,
1160)]
1161pub enum VersionComparison {
1162 #[strum(to_string = "<=")]
1164 LessOrEqual,
1165
1166 #[strum(to_string = ">=")]
1168 GreaterOrEqual,
1169
1170 #[strum(to_string = "=")]
1172 Equal,
1173
1174 #[strum(to_string = "<")]
1176 Less,
1177
1178 #[strum(to_string = ">")]
1180 Greater,
1181}
1182
1183impl VersionComparison {
1184 fn is_compatible_with(self, ord: Ordering) -> bool {
1187 match (self, ord) {
1188 (VersionComparison::Less, Ordering::Less)
1189 | (VersionComparison::LessOrEqual, Ordering::Less | Ordering::Equal)
1190 | (VersionComparison::Equal, Ordering::Equal)
1191 | (VersionComparison::GreaterOrEqual, Ordering::Greater | Ordering::Equal)
1192 | (VersionComparison::Greater, Ordering::Greater) => true,
1193
1194 (VersionComparison::Less, Ordering::Equal | Ordering::Greater)
1195 | (VersionComparison::LessOrEqual, Ordering::Greater)
1196 | (VersionComparison::Equal, Ordering::Less | Ordering::Greater)
1197 | (VersionComparison::GreaterOrEqual, Ordering::Less)
1198 | (VersionComparison::Greater, Ordering::Less | Ordering::Equal) => false,
1199 }
1200 }
1201
1202 pub fn parser(input: &mut &str) -> ModalResult<Self> {
1210 alt((
1211 ("<=", eof).value(Self::LessOrEqual),
1213 (">=", eof).value(Self::GreaterOrEqual),
1214 ("=", eof).value(Self::Equal),
1215 ("<", eof).value(Self::Less),
1216 (">", eof).value(Self::Greater),
1217 fail.context(StrContext::Label("comparison operator"))
1218 .context_with(iter_str_context!([VersionComparison::VARIANTS])),
1219 ))
1220 .parse_next(input)
1221 }
1222}
1223
1224impl FromStr for VersionComparison {
1225 type Err = Error;
1226
1227 fn from_str(s: &str) -> Result<Self, Self::Err> {
1235 Ok(Self::parser.parse(s)?)
1236 }
1237}
1238
1239#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1263pub struct VersionRequirement {
1264 pub comparison: VersionComparison,
1266 pub version: Version,
1268}
1269
1270impl VersionRequirement {
1271 pub fn new(comparison: VersionComparison, version: Version) -> Self {
1273 VersionRequirement {
1274 comparison,
1275 version,
1276 }
1277 }
1278
1279 pub fn is_satisfied_by(&self, ver: &Version) -> bool {
1300 self.comparison.is_compatible_with(ver.cmp(&self.version))
1301 }
1302
1303 pub fn parser(input: &mut &str) -> ModalResult<Self> {
1311 seq!(Self {
1312 comparison: take_while(1.., ('<', '>', '='))
1313 .context(StrContext::Expected(StrContextValue::Description(
1315 "version comparison operator"
1316 )))
1317 .and_then(VersionComparison::parser),
1318 version: Version::parser,
1319 })
1320 .parse_next(input)
1321 }
1322}
1323
1324impl Display for VersionRequirement {
1325 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1326 write!(f, "{}{}", self.comparison, self.version)
1327 }
1328}
1329
1330impl FromStr for VersionRequirement {
1331 type Err = Error;
1332
1333 fn from_str(s: &str) -> Result<Self, Self::Err> {
1341 Ok(Self::parser.parse(s)?)
1342 }
1343}
1344
1345#[cfg(test)]
1346mod tests {
1347 use rstest::rstest;
1348
1349 use super::*;
1350
1351 #[rstest]
1352 #[case("1.0.0", Ok(SchemaVersion(SemverVersion::new(1, 0, 0))))]
1353 #[case("1", Ok(SchemaVersion(SemverVersion::new(1, 0, 0))))]
1354 #[case("-1.0.0", Err(Error::InvalidSemver { kind: String::from("unexpected character '-' while parsing major version number") }))]
1355 fn schema_version(#[case] version: &str, #[case] result: Result<SchemaVersion, Error>) {
1356 assert_eq!(result, SchemaVersion::from_str(version))
1357 }
1358
1359 #[rstest]
1361 #[case(
1362 "1.0.0-1-any",
1363 BuildToolVersion::new(Version::from_str("1.0.0-1").unwrap(), Some(Architecture::from_str("any").unwrap())),
1364 )]
1365 #[case(
1366 "1:1.0.0-1-any",
1367 BuildToolVersion::new(Version::from_str("1:1.0.0-1").unwrap(), Some(Architecture::from_str("any").unwrap())),
1368 )]
1369 #[case(
1370 "1.0.0",
1371 BuildToolVersion::new(Version::from_str("1.0.0").unwrap(), None),
1372 )]
1373 fn valid_buildtoolver_new(#[case] buildtoolver: &str, #[case] expected: BuildToolVersion) {
1374 assert_eq!(
1375 BuildToolVersion::from_str(buildtoolver),
1376 Ok(expected),
1377 "Expected valid parse of buildtoolver '{buildtoolver}'"
1378 );
1379 }
1380
1381 #[rstest]
1383 #[case("1.0.0-any", Error::MissingComponent { component: "pkgrel" })]
1384 #[case("1.0.0-1-foo", strum::ParseError::VariantNotFound.into())]
1385 fn invalid_buildtoolver_new(#[case] buildtoolver: &str, #[case] expected: Error) {
1386 assert_eq!(
1387 BuildToolVersion::from_str(buildtoolver),
1388 Err(expected),
1389 "Expected error during parse of buildtoolver '{buildtoolver}'"
1390 );
1391 }
1392
1393 #[rstest]
1394 #[case(".1.0.0-1-any", "invalid first pkgver character")]
1395 fn invalid_buildtoolver_badpkgver(#[case] buildtoolver: &str, #[case] err_snippet: &str) {
1396 let Err(Error::ParseError(err_msg)) = BuildToolVersion::from_str(buildtoolver) else {
1397 panic!("'{buildtoolver}' erroneously parsed as BuildToolVersion")
1398 };
1399 assert!(
1400 err_msg.contains(err_snippet),
1401 "Error:\n=====\n{err_msg}\n=====\nshould contain snippet:\n\n{err_snippet}"
1402 );
1403 }
1404
1405 #[rstest]
1406 #[case(
1407 SchemaVersion(SemverVersion::new(1, 0, 0)),
1408 SchemaVersion(SemverVersion::new(0, 1, 0))
1409 )]
1410 fn compare_schema_version(#[case] version_a: SchemaVersion, #[case] version_b: SchemaVersion) {
1411 assert!(version_a > version_b);
1412 }
1413
1414 #[rstest]
1416 #[case(
1417 "foo",
1418 Version {
1419 epoch: None,
1420 pkgver: PackageVersion::new("foo".to_string()).unwrap(),
1421 pkgrel: None
1422 },
1423 )]
1424 #[case(
1425 "1:foo-1",
1426 Version {
1427 pkgver: PackageVersion::new("foo".to_string()).unwrap(),
1428 epoch: Some(Epoch::new(NonZeroUsize::new(1).unwrap())),
1429 pkgrel: Some(PackageRelease::new(1, None))
1430 },
1431 )]
1432 #[case(
1433 "1:foo",
1434 Version {
1435 pkgver: PackageVersion::new("foo".to_string()).unwrap(),
1436 epoch: Some(Epoch::new(NonZeroUsize::new(1).unwrap())),
1437 pkgrel: None,
1438 },
1439 )]
1440 #[case(
1441 "foo-1",
1442 Version {
1443 pkgver: PackageVersion::new("foo".to_string()).unwrap(),
1444 epoch: None,
1445 pkgrel: Some(PackageRelease::new(1, None))
1446 }
1447 )]
1448 fn valid_version_from_string(#[case] version: &str, #[case] expected: Version) {
1449 assert_eq!(
1450 Version::from_str(version),
1451 Ok(expected),
1452 "Expected valid parsing for version {version}"
1453 )
1454 }
1455
1456 #[rstest]
1458 #[case::two_pkgrel("1:foo-1-1", "expected end of package release value")]
1459 #[case::two_epoch("1:1:foo-1", "invalid pkgver character")]
1460 #[case::no_version("", "expected pkgver string")]
1461 #[case::no_version(":", "invalid first pkgver character")]
1462 #[case::no_version(".", "invalid first pkgver character")]
1463 #[case::invalid_integer(
1464 "-1foo:1",
1465 "invalid package epoch\nexpected positive non-zero decimal integer, followed by a ':'"
1466 )]
1467 #[case::invalid_integer(
1468 "1-foo:1",
1469 "invalid package epoch\nexpected positive non-zero decimal integer, followed by a ':'"
1470 )]
1471 fn parse_error_in_version_from_string(#[case] version: &str, #[case] err_snippet: &str) {
1472 let Err(Error::ParseError(err_msg)) = Version::from_str(version) else {
1473 panic!("parsing '{version}' did not fail as expected")
1474 };
1475 assert!(
1476 err_msg.contains(err_snippet),
1477 "Error:\n=====\n{err_msg}\n=====\nshould contain snippet:\n\n{err_snippet}"
1478 );
1479 }
1480
1481 #[rstest]
1484 #[case(
1485 "1.0.0-1",
1486 Ok(Version{
1487 pkgver: PackageVersion::new("1.0.0".to_string()).unwrap(),
1488 pkgrel: Some(PackageRelease::new(1, None)),
1489 epoch: None,
1490 })
1491 )]
1492 #[case("1.0.0", Err(Error::MissingComponent { component: "pkgrel" }))]
1493 fn version_with_pkgrel(#[case] version: &str, #[case] result: Result<Version, Error>) {
1494 assert_eq!(result, Version::with_pkgrel(version));
1495 }
1496
1497 #[rstest]
1498 #[case("1", Ok(Epoch(NonZeroUsize::new(1).unwrap())))]
1499 fn epoch(#[case] version: &str, #[case] result: Result<Epoch, Error>) {
1500 assert_eq!(result, Epoch::from_str(version));
1501 }
1502
1503 #[rstest]
1504 #[case("0", "expected positive non-zero decimal integer")]
1505 #[case("-0", "expected positive non-zero decimal integer")]
1506 #[case("z", "expected positive non-zero decimal integer")]
1507 fn epoch_parse_failure(#[case] input: &str, #[case] err_snippet: &str) {
1508 let Err(Error::ParseError(err_msg)) = Epoch::from_str(input) else {
1509 panic!("'{input}' erroneously parsed as Epoch")
1510 };
1511 assert!(
1512 err_msg.contains(err_snippet),
1513 "Error:\n=====\n{err_msg}\n=====\nshould contain snippet:\n\n{err_snippet}"
1514 );
1515 }
1516
1517 #[rstest]
1519 #[case("foo")]
1520 #[case("1.0.0")]
1521 fn valid_pkgver(#[case] pkgver: &str) {
1522 let parsed = PackageVersion::new(pkgver.to_string());
1523 assert!(parsed.is_ok(), "Expected pkgver {pkgver} to be valid.");
1524 assert_eq!(
1525 parsed.as_ref().unwrap().to_string(),
1526 pkgver,
1527 "Expected parsed PackageVersion representation '{}' to be identical to input '{}'",
1528 parsed.unwrap(),
1529 pkgver
1530 );
1531 }
1532
1533 #[rstest]
1535 #[case("1:foo", "invalid pkgver character")]
1536 #[case("foo-1", "invalid pkgver character")]
1537 #[case("foo,1", "invalid pkgver character")]
1538 #[case(".foo", "invalid first pkgver character")]
1539 #[case("_foo", "invalid first pkgver character")]
1540 #[case("ß", "invalid first pkgver character")]
1542 #[case("1.ß", "invalid pkgver character")]
1543 fn invalid_pkgver(#[case] pkgver: &str, #[case] err_snippet: &str) {
1544 let Err(Error::ParseError(err_msg)) = PackageVersion::new(pkgver.to_string()) else {
1545 panic!("Expected pkgver {pkgver} to be invalid.")
1546 };
1547 assert!(
1548 err_msg.contains(err_snippet),
1549 "Error:\n=====\n{err_msg}\n=====\nshould contain snippet:\n\n{err_snippet}"
1550 );
1551 }
1552
1553 #[rstest]
1555 #[case("0")]
1556 #[case("1")]
1557 #[case("10")]
1558 #[case("1.0")]
1559 #[case("10.5")]
1560 #[case("0.1")]
1561 fn valid_pkgrel(#[case] pkgrel: &str) {
1562 let parsed = PackageRelease::from_str(pkgrel);
1563 assert!(parsed.is_ok(), "Expected pkgrel {pkgrel} to be valid.");
1564 assert_eq!(
1565 parsed.as_ref().unwrap().to_string(),
1566 pkgrel,
1567 "Expected parsed PackageRelease representation '{}' to be identical to input '{}'",
1568 parsed.unwrap(),
1569 pkgrel
1570 );
1571 }
1572
1573 #[rstest]
1575 #[case(".1", "expected positive decimal integer")]
1576 #[case("1.", "expected single '.' followed by positive decimal integer")]
1577 #[case("1..1", "expected single '.' followed by positive decimal integer")]
1578 #[case("-1", "expected positive decimal integer")]
1579 #[case("a", "expected positive decimal integer")]
1580 #[case("1.a", "expected single '.' followed by positive decimal integer")]
1581 #[case("1.0.0", "expected end of package release")]
1582 #[case("", "expected positive decimal integer")]
1583 fn invalid_pkgrel(#[case] pkgrel: &str, #[case] err_snippet: &str) {
1584 let Err(Error::ParseError(err_msg)) = PackageRelease::from_str(pkgrel) else {
1585 panic!("'{pkgrel}' erroneously parsed as PackageRelease")
1586 };
1587 assert!(
1588 err_msg.contains(err_snippet),
1589 "Error:\n=====\n{err_msg}\n=====\nshould contain snippet:\n\n{err_snippet}"
1590 );
1591 }
1592
1593 #[rstest]
1595 #[case("1", "1.0", Ordering::Less)]
1596 #[case("1.0", "2", Ordering::Less)]
1597 #[case("1", "1.1", Ordering::Less)]
1598 #[case("1.0", "1.1", Ordering::Less)]
1599 #[case("0", "1.1", Ordering::Less)]
1600 #[case("1", "11", Ordering::Less)]
1601 #[case("1", "1", Ordering::Equal)]
1602 #[case("1.2", "1.2", Ordering::Equal)]
1603 #[case("2.0", "2.0", Ordering::Equal)]
1604 #[case("2", "1.0", Ordering::Greater)]
1605 #[case("1.1", "1", Ordering::Greater)]
1606 #[case("1.1", "1.0", Ordering::Greater)]
1607 #[case("1.1", "0", Ordering::Greater)]
1608 #[case("11", "1", Ordering::Greater)]
1609 fn pkgrel_cmp(#[case] first: &str, #[case] second: &str, #[case] order: Ordering) {
1610 let first = PackageRelease::from_str(first).unwrap();
1611 let second = PackageRelease::from_str(second).unwrap();
1612 assert_eq!(
1613 first.cmp(&second),
1614 order,
1615 "{first} should be {order:?} to {second}"
1616 );
1617 }
1618
1619 #[rstest]
1621 #[case(Version::from_str("1:1-1").unwrap(), "1:1-1")]
1622 #[case(Version::from_str("1-1").unwrap(), "1-1")]
1623 #[case(Version::from_str("1").unwrap(), "1")]
1624 #[case(Version::from_str("1:1").unwrap(), "1:1")]
1625 fn version_to_string(#[case] version: Version, #[case] to_str: &str) {
1626 assert_eq!(format!("{version}"), to_str);
1627 }
1628
1629 #[rstest]
1630 #[case(Version::from_str("1"), Version::from_str("1"), Ordering::Equal)]
1632 #[case(Version::from_str("1"), Version::from_str("2"), Ordering::Less)]
1633 #[case(
1634 Version::from_str("20220102"),
1635 Version::from_str("20220202"),
1636 Ordering::Less
1637 )]
1638 #[case(Version::from_str("1"), Version::from_str("1.1"), Ordering::Less)]
1640 #[case(Version::from_str("01"), Version::from_str("1"), Ordering::Equal)]
1641 #[case(Version::from_str("001a"), Version::from_str("1a"), Ordering::Equal)]
1642 #[case(Version::from_str("a1a"), Version::from_str("a1b"), Ordering::Less)]
1643 #[case(Version::from_str("foo"), Version::from_str("1.1"), Ordering::Less)]
1644 #[case(Version::from_str("1.0"), Version::from_str("1..0"), Ordering::Less)]
1646 #[case(Version::from_str("1.1"), Version::from_str("1.1"), Ordering::Equal)]
1647 #[case(Version::from_str("1.1"), Version::from_str("1.2"), Ordering::Less)]
1648 #[case(Version::from_str("1..0"), Version::from_str("1..0"), Ordering::Equal)]
1649 #[case(Version::from_str("1..0"), Version::from_str("1..1"), Ordering::Less)]
1650 #[case(Version::from_str("1+0"), Version::from_str("1.0"), Ordering::Equal)]
1651 #[case(Version::from_str("1+1"), Version::from_str("1+2"), Ordering::Less)]
1652 #[case(Version::from_str("1.1"), Version::from_str("1.1.a"), Ordering::Less)]
1654 #[case(Version::from_str("1.1"), Version::from_str("1.11a"), Ordering::Less)]
1655 #[case(Version::from_str("1.1"), Version::from_str("1.1_a"), Ordering::Less)]
1656 #[case(Version::from_str("1.1a"), Version::from_str("1.1"), Ordering::Less)]
1657 #[case(Version::from_str("1.1a1"), Version::from_str("1.1"), Ordering::Less)]
1658 #[case(Version::from_str("1.a"), Version::from_str("1.1"), Ordering::Less)]
1659 #[case(Version::from_str("1.a"), Version::from_str("1.alpha"), Ordering::Less)]
1660 #[case(Version::from_str("1.a1"), Version::from_str("1.1"), Ordering::Less)]
1661 #[case(Version::from_str("1.a11"), Version::from_str("1.1"), Ordering::Less)]
1662 #[case(Version::from_str("1.a1a"), Version::from_str("1.a1"), Ordering::Less)]
1663 #[case(Version::from_str("1.alpha"), Version::from_str("1.b"), Ordering::Less)]
1664 #[case(Version::from_str("a.1"), Version::from_str("1.1"), Ordering::Less)]
1665 #[case(
1666 Version::from_str("1.alpha0.0"),
1667 Version::from_str("1.alpha.0"),
1668 Ordering::Less
1669 )]
1670 #[case(Version::from_str("1.0"), Version::from_str("1.0."), Ordering::Less)]
1672 #[case(Version::from_str("1.0."), Version::from_str("1.0.0"), Ordering::Less)]
1674 #[case(Version::from_str("1.0.."), Version::from_str("1.0."), Ordering::Equal)]
1675 #[case(
1676 Version::from_str("1.0.alpha.0"),
1677 Version::from_str("1.0."),
1678 Ordering::Less
1679 )]
1680 #[case(
1681 Version::from_str("1.a001a.1"),
1682 Version::from_str("1.a1a.1"),
1683 Ordering::Equal
1684 )]
1685 fn version_cmp(
1686 #[case] version_a: Result<Version, Error>,
1687 #[case] version_b: Result<Version, Error>,
1688 #[case] expected: Ordering,
1689 ) {
1690 let version_a = version_a.unwrap();
1692 let version_b = version_b.unwrap();
1693
1694 let vercmp_result = match &expected {
1696 Ordering::Equal => 0,
1697 Ordering::Greater => 1,
1698 Ordering::Less => -1,
1699 };
1700
1701 let ordering = version_a.cmp(&version_b);
1702 assert_eq!(
1703 ordering, expected,
1704 "Failed to compare '{version_a}' and '{version_b}'. Expected {expected:?} got {ordering:?}"
1705 );
1706
1707 assert_eq!(Version::vercmp(&version_a, &version_b), vercmp_result);
1708
1709 #[cfg(feature = "compatibility_tests")]
1711 {
1712 let output = std::process::Command::new("vercmp")
1713 .arg(version_a.to_string())
1714 .arg(version_b.to_string())
1715 .output()
1716 .unwrap();
1717 let result = String::from_utf8_lossy(&output.stdout);
1718 assert_eq!(result.trim(), vercmp_result.to_string());
1719 }
1720
1721 let reverse_vercmp_result = match &expected {
1723 Ordering::Equal => 0,
1724 Ordering::Greater => -1,
1725 Ordering::Less => 1,
1726 };
1727 let reverse_expected = match &expected {
1728 Ordering::Equal => Ordering::Equal,
1729 Ordering::Greater => Ordering::Less,
1730 Ordering::Less => Ordering::Greater,
1731 };
1732
1733 let reverse_ordering = version_b.cmp(&version_a);
1734 assert_eq!(
1735 reverse_ordering, reverse_expected,
1736 "Failed to compare '{version_a}' and '{version_b}'. Expected {expected:?} got {ordering:?}"
1737 );
1738
1739 assert_eq!(
1740 Version::vercmp(&version_b, &version_a),
1741 reverse_vercmp_result
1742 );
1743 }
1744
1745 #[rstest]
1747 #[case("<", VersionComparison::Less)]
1748 #[case("<=", VersionComparison::LessOrEqual)]
1749 #[case("=", VersionComparison::Equal)]
1750 #[case(">=", VersionComparison::GreaterOrEqual)]
1751 #[case(">", VersionComparison::Greater)]
1752 fn valid_version_comparison(#[case] comparison: &str, #[case] expected: VersionComparison) {
1753 assert_eq!(comparison.parse(), Ok(expected));
1754 }
1755
1756 #[rstest]
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("= ", "invalid comparison operator")]
1764 #[case("<1", "invalid comparison operator")]
1765 fn invalid_version_comparison(#[case] comparison: &str, #[case] err_snippet: &str) {
1766 let Err(Error::ParseError(err_msg)) = VersionComparison::from_str(comparison) else {
1767 panic!("'{comparison}' did not fail as expected")
1768 };
1769 assert!(
1770 err_msg.contains(err_snippet),
1771 "Error:\n=====\n{err_msg}\n=====\nshould contain snippet:\n\n{err_snippet}"
1772 );
1773 }
1774
1775 #[rstest]
1777 #[case("=1", VersionRequirement {
1778 comparison: VersionComparison::Equal,
1779 version: Version::from_str("1").unwrap(),
1780 })]
1781 #[case("<=42:abcd-2.4", VersionRequirement {
1782 comparison: VersionComparison::LessOrEqual,
1783 version: Version::from_str("42:abcd-2.4").unwrap(),
1784 })]
1785 #[case(">3.1", VersionRequirement {
1786 comparison: VersionComparison::Greater,
1787 version: Version::from_str("3.1").unwrap(),
1788 })]
1789 fn valid_version_requirement(#[case] requirement: &str, #[case] expected: VersionRequirement) {
1790 assert_eq!(
1791 requirement.parse(),
1792 Ok(expected),
1793 "Expected successful parse for version requirement '{requirement}'"
1794 );
1795 }
1796
1797 #[rstest]
1798 #[case::bad_operator("<>3.1", "invalid comparison operator")]
1799 #[case::no_operator("3.1", "expected version comparison operator")]
1800 #[case::arrow_operator("=>3.1", "invalid comparison operator")]
1801 #[case::no_version("<=", "expected pkgver string")]
1802 fn invalid_version_requirement(#[case] requirement: &str, #[case] err_snippet: &str) {
1803 let Err(Error::ParseError(err_msg)) = VersionRequirement::from_str(requirement) else {
1804 panic!("'{requirement}' erroneously parsed as VersionRequirement")
1805 };
1806 assert!(
1807 err_msg.contains(err_snippet),
1808 "Error:\n=====\n{err_msg}\n=====\nshould contain snippet:\n\n{err_snippet}"
1809 );
1810 }
1811
1812 #[rstest]
1813 #[case("<3.1>3.2", "invalid pkgver character")]
1814 fn invalid_version_requirement_pkgver_parse(
1815 #[case] requirement: &str,
1816 #[case] err_snippet: &str,
1817 ) {
1818 let Err(Error::ParseError(err_msg)) = VersionRequirement::from_str(requirement) else {
1819 panic!("'{requirement}' erroneously parsed as VersionRequirement")
1820 };
1821 assert!(
1822 err_msg.contains(err_snippet),
1823 "Error:\n=====\n{err_msg}\n=====\nshould contain snippet:\n\n{err_snippet}"
1824 );
1825 }
1826
1827 #[rstest]
1829 #[case("=1", "1", true)]
1830 #[case("=1", "1.0", false)]
1831 #[case("=1", "1-1", false)]
1832 #[case("=1", "1:1", false)]
1833 #[case("=1", "0.9", false)]
1834 #[case("<42", "41", true)]
1835 #[case("<42", "42", false)]
1836 #[case("<42", "43", false)]
1837 #[case("<=42", "41", true)]
1838 #[case("<=42", "42", true)]
1839 #[case("<=42", "43", false)]
1840 #[case(">42", "41", false)]
1841 #[case(">42", "42", false)]
1842 #[case(">42", "43", true)]
1843 #[case(">=42", "41", false)]
1844 #[case(">=42", "42", true)]
1845 #[case(">=42", "43", true)]
1846 fn version_requirement_satisfied(
1847 #[case] requirement: &str,
1848 #[case] version: &str,
1849 #[case] result: bool,
1850 ) {
1851 let requirement = VersionRequirement::from_str(requirement).unwrap();
1852 let version = Version::from_str(version).unwrap();
1853 assert_eq!(requirement.is_satisfied_by(&version), result);
1854 }
1855
1856 #[rstest]
1857 #[case("1.0.0", vec![
1858 VersionSegment::Segment{ text:"1", delimiter_count: 0},
1859 VersionSegment::Segment{ text:"0", delimiter_count: 1},
1860 VersionSegment::Segment{ text:"0", delimiter_count: 1},
1861 ])]
1862 #[case("1..0", vec![
1863 VersionSegment::Segment{ text:"1", delimiter_count: 0},
1864 VersionSegment::Segment{ text:"0", delimiter_count: 2},
1865 ])]
1866 #[case("1.0.", vec![
1867 VersionSegment::Segment{ text:"1", delimiter_count: 0},
1868 VersionSegment::Segment{ text:"0", delimiter_count: 1},
1869 VersionSegment::Segment{ text:"", delimiter_count: 1},
1870 ])]
1871 #[case("1..", vec![
1872 VersionSegment::Segment{ text:"1", delimiter_count: 0},
1873 VersionSegment::Segment{ text:"", delimiter_count: 2},
1874 ])]
1875 #[case("1...", vec![
1876 VersionSegment::Segment{ text:"1", delimiter_count: 0},
1877 VersionSegment::Segment{ text:"", delimiter_count: 3},
1878 ])]
1879 #[case("1.🗻lol.0", vec![
1880 VersionSegment::Segment{ text:"1", delimiter_count: 0},
1881 VersionSegment::Segment{ text:"lol", delimiter_count: 2},
1882 VersionSegment::Segment{ text:"0", delimiter_count: 1},
1883 ])]
1884 #[case("1.🗻lol.", vec![
1885 VersionSegment::Segment{ text:"1", delimiter_count: 0},
1886 VersionSegment::Segment{ text:"lol", delimiter_count: 2},
1887 VersionSegment::Segment{ text:"", delimiter_count: 1},
1888 ])]
1889 #[case("20220202", vec![
1890 VersionSegment::Segment{ text:"20220202", delimiter_count: 0},
1891 ])]
1892 #[case("some_string", vec![
1893 VersionSegment::Segment{ text:"some", delimiter_count: 0},
1894 VersionSegment::Segment{ text:"string", delimiter_count: 1}
1895 ])]
1896 #[case("alpha7654numeric321", vec![
1897 VersionSegment::Segment{ text:"alpha", delimiter_count: 0},
1898 VersionSegment::SubSegment{ text:"7654"},
1899 VersionSegment::SubSegment{ text:"numeric"},
1900 VersionSegment::SubSegment{ text:"321"},
1901 ])]
1902 fn version_segment_iterator(
1903 #[case] version: &str,
1904 #[case] expected_segments: Vec<VersionSegment>,
1905 ) {
1906 let version = PackageVersion(version.to_string());
1907 let mut segments_iter = version.segments();
1909 let mut expected_iter = expected_segments.clone().into_iter();
1910
1911 loop {
1914 let next_segment = segments_iter.next();
1915 assert_eq!(
1916 next_segment,
1917 expected_iter.next(),
1918 "Failed for segment {next_segment:?} in version string {version}:\nsegments: {:?}\n expected: {:?}",
1919 version.segments().collect::<Vec<VersionSegment>>(),
1920 expected_segments,
1921 );
1922 if next_segment.is_none() {
1923 break;
1924 }
1925 }
1926 }
1927}