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 nonempty::NonEmpty;
11use semver::{Comparator, Error, Op, Version, VersionReq};
12use serde::{de, Deserialize, Deserializer, Serialize};
13use thiserror::Error;
14
15#[derive(Debug, Error)]
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)]
140pub enum PackageVersionParseError {
141 #[error(transparent)]
142 Specrev(#[from] SpecrevParseError),
143 #[error("failed to parse version: {0}")]
144 Version(#[from] Error),
145}
146
147impl Serialize for PackageVersion {
148 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
149 where
150 S: serde::Serializer,
151 {
152 match self {
153 PackageVersion::SemVer(version) => version.serialize(serializer),
154 PackageVersion::DevVer(version) => version.serialize(serializer),
155 PackageVersion::StringVer(version) => version.serialize(serializer),
156 }
157 }
158}
159
160impl<'de> Deserialize<'de> for PackageVersion {
161 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
162 where
163 D: Deserializer<'de>,
164 {
165 let s = String::deserialize(deserializer)?;
166 Self::from_str(&s).map_err(de::Error::custom)
167 }
168}
169
170impl Display for PackageVersion {
171 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
172 match self {
173 PackageVersion::SemVer(version) => version.fmt(f),
174 PackageVersion::DevVer(version) => version.fmt(f),
175 PackageVersion::StringVer(version) => version.fmt(f),
176 }
177 }
178}
179
180impl PartialOrd for PackageVersion {
181 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
182 Some(self.cmp(other))
183 }
184}
185
186impl Ord for PackageVersion {
187 fn cmp(&self, other: &Self) -> Ordering {
188 match (self, other) {
189 (PackageVersion::SemVer(a), PackageVersion::SemVer(b)) => a.cmp(b),
190 (PackageVersion::SemVer(..), PackageVersion::DevVer(..)) => Ordering::Less,
191 (PackageVersion::SemVer(..), PackageVersion::StringVer(..)) => Ordering::Greater,
192 (PackageVersion::DevVer(..), PackageVersion::SemVer(..)) => Ordering::Greater,
193 (PackageVersion::DevVer(a), PackageVersion::DevVer(b)) => a.cmp(b),
194 (PackageVersion::DevVer(..), PackageVersion::StringVer(..)) => Ordering::Greater,
195 (PackageVersion::StringVer(a), PackageVersion::StringVer(b)) => a.cmp(b),
196 (PackageVersion::StringVer(..), PackageVersion::SemVer(..)) => Ordering::Less,
197 (PackageVersion::StringVer(..), PackageVersion::DevVer(..)) => Ordering::Less,
198 }
199 }
200}
201
202impl FromStr for PackageVersion {
203 type Err = PackageVersionParseError;
204
205 fn from_str(text: &str) -> Result<Self, Self::Err> {
206 let (modrev, specrev) = split_specrev(text)?;
207 match modrev {
208 "scm" => Ok(PackageVersion::DevVer(DevVer {
209 modrev: DevVersion::Scm,
210 specrev,
211 })),
212 "dev" => Ok(PackageVersion::DevVer(DevVer {
213 modrev: DevVersion::Dev,
214 specrev,
215 })),
216 modrev => match parse_version(modrev) {
217 Ok(version) => Ok(PackageVersion::SemVer(SemVer {
218 component_count: cmp::min(text.chars().filter(|c| *c == '.').count() + 1, 3),
219 version,
220 specrev,
221 })),
222 Err(_) => Ok(PackageVersion::StringVer(StringVer {
223 modrev: modrev.into(),
224 specrev,
225 })),
226 },
227 }
228 }
229}
230
231#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
233pub struct SpecRev(u16);
234
235impl From<u16> for SpecRev {
236 fn from(value: u16) -> Self {
237 Self(value)
238 }
239}
240
241impl Display for SpecRev {
242 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
243 self.0.fmt(f)
244 }
245}
246
247impl Default for SpecRev {
248 fn default() -> Self {
249 Self(1)
250 }
251}
252
253pub(crate) struct SpecRevIterator {
255 current: u16,
256}
257
258impl SpecRevIterator {
259 pub fn new() -> Self {
260 SpecRevIterator {
261 current: SpecRev::default().0,
262 }
263 }
264}
265
266impl Iterator for SpecRevIterator {
267 type Item = SpecRev;
268
269 fn next(&mut self) -> Option<Self::Item> {
270 let current = self.current;
271 self.current += 1;
272 if self.current == 0 {
273 None } else {
275 Some(SpecRev(current))
276 }
277 }
278}
279
280#[derive(Clone, Eq, PartialEq, Hash, Debug)]
282pub struct SemVer {
283 version: Version,
284 component_count: usize,
285 specrev: SpecRev,
286}
287
288impl HasModRev for SemVer {
289 fn to_modrev_string(&self) -> String {
290 self.version.to_string()
291 }
292}
293
294impl Display for SemVer {
295 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
296 let (version_str, remainder) = split_semver_version(&self.version.to_string());
297 let mut luarocks_version_str = version_str.split('.').take(self.component_count).join(".");
298 if let Some(remainder) = remainder {
299 luarocks_version_str.push_str(&format!(".{remainder}"));
303 }
304 let str = format!("{}-{}", luarocks_version_str, self.specrev);
305 str.fmt(f)
306 }
307}
308
309fn split_semver_version(version_str: &str) -> (String, Option<String>) {
310 if let Some(pos) = version_str.rfind('-') {
311 if let Some(pre_build_str) = version_str.get(pos + 1..) {
312 (version_str[..pos].into(), Some(pre_build_str.into()))
313 } else {
314 (version_str[..pos].into(), None)
315 }
316 } else {
317 (version_str.into(), None)
318 }
319}
320
321impl Serialize for SemVer {
322 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
323 where
324 S: serde::Serializer,
325 {
326 self.to_string().serialize(serializer)
327 }
328}
329
330impl Ord for SemVer {
331 fn cmp(&self, other: &Self) -> Ordering {
332 let result = self.version.cmp(&other.version);
333 if result == Ordering::Equal {
334 return self.specrev.cmp(&other.specrev);
335 }
336 result
337 }
338}
339
340impl PartialOrd for SemVer {
341 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
342 Some(self.cmp(other))
343 }
344}
345
346#[derive(Clone, Eq, PartialEq, PartialOrd, Ord, Hash, Debug, Serialize, Deserialize, Default)]
347#[serde(rename_all = "lowercase")]
348pub enum DevVersion {
349 #[default]
350 Dev,
351 Scm,
352}
353
354impl Display for DevVersion {
355 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
356 match self {
357 Self::Dev => "dev".fmt(f),
358 Self::Scm => "scm".fmt(f),
359 }
360 }
361}
362
363#[derive(Clone, Eq, PartialEq, Hash, Debug, Default)]
364pub struct DevVer {
365 modrev: DevVersion,
366 specrev: SpecRev,
367}
368
369impl DevVer {
370 fn default_with_specrev(specrev: SpecRev) -> Self {
371 Self {
372 modrev: DevVersion::default(),
373 specrev,
374 }
375 }
376}
377
378impl HasModRev for DevVer {
379 fn to_modrev_string(&self) -> String {
380 self.modrev.to_string().to_lowercase()
381 }
382}
383
384impl Display for DevVer {
385 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
386 let str = format!("{}-{}", self.modrev, self.specrev);
387 str.fmt(f)
388 }
389}
390
391impl Serialize for DevVer {
392 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
393 where
394 S: serde::Serializer,
395 {
396 self.to_string().serialize(serializer)
397 }
398}
399
400impl Ord for DevVer {
401 fn cmp(&self, other: &Self) -> Ordering {
402 let result = self.specrev.cmp(&other.specrev);
404 if result == Ordering::Equal {
405 return self.modrev.cmp(&other.modrev);
406 }
407 result
408 }
409}
410
411impl PartialOrd for DevVer {
412 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
413 Some(self.cmp(other))
414 }
415}
416
417#[derive(Clone, Eq, PartialEq, Hash, Debug)]
418pub struct StringVer {
419 modrev: String,
420 specrev: SpecRev,
421}
422
423impl HasModRev for StringVer {
424 fn to_modrev_string(&self) -> String {
425 self.modrev.to_string()
426 }
427}
428
429impl Display for StringVer {
430 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
431 let str = format!("{}-{}", self.modrev, self.specrev);
432 str.fmt(f)
433 }
434}
435
436impl Serialize for StringVer {
437 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
438 where
439 S: serde::Serializer,
440 {
441 self.to_string().serialize(serializer)
442 }
443}
444
445impl Ord for StringVer {
446 fn cmp(&self, other: &Self) -> Ordering {
447 let result = self.specrev.cmp(&other.specrev);
449 if result == Ordering::Equal {
450 return self.modrev.cmp(&other.modrev);
451 }
452 result
453 }
454}
455
456impl PartialOrd for StringVer {
457 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
458 Some(self.cmp(other))
459 }
460}
461
462pub(crate) trait HasModRev {
463 fn to_modrev_string(&self) -> String;
466}
467
468#[derive(Error, Debug)]
469#[error(transparent)]
470pub struct PackageVersionReqError(#[from] Error);
471
472#[derive(Clone, Eq, PartialEq, Hash, Debug)]
475pub enum PackageVersionReq {
476 SemVer(VersionReq),
478 DevVer(DevVersion),
480 StringVer(String),
482 Any,
484}
485
486impl PackageVersionReq {
487 pub fn any() -> Self {
489 PackageVersionReq::Any
490 }
491
492 pub fn parse(text: &str) -> Result<Self, PackageVersionReqError> {
493 PackageVersionReq::from_str(text)
494 }
495
496 pub fn matches(&self, version: &PackageVersion) -> bool {
497 match (self, version) {
498 (PackageVersionReq::SemVer(req), PackageVersion::SemVer(ver)) => {
499 req.matches(&ver.version)
500 }
501 (PackageVersionReq::DevVer(req), PackageVersion::DevVer(ver)) => req == &ver.modrev,
502 (PackageVersionReq::StringVer(req), PackageVersion::StringVer(ver)) => {
503 req == &ver.modrev
504 }
505 (PackageVersionReq::Any, _) => true,
506 _ => false,
507 }
508 }
509
510 pub fn is_any(&self) -> bool {
511 matches!(self, PackageVersionReq::Any)
512 }
513}
514
515impl Display for PackageVersionReq {
516 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
517 match self {
518 PackageVersionReq::SemVer(version_req) => {
519 let mut str = version_req.to_string();
520 if str.starts_with("=") {
521 str = str.replacen("=", "==", 1);
522 } else if str.starts_with("^") {
523 str = str.replacen("^", "~>", 1);
524 }
525 str.fmt(f)
526 }
527 PackageVersionReq::DevVer(name_req) => write!(f, "=={}", &name_req),
528 PackageVersionReq::StringVer(name_req) => write!(f, "=={}", &name_req),
529 PackageVersionReq::Any => f.write_str("any"),
530 }
531 }
532}
533
534impl<'de> Deserialize<'de> for PackageVersionReq {
535 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
536 where
537 D: Deserializer<'de>,
538 {
539 String::deserialize(deserializer)?
540 .parse()
541 .map_err(serde::de::Error::custom)
542 }
543}
544
545impl FromStr for PackageVersionReq {
546 type Err = PackageVersionReqError;
547
548 fn from_str(text: &str) -> Result<Self, Self::Err> {
549 let text = correct_version_req_str(text);
550
551 let trimmed = text.trim_start_matches('=').trim_start_matches('@').trim();
552
553 match parse_version_req(&text) {
554 Ok(_) => Ok(PackageVersionReq::SemVer(parse_version_req(&text)?)),
555 Err(_) => match trimmed {
556 "scm" => Ok(PackageVersionReq::DevVer(DevVersion::Scm)),
557 "dev" => Ok(PackageVersionReq::DevVer(DevVersion::Dev)),
558 ver => Ok(PackageVersionReq::StringVer(ver.to_string())),
559 },
560 }
561 }
562}
563
564fn correct_version_req_str(text: &str) -> String {
565 text.chars()
566 .chunk_by(|t| t.is_alphanumeric() || matches!(t, '-' | '_' | '.'))
567 .into_iter()
568 .map(|(is_version_str, chars)| (is_version_str, chars.collect::<String>()))
569 .map(|(is_version_str, chunk)| {
570 if is_version_str && !is_known_dev_version_str(&chunk) {
571 let version_str = trim_specrev(&chunk);
572 correct_prerelease_version_string(version_str)
573 } else {
574 chunk
575 }
576 })
577 .collect::<String>()
578}
579
580fn trim_specrev(version_str: &str) -> &str {
581 if let Some(pos) = version_str.rfind('-') {
582 &version_str[..pos]
583 } else {
584 version_str
585 }
586}
587
588#[derive(Error, Debug)]
589pub enum SpecrevParseError {
590 #[error("specrev {specrev} in version {full_version} contains non-numeric characters")]
591 InvalidSpecrev {
592 specrev: String,
593 full_version: String,
594 },
595 #[error("could not parse specrev in version {0}")]
596 InvalidVersion(String),
597}
598
599fn split_specrev(version_str: &str) -> Result<(&str, SpecRev), SpecrevParseError> {
600 if let Some(pos) = version_str.rfind('-') {
601 if let Some(specrev_str) = version_str.get(pos + 1..) {
602 if specrev_str.chars().all(|c| c.is_ascii_digit()) {
603 let specrev =
604 specrev_str
605 .parse::<u16>()
606 .map_err(|_| SpecrevParseError::InvalidSpecrev {
607 specrev: specrev_str.into(),
608 full_version: version_str.into(),
609 })?;
610 Ok((&version_str[..pos], specrev.into()))
611 } else {
612 Err(SpecrevParseError::InvalidSpecrev {
613 specrev: specrev_str.into(),
614 full_version: version_str.into(),
615 })
616 }
617 } else {
618 Err(SpecrevParseError::InvalidVersion(version_str.into()))
619 }
620 } else {
621 Ok((version_str, 1.into()))
623 }
624}
625
626fn is_known_dev_version_str(text: &str) -> bool {
627 matches!(text, "dev" | "scm")
628}
629
630fn parse_version(s: &str) -> Result<Version, Error> {
633 let version_str = correct_version_string(s);
634 Version::parse(&version_str)
635}
636
637fn parse_version_req(version_constraints: &str) -> Result<VersionReq, Error> {
639 let unescaped = decode_html_entities(version_constraints)
640 .to_string()
641 .as_str()
642 .to_owned();
643 let transformed = match unescaped {
644 s if s.starts_with("~>") => parse_pessimistic_version_constraint(s)?,
645 s if s.starts_with("@") => format!("={}", &s[1..]),
646 s if s.starts_with("==") => s[1..].to_string(),
648 s if s .find(|c: char| c.is_alphanumeric())
650 .is_some_and(|idx| idx == 0) =>
651 {
652 format!("={}", &s)
653 }
654 s => s,
655 };
656
657 let version_req = VersionReq::parse(&transformed)?;
658 Ok(version_req)
659}
660
661fn parse_pessimistic_version_constraint(version_constraint: String) -> Result<String, Error> {
662 let min_version_str = &version_constraint[2..].trim();
664 let min_version = Version::parse(&correct_version_string(min_version_str))?;
665
666 let max_version = match min_version_str.matches('.').count() {
667 0 => Version {
668 major: &min_version.major + 1,
669 ..min_version.clone()
670 },
671 1 => Version {
672 minor: &min_version.minor + 1,
673 ..min_version.clone()
674 },
675 _ => Version {
676 patch: &min_version.patch + 1,
677 ..min_version.clone()
678 },
679 };
680
681 Ok(format!(">= {min_version}, < {max_version}"))
682}
683
684fn correct_version_string(version: &str) -> String {
689 let version = append_minor_patch_if_missing(version);
690 correct_prerelease_version_string(&version)
691}
692
693fn correct_prerelease_version_string(version: &str) -> String {
694 let parts: Vec<&str> = version.split('.').collect();
695 if parts.len() > 3 {
696 let corrected_version = format!(
697 "{}.{}.{}-{}",
698 parts[0],
699 parts[1],
700 parts[2],
701 parts[3..].join(".")
702 );
703 corrected_version
704 } else {
705 version.to_string()
706 }
707}
708
709fn append_minor_patch_if_missing(version: &str) -> String {
711 if version.matches('.').count() < 2 {
712 append_minor_patch_if_missing(&format!("{version}.0"))
713 } else {
714 version.to_string()
715 }
716}
717
718#[cfg(test)]
719mod tests {
720 use super::*;
721
722 #[tokio::test]
723 async fn parse_semver_version() {
724 assert_eq!(
725 PackageVersion::parse("1-1").unwrap(),
726 PackageVersion::SemVer(SemVer {
727 version: "1.0.0".parse().unwrap(),
728 component_count: 1,
729 specrev: 1.into(),
730 })
731 );
732 assert_eq!(
733 PackageVersion::parse("1.0-1").unwrap(),
734 PackageVersion::SemVer(SemVer {
735 version: "1.0.0".parse().unwrap(),
736 component_count: 2,
737 specrev: 1.into(),
738 })
739 );
740 assert_eq!(
741 PackageVersion::parse("1.0.0-1").unwrap(),
742 PackageVersion::SemVer(SemVer {
743 version: "1.0.0".parse().unwrap(),
744 component_count: 3,
745 specrev: 1.into()
746 })
747 );
748 assert_eq!(
749 PackageVersion::parse("1.0.0-1").unwrap(),
750 PackageVersion::SemVer(SemVer {
751 version: "1.0.0".parse().unwrap(),
752 component_count: 3,
753 specrev: 1.into()
754 })
755 );
756 assert_eq!(
757 PackageVersion::parse("1.0.0-10-1").unwrap(),
758 PackageVersion::SemVer(SemVer {
759 version: "1.0.0-10".parse().unwrap(),
760 component_count: 3,
761 specrev: 1.into()
762 })
763 );
764 assert_eq!(
765 PackageVersion::parse("1.0.0.10-1").unwrap(),
766 PackageVersion::SemVer(SemVer {
767 version: "1.0.0-10".parse().unwrap(),
768 component_count: 3,
769 specrev: 1.into()
770 })
771 );
772 assert_eq!(
773 PackageVersion::parse("1.0.0.10.0-1").unwrap(),
774 PackageVersion::SemVer(SemVer {
775 version: "1.0.0-10.0".parse().unwrap(),
776 component_count: 3,
777 specrev: 1.into()
778 })
779 );
780 }
781
782 #[tokio::test]
783 async fn parse_dev_version() {
784 assert_eq!(
785 PackageVersion::parse("dev-1").unwrap(),
786 PackageVersion::DevVer(DevVer {
787 modrev: DevVersion::Dev,
788 specrev: 1.into()
789 })
790 );
791 assert_eq!(
792 PackageVersion::parse("scm-1").unwrap(),
793 PackageVersion::DevVer(DevVer {
794 modrev: DevVersion::Scm,
795 specrev: 1.into()
796 })
797 );
798 assert_eq!(
799 PackageVersion::parse("git-1").unwrap(),
800 PackageVersion::StringVer(StringVer {
801 modrev: "git".into(),
802 specrev: 1.into()
803 })
804 );
805 assert_eq!(
806 PackageVersion::parse("scm-1").unwrap(),
807 PackageVersion::DevVer(DevVer {
808 modrev: DevVersion::Scm,
809 specrev: 1.into()
810 })
811 );
812 }
813
814 #[tokio::test]
815 async fn parse_dev_version_req() {
816 assert_eq!(
817 PackageVersionReq::parse("dev").unwrap(),
818 PackageVersionReq::DevVer(DevVersion::Dev)
819 );
820 assert_eq!(
821 PackageVersionReq::parse("scm").unwrap(),
822 PackageVersionReq::DevVer(DevVersion::Scm)
823 );
824 assert_eq!(
825 PackageVersionReq::parse("git").unwrap(),
826 PackageVersionReq::StringVer("git".into())
827 );
828 assert_eq!(
829 PackageVersionReq::parse("==dev").unwrap(),
830 PackageVersionReq::DevVer(DevVersion::Dev)
831 );
832 assert_eq!(
833 PackageVersionReq::parse("==git").unwrap(),
834 PackageVersionReq::StringVer("git".into())
835 );
836 assert_eq!(
837 PackageVersionReq::parse("== dev").unwrap(),
838 PackageVersionReq::DevVer(DevVersion::Dev)
839 );
840 assert_eq!(
841 PackageVersionReq::parse("== scm").unwrap(),
842 PackageVersionReq::DevVer(DevVersion::Scm)
843 );
844 assert_eq!(
845 PackageVersionReq::parse("@dev").unwrap(),
846 PackageVersionReq::DevVer(DevVersion::Dev)
847 );
848 assert_eq!(
849 PackageVersionReq::parse("@git").unwrap(),
850 PackageVersionReq::StringVer("git".into())
851 );
852 assert_eq!(
853 PackageVersionReq::parse("@ dev").unwrap(),
854 PackageVersionReq::DevVer(DevVersion::Dev)
855 );
856 assert_eq!(
857 PackageVersionReq::parse("@ scm").unwrap(),
858 PackageVersionReq::DevVer(DevVersion::Scm)
859 );
860 assert_eq!(
861 PackageVersionReq::parse(">1-1,<1.2-2").unwrap(),
862 PackageVersionReq::SemVer(">1,<1.2".parse().unwrap())
863 );
864 assert_eq!(
865 PackageVersionReq::parse("> 1-1, < 1.2-2").unwrap(),
866 PackageVersionReq::SemVer("> 1, < 1.2".parse().unwrap())
867 );
868 assert_eq!(
869 PackageVersionReq::parse("> 2.1.0.10, < 2.1.1").unwrap(),
870 PackageVersionReq::SemVer("> 2.1.0-10, < 2.1.1".parse().unwrap())
871 );
872 }
873
874 #[tokio::test]
875 async fn package_version_req_semver_roundtrips() {
876 let req = PackageVersionReq::parse("==0.7.1").unwrap();
877 assert_eq!(req.to_string(), "==0.7.1");
878
879 let req = PackageVersionReq::parse("0.7.1").unwrap();
880 assert_eq!(req.to_string(), "==0.7.1");
881
882 let req = PackageVersionReq::parse(">=0.7.1").unwrap();
883 assert_eq!(req.to_string(), ">=0.7.1");
884
885 let req = PackageVersionReq::parse(">0.7.1").unwrap();
886 assert_eq!(req.to_string(), ">0.7.1");
887
888 let req = PackageVersionReq::parse("<0.7.1").unwrap();
889 assert_eq!(req.to_string(), "<0.7.1");
890
891 let req = PackageVersionReq::parse("~> 0.7.1").unwrap();
892 assert_eq!(req.to_string(), ">=0.7.1, <0.7.2");
893 }
894
895 #[tokio::test]
896 async fn package_version_req_devver_roundtrips() {
897 let req = PackageVersionReq::parse("==scm").unwrap();
898 assert_eq!(req.to_string(), "==scm");
899
900 let req = PackageVersionReq::parse("@scm").unwrap();
901 assert_eq!(req.to_string(), "==scm");
902
903 let req = PackageVersionReq::parse("scm").unwrap();
904 assert_eq!(req.to_string(), "==scm");
905
906 let req = PackageVersionReq::parse("==a144124839f027a2d0a95791936c478d047126fc").unwrap();
907 assert_eq!(
908 req.to_string(),
909 "==a144124839f027a2d0a95791936c478d047126fc"
910 );
911 }
912}