1use std::{
2 cmp::{self, Ordering},
3 fmt::Display,
4 str::FromStr,
5};
6
7use html_escape::decode_html_entities;
8use itertools::Itertools;
9
10use miette::Diagnostic;
11use nonempty::NonEmpty;
12use semver::{Comparator, Error, Op, Prerelease, Version, VersionReq};
13use serde::{de, Deserialize, Deserializer, Serialize};
14use thiserror::Error;
15#[derive(Debug, Error, Diagnostic)]
16pub enum VersionReqToVersionError {
17 #[error("cannot parse version from non-exact version requirement '{0}'")]
18 NonExactVersionReq(VersionReq),
19 #[error("cannot parse version from version requirement '*' (any version)")]
20 Any,
21}
22
23#[derive(Clone, Eq, PartialEq, Hash, Debug)]
24pub enum PackageVersion {
25 SemVer(SemVer),
28 DevVer(DevVer),
30 StringVer(StringVer),
34}
35
36impl HasModRev for PackageVersion {
37 fn to_modrev_string(&self) -> String {
38 match self {
39 Self::SemVer(ver) => ver.to_modrev_string(),
40 Self::DevVer(ver) => ver.to_modrev_string(),
41 Self::StringVer(ver) => ver.to_modrev_string(),
42 }
43 }
44}
45
46impl PackageVersion {
47 pub fn parse(text: &str) -> Result<Self, PackageVersionParseError> {
48 PackageVersion::from_str(text)
49 }
50 pub fn into_version_req(&self) -> PackageVersionReq {
52 match self {
53 PackageVersion::DevVer(DevVer { modrev, .. }) => {
54 PackageVersionReq::DevVer(modrev.to_owned())
55 }
56 PackageVersion::StringVer(StringVer { modrev, .. }) => {
57 PackageVersionReq::StringVer(modrev.to_owned())
58 }
59 PackageVersion::SemVer(SemVer { version, .. }) => {
60 let version = version.to_owned();
61 PackageVersionReq::SemVer(VersionReq {
62 comparators: vec![Comparator {
63 op: Op::Exact,
64 major: version.major,
65 minor: Some(version.minor),
66 patch: Some(version.patch),
67 pre: version.pre,
68 }],
69 })
70 }
71 }
72 }
73
74 pub(crate) fn is_semver(&self) -> bool {
75 matches!(self, PackageVersion::SemVer(_))
76 }
77
78 pub(crate) fn default_dev_version() -> Self {
79 Self::DevVer(DevVer::default())
80 }
81
82 pub(crate) fn default_dev_version_with_specrev(specrev: SpecRev) -> Self {
83 Self::DevVer(DevVer::default_with_specrev(specrev))
84 }
85}
86
87impl TryFrom<PackageVersionReq> for PackageVersion {
88 type Error = VersionReqToVersionError;
89
90 fn try_from(req: PackageVersionReq) -> Result<Self, Self::Error> {
91 match req {
92 PackageVersionReq::SemVer(version_req) => {
93 match NonEmpty::try_from(version_req.comparators.clone()) {
94 Ok(comparators)
95 if comparators
96 .iter()
97 .any(|comparator| comparator.op != semver::Op::Exact) =>
98 {
99 Err(VersionReqToVersionError::NonExactVersionReq(version_req))
100 }
101 Ok(comparators) => {
102 let comparator = comparators.first();
103 let version = semver::Version {
104 major: comparator.major,
105 minor: comparator.minor.unwrap_or(0),
106 patch: comparator.patch.unwrap_or(0),
107 pre: comparator.pre.clone(),
108 build: semver::BuildMetadata::EMPTY,
109 };
110 let component_count = if comparator.patch.is_some() {
111 3
112 } else if comparator.minor.is_some() {
113 2
114 } else {
115 1
116 };
117 Ok(PackageVersion::SemVer(SemVer {
118 version,
119 component_count,
120 specrev: 1.into(),
121 }))
122 }
123 Err(_) => Err(VersionReqToVersionError::NonExactVersionReq(version_req)),
124 }
125 }
126 PackageVersionReq::DevVer(modrev) => Ok(PackageVersion::DevVer(DevVer {
127 modrev,
128 specrev: 1.into(),
129 })),
130 PackageVersionReq::StringVer(modrev) => Ok(PackageVersion::StringVer(StringVer {
131 modrev,
132 specrev: 1.into(),
133 })),
134 PackageVersionReq::Any => Err(VersionReqToVersionError::Any),
135 }
136 }
137}
138
139#[derive(Error, Debug, Diagnostic)]
140#[non_exhaustive]
141pub enum PackageVersionParseError {
142 #[error(transparent)]
143 #[diagnostic(transparent)]
144 Specrev(#[from] SpecrevParseError),
145 #[error("failed to parse version")]
146 Version(#[from] Error),
147}
148
149impl Serialize for PackageVersion {
150 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
151 where
152 S: serde::Serializer,
153 {
154 match self {
155 PackageVersion::SemVer(version) => version.serialize(serializer),
156 PackageVersion::DevVer(version) => version.serialize(serializer),
157 PackageVersion::StringVer(version) => version.serialize(serializer),
158 }
159 }
160}
161
162impl<'de> Deserialize<'de> for PackageVersion {
163 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
164 where
165 D: Deserializer<'de>,
166 {
167 let s = String::deserialize(deserializer)?;
168 Self::from_str(&s).map_err(de::Error::custom)
169 }
170}
171
172impl Display for PackageVersion {
173 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
174 match self {
175 PackageVersion::SemVer(version) => version.fmt(f),
176 PackageVersion::DevVer(version) => version.fmt(f),
177 PackageVersion::StringVer(version) => version.fmt(f),
178 }
179 }
180}
181
182impl PartialOrd for PackageVersion {
183 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
184 Some(self.cmp(other))
185 }
186}
187
188impl Ord for PackageVersion {
189 fn cmp(&self, other: &Self) -> Ordering {
190 match (self, other) {
191 (PackageVersion::SemVer(a), PackageVersion::SemVer(b)) => a.cmp(b),
192 (PackageVersion::SemVer(..), PackageVersion::DevVer(..)) => Ordering::Less,
193 (PackageVersion::SemVer(..), PackageVersion::StringVer(..)) => Ordering::Greater,
194 (PackageVersion::DevVer(..), PackageVersion::SemVer(..)) => Ordering::Greater,
195 (PackageVersion::DevVer(a), PackageVersion::DevVer(b)) => a.cmp(b),
196 (PackageVersion::DevVer(..), PackageVersion::StringVer(..)) => Ordering::Greater,
197 (PackageVersion::StringVer(a), PackageVersion::StringVer(b)) => a.cmp(b),
198 (PackageVersion::StringVer(..), PackageVersion::SemVer(..)) => Ordering::Less,
199 (PackageVersion::StringVer(..), PackageVersion::DevVer(..)) => Ordering::Less,
200 }
201 }
202}
203
204impl FromStr for PackageVersion {
205 type Err = PackageVersionParseError;
206
207 fn from_str(text: &str) -> Result<Self, Self::Err> {
208 let (modrev, specrev) = split_specrev(text)?;
209 match modrev {
210 "scm" => Ok(PackageVersion::DevVer(DevVer {
211 modrev: DevVersion::Scm,
212 specrev,
213 })),
214 "dev" => Ok(PackageVersion::DevVer(DevVer {
215 modrev: DevVersion::Dev,
216 specrev,
217 })),
218 modrev => match parse_version(modrev) {
219 Ok(version) => Ok(PackageVersion::SemVer(SemVer {
220 component_count: cmp::min(text.chars().filter(|c| *c == '.').count() + 1, 3),
221 version,
222 specrev,
223 })),
224 Err(_) => Ok(PackageVersion::StringVer(StringVer {
225 modrev: modrev.into(),
226 specrev,
227 })),
228 },
229 }
230 }
231}
232
233#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
235pub struct SpecRev(u16);
236
237impl From<u16> for SpecRev {
238 fn from(value: u16) -> Self {
239 Self(value)
240 }
241}
242
243impl Display for SpecRev {
244 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
245 self.0.fmt(f)
246 }
247}
248
249impl Default for SpecRev {
250 fn default() -> Self {
251 Self(1)
252 }
253}
254
255pub(crate) struct SpecRevIterator {
257 current: u16,
258}
259
260impl SpecRevIterator {
261 pub fn new() -> Self {
262 SpecRevIterator {
263 current: SpecRev::default().0,
264 }
265 }
266}
267
268impl Iterator for SpecRevIterator {
269 type Item = SpecRev;
270
271 fn next(&mut self) -> Option<Self::Item> {
272 let current = self.current;
273 self.current += 1;
274 if self.current == 0 {
275 None } else {
277 Some(SpecRev(current))
278 }
279 }
280}
281
282#[derive(Clone, Eq, PartialEq, Hash, Debug)]
284pub struct SemVer {
285 version: Version,
286 component_count: usize,
287 specrev: SpecRev,
288}
289
290impl HasModRev for SemVer {
291 fn to_modrev_string(&self) -> String {
292 self.version.to_string()
293 }
294}
295
296impl Display for SemVer {
297 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
298 let (version_str, remainder) = split_semver_version(&self.version.to_string());
299 let mut luarocks_version_str = version_str.split('.').take(self.component_count).join(".");
300 if let Some(remainder) = remainder {
301 luarocks_version_str.push_str(&format!(".{remainder}"));
305 }
306 let str = format!("{}-{}", luarocks_version_str, self.specrev);
307 str.fmt(f)
308 }
309}
310
311fn split_semver_version(version_str: &str) -> (String, Option<String>) {
312 if let Some(pos) = version_str.rfind('-') {
313 if let Some(pre_build_str) = version_str.get(pos + 1..) {
314 (version_str[..pos].into(), Some(pre_build_str.into()))
315 } else {
316 (version_str[..pos].into(), None)
317 }
318 } else {
319 (version_str.into(), None)
320 }
321}
322
323impl Serialize for SemVer {
324 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
325 where
326 S: serde::Serializer,
327 {
328 self.to_string().serialize(serializer)
329 }
330}
331
332impl Ord for SemVer {
333 fn cmp(&self, other: &Self) -> Ordering {
334 match cmp_versions(&self.version, &other.version) {
335 Ordering::Equal => self.specrev.cmp(&other.specrev),
336 ord => ord,
337 }
338 }
339}
340
341fn cmp_versions(a: &Version, b: &Version) -> Ordering {
345 match (a.major, a.minor, a.patch).cmp(&(b.major, b.minor, b.patch)) {
346 Ordering::Equal => cmp_prerelease(&a.pre, &b.pre),
347 ord => ord,
348 }
349}
350
351fn cmp_prerelease(a: &Prerelease, b: &Prerelease) -> Ordering {
352 match (a.is_empty(), b.is_empty()) {
353 (true, true) => Ordering::Equal,
354 (false, true) => Ordering::Greater,
355 (true, false) => Ordering::Less,
356 (false, false) => a.cmp(b),
357 }
358}
359
360fn comparator_matches(comparator: &Comparator, version: &Version) -> bool {
364 match comparator.op {
365 Op::Exact | Op::Wildcard => matches_exact(comparator, version),
366 Op::Greater => matches_greater(comparator, version),
367 Op::GreaterEq => matches_greater(comparator, version) || matches_exact(comparator, version),
368 Op::Less => matches_less(comparator, version),
369 Op::LessEq => matches_less(comparator, version) || matches_exact(comparator, version),
370 Op::Tilde => matches_tilde(comparator, version),
371 Op::Caret => matches_caret(comparator, version),
372 _ => false,
373 }
374}
375
376fn matches_exact(comparator: &Comparator, version: &Version) -> bool {
377 if version.major != comparator.major {
378 return false;
379 }
380 if let Some(minor) = comparator.minor {
381 if version.minor != minor {
382 return false;
383 }
384 }
385 if let Some(patch) = comparator.patch {
386 if version.patch != patch {
387 return false;
388 }
389 }
390 version.pre == comparator.pre
391}
392
393fn matches_greater(comparator: &Comparator, version: &Version) -> bool {
394 if version.major != comparator.major {
395 return version.major > comparator.major;
396 }
397 match comparator.minor {
398 None => return false,
399 Some(minor) => {
400 if version.minor != minor {
401 return version.minor > minor;
402 }
403 }
404 }
405 match comparator.patch {
406 None => return false,
407 Some(patch) => {
408 if version.patch != patch {
409 return version.patch > patch;
410 }
411 }
412 }
413 cmp_prerelease(&version.pre, &comparator.pre) == Ordering::Greater
414}
415
416fn matches_less(comparator: &Comparator, version: &Version) -> bool {
417 if version.major != comparator.major {
418 return version.major < comparator.major;
419 }
420 match comparator.minor {
421 None => return false,
422 Some(minor) => {
423 if version.minor != minor {
424 return version.minor < minor;
425 }
426 }
427 }
428 match comparator.patch {
429 None => return false,
430 Some(patch) => {
431 if version.patch != patch {
432 return version.patch < patch;
433 }
434 }
435 }
436 cmp_prerelease(&version.pre, &comparator.pre) == Ordering::Less
437}
438
439fn matches_tilde(comparator: &Comparator, version: &Version) -> bool {
440 if version.major != comparator.major {
441 return false;
442 }
443 if let Some(minor) = comparator.minor {
444 if version.minor != minor {
445 return false;
446 }
447 }
448 if let Some(patch) = comparator.patch {
449 if version.patch != patch {
450 return version.patch > patch;
451 }
452 }
453 cmp_prerelease(&version.pre, &comparator.pre) != Ordering::Less
454}
455
456fn matches_caret(comparator: &Comparator, version: &Version) -> bool {
457 if version.major != comparator.major {
458 return false;
459 }
460 let minor = match comparator.minor {
461 None => return true,
462 Some(minor) => minor,
463 };
464 let patch = match comparator.patch {
465 None => {
466 if comparator.major > 0 {
467 return version.minor >= minor;
468 } else {
469 return version.minor == minor;
470 }
471 }
472 Some(patch) => patch,
473 };
474 if comparator.major > 0 {
475 if version.minor != minor {
476 return version.minor > minor;
477 } else if version.patch != patch {
478 return version.patch > patch;
479 }
480 } else if minor > 0 {
481 if version.minor != minor {
482 return false;
483 } else if version.patch != patch {
484 return version.patch > patch;
485 }
486 } else if version.minor != minor || version.patch != patch {
487 return false;
488 }
489 cmp_prerelease(&version.pre, &comparator.pre) != Ordering::Less
490}
491
492impl PartialOrd for SemVer {
493 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
494 Some(self.cmp(other))
495 }
496}
497
498#[derive(Clone, Eq, PartialEq, PartialOrd, Ord, Hash, Debug, Serialize, Deserialize, Default)]
499#[serde(rename_all = "lowercase")]
500pub enum DevVersion {
501 #[default]
502 Dev,
503 Scm,
504}
505
506impl Display for DevVersion {
507 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
508 match self {
509 Self::Dev => "dev".fmt(f),
510 Self::Scm => "scm".fmt(f),
511 }
512 }
513}
514
515#[derive(Clone, Eq, PartialEq, Hash, Debug, Default)]
516pub struct DevVer {
517 modrev: DevVersion,
518 specrev: SpecRev,
519}
520
521impl DevVer {
522 fn default_with_specrev(specrev: SpecRev) -> Self {
523 Self {
524 modrev: DevVersion::default(),
525 specrev,
526 }
527 }
528}
529
530impl HasModRev for DevVer {
531 fn to_modrev_string(&self) -> String {
532 self.modrev.to_string().to_lowercase()
533 }
534}
535
536impl Display for DevVer {
537 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
538 let str = format!("{}-{}", self.modrev, self.specrev);
539 str.fmt(f)
540 }
541}
542
543impl Serialize for DevVer {
544 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
545 where
546 S: serde::Serializer,
547 {
548 self.to_string().serialize(serializer)
549 }
550}
551
552impl Ord for DevVer {
553 fn cmp(&self, other: &Self) -> Ordering {
554 let result = self.specrev.cmp(&other.specrev);
556 if result == Ordering::Equal {
557 return self.modrev.cmp(&other.modrev);
558 }
559 result
560 }
561}
562
563impl PartialOrd for DevVer {
564 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
565 Some(self.cmp(other))
566 }
567}
568
569#[derive(Clone, Eq, PartialEq, Hash, Debug)]
570pub struct StringVer {
571 modrev: String,
572 specrev: SpecRev,
573}
574
575impl HasModRev for StringVer {
576 fn to_modrev_string(&self) -> String {
577 self.modrev.to_string()
578 }
579}
580
581impl Display for StringVer {
582 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
583 let str = format!("{}-{}", self.modrev, self.specrev);
584 str.fmt(f)
585 }
586}
587
588impl Serialize for StringVer {
589 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
590 where
591 S: serde::Serializer,
592 {
593 self.to_string().serialize(serializer)
594 }
595}
596
597impl Ord for StringVer {
598 fn cmp(&self, other: &Self) -> Ordering {
599 let result = self.specrev.cmp(&other.specrev);
601 if result == Ordering::Equal {
602 return self.modrev.cmp(&other.modrev);
603 }
604 result
605 }
606}
607
608impl PartialOrd for StringVer {
609 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
610 Some(self.cmp(other))
611 }
612}
613
614pub(crate) trait HasModRev {
615 fn to_modrev_string(&self) -> String;
618}
619
620#[derive(Error, Debug, Diagnostic)]
621#[non_exhaustive]
622#[error(transparent)]
623pub struct PackageVersionReqError(#[from] Error);
624
625#[derive(Clone, Eq, PartialEq, Hash, Debug)]
628pub enum PackageVersionReq {
629 SemVer(VersionReq),
631 DevVer(DevVersion),
633 StringVer(String),
635 Any,
637}
638
639impl PackageVersionReq {
640 pub fn any() -> Self {
642 PackageVersionReq::Any
643 }
644
645 pub fn parse(text: &str) -> Result<Self, PackageVersionReqError> {
646 PackageVersionReq::from_str(text)
647 }
648
649 pub fn matches(&self, version: &PackageVersion) -> bool {
650 match (self, version) {
651 (PackageVersionReq::SemVer(req), PackageVersion::SemVer(ver)) => req
652 .comparators
653 .iter()
654 .all(|comparator| comparator_matches(comparator, &ver.version)),
655 (PackageVersionReq::DevVer(req), PackageVersion::DevVer(ver)) => req == &ver.modrev,
656 (PackageVersionReq::StringVer(req), PackageVersion::StringVer(ver)) => {
657 req == &ver.modrev
658 }
659 (PackageVersionReq::Any, _) => true,
660 _ => false,
661 }
662 }
663
664 pub fn is_any(&self) -> bool {
665 matches!(self, PackageVersionReq::Any)
666 }
667}
668
669impl Display for PackageVersionReq {
670 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
671 match self {
672 PackageVersionReq::SemVer(version_req) => {
673 let mut str = version_req.to_string();
674 if str.starts_with("=") {
675 str = str.replacen("=", "==", 1);
676 } else if str.starts_with("^") {
677 str = str.replacen("^", "~>", 1);
678 }
679 str.fmt(f)
680 }
681 PackageVersionReq::DevVer(name_req) => write!(f, "=={}", name_req),
682 PackageVersionReq::StringVer(name_req) => write!(f, "=={}", name_req),
683 PackageVersionReq::Any => f.write_str("any"),
684 }
685 }
686}
687
688impl<'de> Deserialize<'de> for PackageVersionReq {
689 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
690 where
691 D: Deserializer<'de>,
692 {
693 String::deserialize(deserializer)?
694 .parse()
695 .map_err(serde::de::Error::custom)
696 }
697}
698
699impl FromStr for PackageVersionReq {
700 type Err = PackageVersionReqError;
701
702 fn from_str(text: &str) -> Result<Self, Self::Err> {
703 let text = correct_version_req_str(text);
704
705 let trimmed = text.trim_start_matches('=').trim_start_matches('@').trim();
706
707 match parse_version_req(&text) {
708 Ok(_) => Ok(PackageVersionReq::SemVer(parse_version_req(&text)?)),
709 Err(_) => match trimmed {
710 "scm" => Ok(PackageVersionReq::DevVer(DevVersion::Scm)),
711 "dev" => Ok(PackageVersionReq::DevVer(DevVersion::Dev)),
712 ver => Ok(PackageVersionReq::StringVer(ver.to_string())),
713 },
714 }
715 }
716}
717
718fn correct_version_req_str(text: &str) -> String {
719 text.chars()
720 .chunk_by(|t| t.is_alphanumeric() || matches!(t, '-' | '_' | '.'))
721 .into_iter()
722 .map(|(is_version_str, chars)| (is_version_str, chars.collect::<String>()))
723 .map(|(is_version_str, chunk)| {
724 if is_version_str && !is_known_dev_version_str(&chunk) {
725 let version_str = trim_specrev(&chunk);
726 correct_prerelease_version_string(version_str)
727 } else {
728 chunk
729 }
730 })
731 .collect::<String>()
732}
733
734fn trim_specrev(version_str: &str) -> &str {
735 if let Some(pos) = version_str.rfind('-') {
736 &version_str[..pos]
737 } else {
738 version_str
739 }
740}
741
742#[derive(Error, Debug, Diagnostic)]
743#[non_exhaustive]
744pub enum SpecrevParseError {
745 #[error("specrev {specrev} in version {full_version} contains non-numeric characters")]
746 InvalidSpecrev {
747 specrev: String,
748 full_version: String,
749 },
750 #[error("could not parse specrev in version {0}")]
751 InvalidVersion(String),
752}
753
754fn split_specrev(version_str: &str) -> Result<(&str, SpecRev), SpecrevParseError> {
755 if let Some(pos) = version_str.rfind('-') {
756 if let Some(specrev_str) = version_str.get(pos + 1..) {
757 if specrev_str.chars().all(|c| c.is_ascii_digit()) {
758 let specrev =
759 specrev_str
760 .parse::<u16>()
761 .map_err(|_| SpecrevParseError::InvalidSpecrev {
762 specrev: specrev_str.into(),
763 full_version: version_str.into(),
764 })?;
765 Ok((&version_str[..pos], specrev.into()))
766 } else {
767 Err(SpecrevParseError::InvalidSpecrev {
768 specrev: specrev_str.into(),
769 full_version: version_str.into(),
770 })
771 }
772 } else {
773 Err(SpecrevParseError::InvalidVersion(version_str.into()))
774 }
775 } else {
776 Ok((version_str, 1.into()))
778 }
779}
780
781fn is_known_dev_version_str(text: &str) -> bool {
782 matches!(text, "dev" | "scm")
783}
784
785fn parse_version(s: &str) -> Result<Version, Error> {
788 let version_str = correct_version_string(s);
789 Version::parse(&version_str)
790}
791
792fn parse_version_req(version_constraints: &str) -> Result<VersionReq, Error> {
794 let unescaped = decode_html_entities(version_constraints)
795 .to_string()
796 .as_str()
797 .to_owned();
798 let transformed = match unescaped {
799 s if s.starts_with("~>") => parse_pessimistic_version_constraint(s)?,
800 s if s.starts_with("@") => format!("={}", &s[1..]),
801 s if s.starts_with("==") => s[1..].to_string(),
803 s if s .find(|c: char| c.is_alphanumeric())
805 .is_some_and(|idx| idx == 0) =>
806 {
807 format!("={}", s)
808 }
809 s => s,
810 };
811
812 let version_req = VersionReq::parse(&transformed)?;
813 Ok(version_req)
814}
815
816fn parse_pessimistic_version_constraint(version_constraint: String) -> Result<String, Error> {
817 let min_version_str = &version_constraint[2..].trim();
819 let min_version = Version::parse(&correct_version_string(min_version_str))?;
820
821 let max_version = match min_version_str.matches('.').count() {
822 0 => Version {
823 major: &min_version.major + 1,
824 ..min_version.clone()
825 },
826 1 => Version {
827 minor: &min_version.minor + 1,
828 ..min_version.clone()
829 },
830 _ => Version {
831 patch: &min_version.patch + 1,
832 ..min_version.clone()
833 },
834 };
835
836 Ok(format!(">= {min_version}, < {max_version}"))
837}
838
839fn correct_version_string(version: &str) -> String {
844 let version = append_minor_patch_if_missing(version);
845 correct_prerelease_version_string(&version)
846}
847
848fn correct_prerelease_version_string(version: &str) -> String {
849 let parts: Vec<&str> = version.split('.').collect();
850 if parts.len() > 3 {
851 let corrected_version = format!(
852 "{}.{}.{}-{}",
853 parts[0],
854 parts[1],
855 parts[2],
856 parts[3..].join(".")
857 );
858 corrected_version
859 } else {
860 version.to_string()
861 }
862}
863
864fn append_minor_patch_if_missing(version: &str) -> String {
866 if version.matches('.').count() < 2 {
867 append_minor_patch_if_missing(&format!("{version}.0"))
868 } else {
869 version.to_string()
870 }
871}
872
873#[cfg(test)]
874mod tests {
875 use super::*;
876
877 #[tokio::test]
878 async fn parse_semver_version() {
879 assert_eq!(
880 PackageVersion::parse("1-1").unwrap(),
881 PackageVersion::SemVer(SemVer {
882 version: "1.0.0".parse().unwrap(),
883 component_count: 1,
884 specrev: 1.into(),
885 })
886 );
887 assert_eq!(
888 PackageVersion::parse("1.0-1").unwrap(),
889 PackageVersion::SemVer(SemVer {
890 version: "1.0.0".parse().unwrap(),
891 component_count: 2,
892 specrev: 1.into(),
893 })
894 );
895 assert_eq!(
896 PackageVersion::parse("1.0.0-1").unwrap(),
897 PackageVersion::SemVer(SemVer {
898 version: "1.0.0".parse().unwrap(),
899 component_count: 3,
900 specrev: 1.into()
901 })
902 );
903 assert_eq!(
904 PackageVersion::parse("1.0.0-1").unwrap(),
905 PackageVersion::SemVer(SemVer {
906 version: "1.0.0".parse().unwrap(),
907 component_count: 3,
908 specrev: 1.into()
909 })
910 );
911 assert_eq!(
912 PackageVersion::parse("1.0.0-10-1").unwrap(),
913 PackageVersion::SemVer(SemVer {
914 version: "1.0.0-10".parse().unwrap(),
915 component_count: 3,
916 specrev: 1.into()
917 })
918 );
919 assert_eq!(
920 PackageVersion::parse("1.0.0.10-1").unwrap(),
921 PackageVersion::SemVer(SemVer {
922 version: "1.0.0-10".parse().unwrap(),
923 component_count: 3,
924 specrev: 1.into()
925 })
926 );
927 assert_eq!(
928 PackageVersion::parse("1.0.0.10.0-1").unwrap(),
929 PackageVersion::SemVer(SemVer {
930 version: "1.0.0-10.0".parse().unwrap(),
931 component_count: 3,
932 specrev: 1.into()
933 })
934 );
935 }
936
937 #[test]
938 fn four_component_versions_sort_after_base_version() {
939 let base = PackageVersion::parse("2.1.0-1").unwrap();
940 let extended = PackageVersion::parse("2.1.0.10-1").unwrap();
941 assert!(extended > base);
942 assert!(base < extended);
943 assert_eq!(
944 PackageVersion::parse("2.1.0.10-1").unwrap(),
945 PackageVersion::parse("2.1.0.10-1").unwrap()
946 );
947 }
948
949 #[test]
950 fn four_component_version_satisfies_greater_eq_req() {
951 let req = PackageVersionReq::parse(">= 2.1.0").unwrap();
952 assert!(req.matches(&PackageVersion::parse("2.1.0.10-1").unwrap()));
953 assert!(req.matches(&PackageVersion::parse("2.1.0-1").unwrap()));
954 assert!(!req.matches(&PackageVersion::parse("2.0.9-1").unwrap()));
955 }
956
957 #[tokio::test]
958 async fn parse_dev_version() {
959 assert_eq!(
960 PackageVersion::parse("dev-1").unwrap(),
961 PackageVersion::DevVer(DevVer {
962 modrev: DevVersion::Dev,
963 specrev: 1.into()
964 })
965 );
966 assert_eq!(
967 PackageVersion::parse("scm-1").unwrap(),
968 PackageVersion::DevVer(DevVer {
969 modrev: DevVersion::Scm,
970 specrev: 1.into()
971 })
972 );
973 assert_eq!(
974 PackageVersion::parse("git-1").unwrap(),
975 PackageVersion::StringVer(StringVer {
976 modrev: "git".into(),
977 specrev: 1.into()
978 })
979 );
980 assert_eq!(
981 PackageVersion::parse("scm-1").unwrap(),
982 PackageVersion::DevVer(DevVer {
983 modrev: DevVersion::Scm,
984 specrev: 1.into()
985 })
986 );
987 }
988
989 #[tokio::test]
990 async fn parse_dev_version_req() {
991 assert_eq!(
992 PackageVersionReq::parse("dev").unwrap(),
993 PackageVersionReq::DevVer(DevVersion::Dev)
994 );
995 assert_eq!(
996 PackageVersionReq::parse("scm").unwrap(),
997 PackageVersionReq::DevVer(DevVersion::Scm)
998 );
999 assert_eq!(
1000 PackageVersionReq::parse("git").unwrap(),
1001 PackageVersionReq::StringVer("git".into())
1002 );
1003 assert_eq!(
1004 PackageVersionReq::parse("==dev").unwrap(),
1005 PackageVersionReq::DevVer(DevVersion::Dev)
1006 );
1007 assert_eq!(
1008 PackageVersionReq::parse("==git").unwrap(),
1009 PackageVersionReq::StringVer("git".into())
1010 );
1011 assert_eq!(
1012 PackageVersionReq::parse("== dev").unwrap(),
1013 PackageVersionReq::DevVer(DevVersion::Dev)
1014 );
1015 assert_eq!(
1016 PackageVersionReq::parse("== scm").unwrap(),
1017 PackageVersionReq::DevVer(DevVersion::Scm)
1018 );
1019 assert_eq!(
1020 PackageVersionReq::parse("@dev").unwrap(),
1021 PackageVersionReq::DevVer(DevVersion::Dev)
1022 );
1023 assert_eq!(
1024 PackageVersionReq::parse("@git").unwrap(),
1025 PackageVersionReq::StringVer("git".into())
1026 );
1027 assert_eq!(
1028 PackageVersionReq::parse("@ dev").unwrap(),
1029 PackageVersionReq::DevVer(DevVersion::Dev)
1030 );
1031 assert_eq!(
1032 PackageVersionReq::parse("@ scm").unwrap(),
1033 PackageVersionReq::DevVer(DevVersion::Scm)
1034 );
1035 assert_eq!(
1036 PackageVersionReq::parse(">1-1,<1.2-2").unwrap(),
1037 PackageVersionReq::SemVer(">1,<1.2".parse().unwrap())
1038 );
1039 assert_eq!(
1040 PackageVersionReq::parse("> 1-1, < 1.2-2").unwrap(),
1041 PackageVersionReq::SemVer("> 1, < 1.2".parse().unwrap())
1042 );
1043 assert_eq!(
1044 PackageVersionReq::parse("> 2.1.0.10, < 2.1.1").unwrap(),
1045 PackageVersionReq::SemVer("> 2.1.0-10, < 2.1.1".parse().unwrap())
1046 );
1047 }
1048
1049 #[tokio::test]
1050 async fn package_version_req_semver_roundtrips() {
1051 let req = PackageVersionReq::parse("==0.7.1").unwrap();
1052 assert_eq!(req.to_string(), "==0.7.1");
1053
1054 let req = PackageVersionReq::parse("0.7.1").unwrap();
1055 assert_eq!(req.to_string(), "==0.7.1");
1056
1057 let req = PackageVersionReq::parse(">=0.7.1").unwrap();
1058 assert_eq!(req.to_string(), ">=0.7.1");
1059
1060 let req = PackageVersionReq::parse(">0.7.1").unwrap();
1061 assert_eq!(req.to_string(), ">0.7.1");
1062
1063 let req = PackageVersionReq::parse("<0.7.1").unwrap();
1064 assert_eq!(req.to_string(), "<0.7.1");
1065
1066 let req = PackageVersionReq::parse("~> 0.7.1").unwrap();
1067 assert_eq!(req.to_string(), ">=0.7.1, <0.7.2");
1068 }
1069
1070 #[tokio::test]
1071 async fn package_version_req_devver_roundtrips() {
1072 let req = PackageVersionReq::parse("==scm").unwrap();
1073 assert_eq!(req.to_string(), "==scm");
1074
1075 let req = PackageVersionReq::parse("@scm").unwrap();
1076 assert_eq!(req.to_string(), "==scm");
1077
1078 let req = PackageVersionReq::parse("scm").unwrap();
1079 assert_eq!(req.to_string(), "==scm");
1080
1081 let req = PackageVersionReq::parse("==a144124839f027a2d0a95791936c478d047126fc").unwrap();
1082 assert_eq!(
1083 req.to_string(),
1084 "==a144124839f027a2d0a95791936c478d047126fc"
1085 );
1086 }
1087}