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