1use std::fmt;
13use std::str::FromStr;
14
15use crate::semver::Version;
16use crate::Error;
17
18#[derive(Clone, Debug, PartialEq, Eq)]
20pub struct VersionReq {
21 pub ranges: Vec<Range>,
23}
24
25impl VersionReq {
26 pub const STAR: Self = Self { ranges: Vec::new() };
28
29 pub fn parse(text: &str) -> Result<Self, Error> {
31 let text = text.trim();
32
33 if text.is_empty() || text == "*" {
34 return Ok(Self::STAR);
35 }
36
37 let ranges: Result<Vec<_>, _> = text.split("||").map(|s| Range::parse(s.trim())).collect();
39
40 Ok(Self { ranges: ranges? })
41 }
42
43 pub fn matches(&self, version: &Version) -> bool {
45 if self.ranges.is_empty() {
47 return true;
48 }
49
50 self.ranges.iter().any(|r| r.matches(version))
52 }
53
54 pub fn is_any(&self) -> bool {
56 self.ranges.is_empty()
57 }
58}
59
60impl fmt::Display for VersionReq {
61 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62 if self.ranges.is_empty() {
63 return write!(f, "*");
64 }
65
66 let mut first = true;
67 for range in &self.ranges {
68 if !first {
69 write!(f, " || ")?;
70 }
71 first = false;
72 write!(f, "{}", range)?;
73 }
74 Ok(())
75 }
76}
77
78impl FromStr for VersionReq {
79 type Err = Error;
80
81 fn from_str(s: &str) -> Result<Self, Self::Err> {
82 Self::parse(s)
83 }
84}
85
86impl Default for VersionReq {
87 fn default() -> Self {
88 Self::STAR
89 }
90}
91
92#[derive(Clone, Debug, PartialEq, Eq)]
94pub struct Range {
95 pub comparators: Vec<Comparator>,
97}
98
99impl Range {
100 pub fn parse(text: &str) -> Result<Self, Error> {
102 let text = text.trim();
103
104 if text.is_empty() || text == "*" {
105 return Ok(Self {
106 comparators: vec![],
107 });
108 }
109
110 if let Some(idx) = text.find(" - ") {
112 let lower = text[..idx].trim();
113 let upper = text[idx + 3..].trim();
114 return Self::parse_hyphen_range(lower, upper);
115 }
116
117 let parts: Vec<&str> = text
119 .split(|c: char| c.is_whitespace() || c == ',')
120 .filter(|s| !s.is_empty())
121 .collect();
122
123 let comparators: Result<Vec<_>, _> = parts.iter().map(|s| Comparator::parse(s)).collect();
124
125 Ok(Self {
126 comparators: comparators?,
127 })
128 }
129
130 fn parse_hyphen_range(lower: &str, upper: &str) -> Result<Self, Error> {
132 let lower_version = Version::parse(lower)?;
133 let upper_version = Version::parse(upper)?;
134
135 Ok(Self {
136 comparators: vec![
137 Comparator {
138 op: Op::GreaterEq,
139 major: lower_version.major,
140 minor: Some(lower_version.minor),
141 patch: Some(lower_version.patch),
142 pre: lower_version.pre,
143 },
144 Comparator {
145 op: Op::LessEq,
146 major: upper_version.major,
147 minor: Some(upper_version.minor),
148 patch: Some(upper_version.patch),
149 pre: upper_version.pre,
150 },
151 ],
152 })
153 }
154
155 pub fn matches(&self, version: &Version) -> bool {
157 if self.comparators.is_empty() {
159 return true;
160 }
161
162 self.comparators.iter().all(|c| c.matches(version))
164 }
165}
166
167impl fmt::Display for Range {
168 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
169 if self.comparators.is_empty() {
170 return write!(f, "*");
171 }
172
173 let mut first = true;
174 for comp in &self.comparators {
175 if !first {
176 write!(f, " ")?;
177 }
178 first = false;
179 write!(f, "{}", comp)?;
180 }
181 Ok(())
182 }
183}
184
185impl FromStr for Range {
186 type Err = Error;
187
188 fn from_str(s: &str) -> Result<Self, Self::Err> {
189 Self::parse(s)
190 }
191}
192
193#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
195pub enum Op {
196 Exact,
198 Greater,
200 GreaterEq,
202 Less,
204 LessEq,
206 Caret,
208 Tilde,
210 Wildcard,
212}
213
214impl fmt::Display for Op {
215 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
216 match self {
217 Op::Exact => Ok(()),
218 Op::Greater => write!(f, ">"),
219 Op::GreaterEq => write!(f, ">="),
220 Op::Less => write!(f, "<"),
221 Op::LessEq => write!(f, "<="),
222 Op::Caret => write!(f, "^"),
223 Op::Tilde => write!(f, "~"),
224 Op::Wildcard => write!(f, "*"),
225 }
226 }
227}
228
229#[derive(Clone, Debug, PartialEq, Eq)]
231pub struct Comparator {
232 pub op: Op,
234 pub major: u64,
236 pub minor: Option<u64>,
238 pub patch: Option<u64>,
240 pub pre: crate::semver::Prerelease,
242}
243
244impl Comparator {
245 pub fn parse(text: &str) -> Result<Self, Error> {
247 let text = text.trim();
248
249 if text.is_empty() {
250 return Err(Error::InvalidVersion("empty comparator".to_string()));
251 }
252
253 if text == "*" || text == "x" || text == "X" {
255 return Ok(Self {
256 op: Op::Wildcard,
257 major: 0,
258 minor: None,
259 patch: None,
260 pre: crate::semver::Prerelease::EMPTY,
261 });
262 }
263
264 let (op, rest) = if let Some(rest) = text.strip_prefix(">=") {
266 (Op::GreaterEq, rest)
267 } else if let Some(rest) = text.strip_prefix("<=") {
268 (Op::LessEq, rest)
269 } else if let Some(rest) = text.strip_prefix('>') {
270 (Op::Greater, rest)
271 } else if let Some(rest) = text.strip_prefix('<') {
272 (Op::Less, rest)
273 } else if let Some(rest) = text.strip_prefix('^') {
274 (Op::Caret, rest)
275 } else if let Some(rest) = text.strip_prefix('~') {
276 (Op::Tilde, rest)
277 } else if let Some(rest) = text.strip_prefix('=') {
278 (Op::Exact, rest)
279 } else {
280 (Op::Exact, text)
281 };
282
283 let rest = rest.trim();
284
285 if rest.contains('*') || rest.contains('x') || rest.contains('X') {
287 return Self::parse_wildcard(rest);
288 }
289
290 let version = Version::parse(rest)?;
292
293 Ok(Self {
294 op,
295 major: version.major,
296 minor: Some(version.minor),
297 patch: Some(version.patch),
298 pre: version.pre,
299 })
300 }
301
302 fn parse_wildcard(text: &str) -> Result<Self, Error> {
304 let parts: Vec<&str> = text.split('.').collect();
305
306 let parse_part = |s: &str| -> Option<u64> {
307 if s == "*" || s == "x" || s == "X" {
308 None
309 } else {
310 s.parse().ok()
311 }
312 };
313
314 let major = parts
315 .first()
316 .and_then(|s| parse_part(s))
317 .ok_or_else(|| Error::InvalidVersion("invalid wildcard".to_string()))?;
318
319 let minor = parts.get(1).and_then(|s| parse_part(s));
320 let patch = parts.get(2).and_then(|s| parse_part(s));
321
322 Ok(Self {
323 op: Op::Wildcard,
324 major,
325 minor,
326 patch,
327 pre: crate::semver::Prerelease::EMPTY,
328 })
329 }
330
331 pub fn matches(&self, version: &Version) -> bool {
333 match self.op {
334 Op::Exact => self.matches_exact(version),
335 Op::Greater => self.matches_greater(version),
336 Op::GreaterEq => self.matches_greater_eq(version),
337 Op::Less => self.matches_less(version),
338 Op::LessEq => self.matches_less_eq(version),
339 Op::Caret => self.matches_caret(version),
340 Op::Tilde => self.matches_tilde(version),
341 Op::Wildcard => self.matches_wildcard(version),
342 }
343 }
344
345 fn matches_exact(&self, version: &Version) -> bool {
346 version.major == self.major
347 && self.minor.map_or(true, |m| version.minor == m)
348 && self.patch.map_or(true, |p| version.patch == p)
349 && (self.pre.is_empty() || version.pre == self.pre)
350 }
351
352 fn matches_greater(&self, version: &Version) -> bool {
353 let cmp_version = self.to_version();
354 version > &cmp_version
355 }
356
357 fn matches_greater_eq(&self, version: &Version) -> bool {
358 let cmp_version = self.to_version();
359 version >= &cmp_version
360 }
361
362 fn matches_less(&self, version: &Version) -> bool {
363 let cmp_version = self.to_version();
364 if version.is_prerelease() && !cmp_version.is_prerelease() {
367 if version.major == cmp_version.major
369 && version.minor == cmp_version.minor
370 && version.patch == cmp_version.patch
371 {
372 return true;
373 }
374 if version.base() >= cmp_version {
376 return false;
377 }
378 }
379 version < &cmp_version
380 }
381
382 fn matches_less_eq(&self, version: &Version) -> bool {
383 let cmp_version = self.to_version();
384 if version.is_prerelease() && !cmp_version.is_prerelease() {
385 if version.major == cmp_version.major
386 && version.minor == cmp_version.minor
387 && version.patch == cmp_version.patch
388 {
389 return true;
390 }
391 if version.base() > cmp_version {
392 return false;
393 }
394 }
395 version <= &cmp_version
396 }
397
398 fn matches_caret(&self, version: &Version) -> bool {
402 if !self.matches_greater_eq(version) {
404 return false;
405 }
406
407 if self.major != 0 {
409 version.major == self.major
411 } else if self.minor.unwrap_or(0) != 0 {
412 version.major == 0 && version.minor == self.minor.unwrap_or(0)
414 } else {
415 version.major == 0 && version.minor == 0 && version.patch == self.patch.unwrap_or(0)
417 }
418 }
419
420 fn matches_tilde(&self, version: &Version) -> bool {
422 if !self.matches_greater_eq(version) {
424 return false;
425 }
426
427 version.major == self.major && version.minor == self.minor.unwrap_or(0)
429 }
430
431 fn matches_wildcard(&self, version: &Version) -> bool {
433 if version.major != self.major {
434 return false;
435 }
436 if let Some(minor) = self.minor {
437 if version.minor != minor {
438 return false;
439 }
440 }
441 if let Some(patch) = self.patch {
442 if version.patch != patch {
443 return false;
444 }
445 }
446 true
447 }
448
449 fn to_version(&self) -> Version {
451 Version {
452 major: self.major,
453 minor: self.minor.unwrap_or(0),
454 patch: self.patch.unwrap_or(0),
455 pre: self.pre.clone(),
456 build: crate::semver::BuildMetadata::EMPTY,
457 }
458 }
459}
460
461impl fmt::Display for Comparator {
462 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
463 write!(f, "{}", self.op)?;
464
465 if self.op == Op::Wildcard && self.minor.is_none() {
466 return write!(f, "{}.*", self.major);
467 }
468
469 write!(f, "{}", self.major)?;
470 if let Some(minor) = self.minor {
471 write!(f, ".{}", minor)?;
472 if let Some(patch) = self.patch {
473 write!(f, ".{}", patch)?;
474 } else if self.op == Op::Wildcard {
475 write!(f, ".*")?;
476 }
477 } else if self.op == Op::Wildcard {
478 write!(f, ".*")?;
479 }
480
481 if !self.pre.is_empty() {
482 write!(f, "-{}", self.pre)?;
483 }
484
485 Ok(())
486 }
487}
488
489#[cfg(test)]
490mod tests {
491 use super::*;
492
493 #[test]
494 fn test_parse_exact() {
495 let req = VersionReq::parse("1.2.3").unwrap();
496 assert!(req.matches(&Version::new(1, 2, 3)));
497 assert!(!req.matches(&Version::new(1, 2, 4)));
498 }
499
500 #[test]
501 fn test_parse_comparison() {
502 let req = VersionReq::parse(">=1.0.0").unwrap();
503 assert!(req.matches(&Version::new(1, 0, 0)));
504 assert!(req.matches(&Version::new(2, 0, 0)));
505 assert!(!req.matches(&Version::new(0, 9, 0)));
506
507 let req = VersionReq::parse("<2.0.0").unwrap();
508 assert!(req.matches(&Version::new(1, 0, 0)));
509 assert!(!req.matches(&Version::new(2, 0, 0)));
510 }
511
512 #[test]
513 fn test_parse_combined() {
514 let req = VersionReq::parse(">=1.0.0 <2.0.0").unwrap();
515 assert!(req.matches(&Version::new(1, 0, 0)));
516 assert!(req.matches(&Version::new(1, 5, 0)));
517 assert!(!req.matches(&Version::new(0, 9, 0)));
518 assert!(!req.matches(&Version::new(2, 0, 0)));
519 }
520
521 #[test]
522 fn test_parse_caret() {
523 let req = VersionReq::parse("^1.2.3").unwrap();
525 assert!(req.matches(&Version::new(1, 2, 3)));
526 assert!(req.matches(&Version::new(1, 9, 0)));
527 assert!(!req.matches(&Version::new(2, 0, 0)));
528 assert!(!req.matches(&Version::new(1, 2, 2)));
529
530 let req = VersionReq::parse("^0.2.3").unwrap();
532 assert!(req.matches(&Version::new(0, 2, 3)));
533 assert!(req.matches(&Version::new(0, 2, 9)));
534 assert!(!req.matches(&Version::new(0, 3, 0)));
535
536 let req = VersionReq::parse("^0.0.3").unwrap();
538 assert!(req.matches(&Version::new(0, 0, 3)));
539 assert!(!req.matches(&Version::new(0, 0, 4)));
540 }
541
542 #[test]
543 fn test_parse_tilde() {
544 let req = VersionReq::parse("~1.2.3").unwrap();
546 assert!(req.matches(&Version::new(1, 2, 3)));
547 assert!(req.matches(&Version::new(1, 2, 9)));
548 assert!(!req.matches(&Version::new(1, 3, 0)));
549 assert!(!req.matches(&Version::new(1, 2, 2)));
550 }
551
552 #[test]
553 fn test_parse_wildcard() {
554 let req = VersionReq::parse("1.*").unwrap();
555 assert!(req.matches(&Version::new(1, 0, 0)));
556 assert!(req.matches(&Version::new(1, 9, 9)));
557 assert!(!req.matches(&Version::new(2, 0, 0)));
558
559 let req = VersionReq::parse("1.2.*").unwrap();
560 assert!(req.matches(&Version::new(1, 2, 0)));
561 assert!(req.matches(&Version::new(1, 2, 9)));
562 assert!(!req.matches(&Version::new(1, 3, 0)));
563 }
564
565 #[test]
566 fn test_parse_hyphen() {
567 let req = VersionReq::parse("1.0.0 - 2.0.0").unwrap();
568 assert!(req.matches(&Version::new(1, 0, 0)));
569 assert!(req.matches(&Version::new(1, 5, 0)));
570 assert!(req.matches(&Version::new(2, 0, 0)));
571 assert!(!req.matches(&Version::new(0, 9, 0)));
572 assert!(!req.matches(&Version::new(2, 0, 1)));
573 }
574
575 #[test]
576 fn test_parse_or() {
577 let req = VersionReq::parse("^1.0.0 || ^2.0.0").unwrap();
578 assert!(req.matches(&Version::new(1, 5, 0)));
579 assert!(req.matches(&Version::new(2, 5, 0)));
580 assert!(!req.matches(&Version::new(3, 0, 0)));
581 }
582
583 #[test]
584 fn test_star() {
585 let req = VersionReq::parse("*").unwrap();
586 assert!(req.matches(&Version::new(0, 0, 0)));
587 assert!(req.matches(&Version::new(999, 999, 999)));
588
589 let req = VersionReq::parse("").unwrap();
590 assert!(req.matches(&Version::new(1, 0, 0)));
591 }
592
593 #[test]
594 fn test_prerelease_matching() {
595 let req = VersionReq::parse(">=1.0.0-alpha").unwrap();
597 assert!(req.matches(&Version::parse("1.0.0-alpha").unwrap()));
598 assert!(req.matches(&Version::parse("1.0.0-beta").unwrap()));
599 assert!(req.matches(&Version::new(1, 0, 0)));
600 }
601
602 #[test]
603 fn test_display() {
604 assert_eq!(VersionReq::parse("^1.2.3").unwrap().to_string(), "^1.2.3");
605 assert_eq!(
606 VersionReq::parse(">=1.0.0 <2.0.0").unwrap().to_string(),
607 ">=1.0.0 <2.0.0"
608 );
609 assert_eq!(VersionReq::parse("*").unwrap().to_string(), "*");
610 }
611}