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