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