Skip to main content

js_semver/
range.rs

1#[cfg(not(feature = "std"))]
2use alloc::{vec, vec::Vec};
3
4use core::fmt;
5use core::str::FromStr;
6
7use crate::error::SemverErrorKind;
8use crate::identifier::{BuildMetadata, PreRelease, validate_build_metadata};
9use crate::number::{MAX_SAFE_INTEGER, parse_nr};
10use crate::version::{Version, compare_core_and_prerelease};
11use crate::{MAX_LENGTH, SemverError};
12
13// --------------------------------------------------------------------------
14// Range types
15// --------------------------------------------------------------------------
16
17/// Comparison operator used in a version comparator.
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub(crate) enum Operator {
20    /// `<` — less than.
21    LessThan,
22    /// `<=` — less than or equal to.
23    LessThanOrEqual,
24    /// `>` — greater than.
25    GreaterThan,
26    /// `>=` — greater than or equal to.
27    GreaterThanOrEqual,
28    /// `=` — exactly equal.
29    Equal,
30}
31
32impl fmt::Display for Operator {
33    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34        f.write_str(match self {
35            Self::LessThan => "<",
36            Self::LessThanOrEqual => "<=",
37            Self::GreaterThan => ">",
38            Self::GreaterThanOrEqual => ">=",
39            Self::Equal => "=",
40        })
41    }
42}
43
44#[derive(Debug, Clone, PartialEq, Eq)]
45struct Comparator {
46    op: Operator,
47    version: Version,
48}
49
50impl Comparator {
51    fn test(&self, v: &Version) -> bool {
52        let cmp = compare_core_and_prerelease(v, &self.version);
53        match self.op {
54            Operator::Equal => cmp == core::cmp::Ordering::Equal,
55            Operator::GreaterThan => cmp == core::cmp::Ordering::Greater,
56            Operator::GreaterThanOrEqual => cmp != core::cmp::Ordering::Less,
57            Operator::LessThan => cmp == core::cmp::Ordering::Less,
58            Operator::LessThanOrEqual => cmp != core::cmp::Ordering::Greater,
59        }
60    }
61}
62
63impl fmt::Display for Comparator {
64    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65        match self.op {
66            Operator::Equal => write!(f, "{}", self.version),
67            Operator::LessThan
68            | Operator::LessThanOrEqual
69            | Operator::GreaterThan
70            | Operator::GreaterThanOrEqual => write!(f, "{}{}", self.op, self.version),
71        }
72    }
73}
74
75#[derive(Debug, Clone, PartialEq, Eq)]
76struct ComparatorSet {
77    comparators: Vec<Comparator>,
78}
79
80impl ComparatorSet {
81    fn test(&self, v: &Version) -> bool {
82        if self.comparators.is_empty() {
83            return v.pre_release.is_empty();
84        }
85
86        if v.pre_release.is_empty() {
87            for comparator in &self.comparators {
88                if !comparator.test(v) {
89                    return false;
90                }
91            }
92            return true;
93        }
94
95        let mut has_matching_prerelease_tuple = false;
96        for comparator in &self.comparators {
97            if !comparator.test(v) {
98                return false;
99            }
100            let comparator_version = &comparator.version;
101            if !comparator_version.pre_release.is_empty()
102                && comparator_version.major == v.major
103                && comparator_version.minor == v.minor
104                && comparator_version.patch == v.patch
105            {
106                has_matching_prerelease_tuple = true;
107            }
108        }
109        has_matching_prerelease_tuple
110    }
111}
112
113/// A version range, e.g. `^1.0.0` or `>=1.0.0 <2.0.0-0`.
114///
115/// Its string form preserves the parsed comparator structure, but may differ
116/// from the original input when wildcards, build metadata, or unrestricted
117/// unions are simplified away.
118///
119/// # Examples
120///
121/// ```rust
122/// use js_semver::Range;
123///
124/// assert_eq!(Range::parse("^1.2.3").unwrap().to_string(), ">=1.2.3 <2.0.0-0");
125/// assert_eq!(Range::parse("^1.2.3 || *").unwrap().to_string(), "*");
126/// assert_eq!(Range::parse("1.x.x+experimental").unwrap().to_string(), ">=1.0.0 <2.0.0-0");
127/// ```
128#[derive(Debug, Clone)]
129pub struct Range {
130    set: ComparatorSets,
131}
132
133#[derive(Debug, Clone)]
134enum ComparatorSets {
135    One(ComparatorSet),
136    Many(Vec<ComparatorSet>),
137}
138
139impl ComparatorSets {
140    fn iter(&self) -> core::slice::Iter<'_, ComparatorSet> {
141        match self {
142            Self::One(set) => core::slice::from_ref(set).iter(),
143            Self::Many(sets) => sets.iter(),
144        }
145    }
146
147    #[cfg(test)]
148    fn len(&self) -> usize {
149        match self {
150            Self::One(_) => 1,
151            Self::Many(sets) => sets.len(),
152        }
153    }
154}
155
156impl Range {
157    /// Parse a range string.
158    ///
159    /// The parsed range is displayed in canonical comparator form.
160    ///
161    /// # Examples
162    ///
163    /// ```rust
164    /// use js_semver::Range;
165    ///
166    /// assert_eq!(Range::parse("^1.2.3").unwrap().to_string(), ">=1.2.3 <2.0.0-0");
167    /// assert_eq!(Range::parse(">=2.0.0").unwrap().to_string(), ">=2.0.0");
168    /// ```
169    ///
170    /// # Errors
171    ///
172    /// Returns [`SemverError`] if `s` is not a valid semver range string.
173    pub fn parse(s: &str) -> Result<Self, SemverError> {
174        parse_range(s)
175    }
176
177    /// Returns `true` if the given [`Version`] satisfies this range.
178    ///
179    /// This follows `node-semver`'s prerelease restriction rule: a prerelease
180    /// version only matches when the range contains a comparator with the same
181    /// `major.minor.patch` tuple and an explicit prerelease.
182    ///
183    /// # Examples
184    ///
185    /// ```rust
186    /// use js_semver::{Range, Version};
187    ///
188    /// let range = Range::parse("^1.2.3").unwrap();
189    ///
190    /// assert!(range.satisfies(&Version::parse("1.5.0").unwrap()));
191    /// assert!(!range.satisfies(&Version::parse("2.0.0").unwrap()));
192    /// ```
193    #[must_use]
194    pub fn satisfies(&self, version: &Version) -> bool {
195        for comparator_set in self.set.iter() {
196            if comparator_set.test(version) {
197                return true;
198            }
199        }
200        false
201    }
202}
203
204impl fmt::Display for Range {
205    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
206        for (i, cs) in self.set.iter().enumerate() {
207            if i > 0 {
208                f.write_str("||")?;
209            }
210            if cs.comparators.is_empty() {
211                f.write_str("*")?;
212            } else {
213                for (j, c) in cs.comparators.iter().enumerate() {
214                    if j > 0 {
215                        f.write_str(" ")?;
216                    }
217                    write!(f, "{c}")?;
218                }
219            }
220        }
221        Ok(())
222    }
223}
224
225impl FromStr for Range {
226    type Err = SemverError;
227    fn from_str(s: &str) -> Result<Self, Self::Err> {
228        parse_range(s)
229    }
230}
231
232// --------------------------------------------------------------------------
233// Partial version (for range parsing)
234// --------------------------------------------------------------------------
235
236#[derive(Debug, Clone)]
237struct Partial {
238    major: Option<u64>,
239    minor: Option<u64>,
240    patch: Option<u64>,
241    pre_release: PreRelease,
242}
243
244impl Partial {
245    fn floor(self) -> Version {
246        Version {
247            major: self.major.unwrap_or(0),
248            minor: self.minor.unwrap_or(0),
249            patch: self.patch.unwrap_or(0),
250            pre_release: self.pre_release,
251            build: BuildMetadata::default(),
252        }
253    }
254}
255
256fn parse_partial(s: &str) -> Result<Partial, SemverError> {
257    let s = s.trim();
258    let s = if has_fully_qualified_numeric_core_after_full_strip(s) {
259        s.strip_prefix(['v', '=']).unwrap_or(s)
260    } else {
261        s.trim_start_matches(['v', '='])
262    };
263    if let Some(partial) = parse_simple_partial(s) {
264        return Ok(partial);
265    }
266    let original_len = s.len();
267    let (s, pre_separator) = strip_build_metadata_and_find_prerelease(s)?;
268    if s.is_empty() || s.starts_with('.') {
269        return Err(SemverErrorKind::MissingVersionSegment.into());
270    }
271    if s.len() != original_len {
272        if let Some(partial) = parse_simple_partial(s) {
273            return Ok(partial);
274        }
275    }
276    let Some(pre_separator) = pre_separator else {
277        return Err(SemverErrorKind::InvalidNumber.into());
278    };
279    let bytes = s.as_bytes();
280    let version_end = pre_separator;
281    let version_core = &s[..version_end];
282    let pre_part = &s[pre_separator + 1..];
283
284    if version_core.is_empty() {
285        return Err(SemverErrorKind::MissingVersionSegment.into());
286    }
287    if bytes.get(version_end.wrapping_sub(1)) == Some(&b'.') {
288        return Err(SemverErrorKind::TrailingDot.into());
289    }
290
291    let (dot1, dot2) = find_component_dots(bytes, version_end, s)?;
292
293    let major_part = if let Some(end) = dot1 {
294        &s[..end]
295    } else {
296        version_core
297    };
298    let major = parse_xr(major_part)?;
299    let minor = match (dot1, dot2) {
300        (Some(start), Some(end)) => parse_xr(&s[start + 1..end])?,
301        (Some(start), None) => parse_xr(&s[start + 1..version_end])?,
302        (None, _) => None,
303    };
304    let patch = match dot2 {
305        Some(start) => parse_xr(&s[start + 1..version_end])?,
306        None => None,
307    };
308
309    let pre_release = if pre_part.is_empty() || pre_part.ends_with('.') {
310        return Err(SemverErrorKind::EmptySegment.into());
311    } else if dot1.is_some()
312        && dot2.is_some()
313        && (major.is_none() || minor.is_none() || patch.is_none())
314    {
315        PreRelease::new(pre_part)?;
316        PreRelease::default()
317    } else {
318        if minor.is_none() || patch.is_none() {
319            return Err(SemverErrorKind::MissingVersionSegment.into());
320        }
321        PreRelease::new(pre_part)?
322    };
323
324    Ok(Partial {
325        major,
326        minor,
327        patch,
328        pre_release,
329    })
330}
331
332fn strip_build_metadata(s: &str) -> Result<&str, SemverError> {
333    let Some(plus) = s.find('+') else {
334        return Ok(s);
335    };
336    let build = &s[plus + 1..];
337    if build.is_empty() {
338        return Err(SemverErrorKind::EmptySegment.into());
339    }
340    validate_build_metadata(build)?;
341    Ok(&s[..plus])
342}
343
344fn strip_build_metadata_and_find_prerelease(s: &str) -> Result<(&str, Option<usize>), SemverError> {
345    let mut pre_separator = None;
346    for (pos, byte) in s.bytes().enumerate() {
347        match byte {
348            b'-' if pre_separator.is_none() => pre_separator = Some(pos),
349            b'+' => {
350                let build = &s[pos + 1..];
351                if build.is_empty() {
352                    return Err(SemverErrorKind::EmptySegment.into());
353                }
354                validate_build_metadata(build)?;
355                return Ok((&s[..pos], pre_separator));
356            }
357            _ => {}
358        }
359    }
360    Ok((s, pre_separator))
361}
362
363fn parse_simple_partial(s: &str) -> Option<Partial> {
364    let bytes = s.as_bytes();
365    let (major, mut pos) = parse_simple_component(bytes, 0)?;
366    let mut minor = None;
367    let mut patch = None;
368
369    if pos < bytes.len() {
370        if bytes[pos] != b'.' {
371            return None;
372        }
373        (minor, pos) = parse_simple_component(bytes, pos + 1)?;
374    }
375
376    if pos < bytes.len() {
377        if bytes[pos] != b'.' {
378            return None;
379        }
380        (patch, pos) = parse_simple_component(bytes, pos + 1)?;
381    }
382
383    if pos != bytes.len() {
384        return None;
385    }
386
387    Some(Partial {
388        major,
389        minor,
390        patch,
391        pre_release: PreRelease::default(),
392    })
393}
394
395fn parse_simple_component(bytes: &[u8], start: usize) -> Option<(Option<u64>, usize)> {
396    match bytes.get(start).copied()? {
397        b'x' | b'X' | b'*' => Some((None, start + 1)),
398        first @ b'0'..=b'9' => {
399            if first == b'0' && bytes.get(start + 1).is_some_and(u8::is_ascii_digit) {
400                return None;
401            }
402
403            let mut pos = start;
404            let mut value = 0u64;
405            while let Some(digit @ b'0'..=b'9') = bytes.get(pos).copied() {
406                if pos - start == 16 {
407                    return None;
408                }
409                value = value * 10 + u64::from(digit - b'0');
410                pos += 1;
411            }
412            (value <= MAX_SAFE_INTEGER).then_some((Some(value), pos))
413        }
414        _ => None,
415    }
416}
417
418fn parse_xr(s: &str) -> Result<Option<u64>, SemverError> {
419    match s {
420        "" | "*" | "x" | "X" => Ok(None),
421        _ => Ok(Some(parse_nr(s)?)),
422    }
423}
424
425fn has_fully_qualified_numeric_core_after_full_strip(s: &str) -> bool {
426    if s.strip_prefix(['v', '=']).is_none() {
427        return false;
428    }
429    let fully_stripped = s.trim_start_matches(['v', '=']);
430    let core_end = fully_stripped
431        .find(['-', '+'])
432        .unwrap_or(fully_stripped.len());
433    let core = &fully_stripped[..core_end];
434    let mut parts = core.split('.');
435    let (Some(major), Some(minor), Some(patch), None) =
436        (parts.next(), parts.next(), parts.next(), parts.next())
437    else {
438        return false;
439    };
440    [major, minor, patch]
441        .into_iter()
442        .all(|part| !part.is_empty() && part.bytes().all(|b| b.is_ascii_digit()))
443}
444
445// --------------------------------------------------------------------------
446// Comparator construction helpers (internal)
447// --------------------------------------------------------------------------
448
449fn find_component_dots(
450    bytes: &[u8],
451    version_end: usize,
452    _raw: &str,
453) -> Result<(Option<usize>, Option<usize>), SemverError> {
454    let mut first = None;
455    let mut second = None;
456    let mut pos = 0;
457    while pos < version_end {
458        if bytes[pos] == b'.' {
459            if pos == 0 || bytes[pos - 1] == b'.' {
460                return Err(SemverErrorKind::EmptySegment.into());
461            }
462            if first.is_none() {
463                first = Some(pos);
464            } else if second.is_none() {
465                second = Some(pos);
466            } else {
467                return Err(SemverErrorKind::UnexpectedDot.into());
468            }
469        }
470        pos += 1;
471    }
472    Ok((first, second))
473}
474
475fn version_with_pre_release(
476    major: u64,
477    minor: u64,
478    patch: u64,
479    pre_release: PreRelease,
480) -> Version {
481    if pre_release.is_empty() {
482        return Version::new(major, minor, patch);
483    }
484
485    Version {
486        major,
487        minor,
488        patch,
489        pre_release,
490        build: BuildMetadata::default(),
491    }
492}
493
494const fn comparator_gte(ver: Version) -> Comparator {
495    Comparator {
496        op: Operator::GreaterThanOrEqual,
497        version: ver,
498    }
499}
500const fn comparator_gt(ver: Version) -> Comparator {
501    Comparator {
502        op: Operator::GreaterThan,
503        version: ver,
504    }
505}
506const fn comparator_lte(ver: Version) -> Comparator {
507    Comparator {
508        op: Operator::LessThanOrEqual,
509        version: ver,
510    }
511}
512const fn comparator_lt(ver: Version) -> Comparator {
513    Comparator {
514        op: Operator::LessThan,
515        version: ver,
516    }
517}
518const fn comparator_eq(ver: Version) -> Comparator {
519    Comparator {
520        op: Operator::Equal,
521        version: ver,
522    }
523}
524
525fn comparator_lt_upper_bound(major: u64, minor: u64, patch: u64) -> Comparator {
526    comparator_lt(version_with_pre_release(
527        major,
528        minor,
529        patch,
530        PreRelease::zero(),
531    ))
532}
533
534fn next_component(value: u64) -> Result<u64, SemverError> {
535    if value >= MAX_SAFE_INTEGER {
536        return Err(SemverErrorKind::MaxSafeIntegerExceeded.into());
537    }
538    Ok(value + 1)
539}
540
541// --------------------------------------------------------------------------
542// Range expansion helpers
543// --------------------------------------------------------------------------
544
545/// Expand a tilde range: `~1.2.3` → `>=1.2.3 <1.3.0-0`.
546fn expand_tilde_into(out: &mut Vec<Comparator>, p: Partial) -> Result<(), SemverError> {
547    match (p.major, p.minor, p.patch) {
548        (None, _, _) => {}
549        (Some(0), None, _) => push_canonical_comparator(out, comparator_lt_upper_bound(1, 0, 0)),
550        (Some(maj), None, _) => {
551            push_canonical_comparator(
552                out,
553                comparator_gte(version_with_pre_release(maj, 0, 0, PreRelease::default())),
554            );
555            push_canonical_comparator(out, comparator_lt_upper_bound(next_component(maj)?, 0, 0));
556        }
557        (Some(maj), Some(mnr), None) => {
558            push_canonical_comparator(
559                out,
560                comparator_gte(version_with_pre_release(maj, mnr, 0, PreRelease::default())),
561            );
562            push_canonical_comparator(out, comparator_lt_upper_bound(maj, next_component(mnr)?, 0));
563        }
564        (Some(maj), Some(mnr), Some(patch)) => {
565            let floor = version_with_pre_release(maj, mnr, patch, p.pre_release);
566            push_canonical_comparator(out, comparator_gte(floor));
567            push_canonical_comparator(out, comparator_lt_upper_bound(maj, next_component(mnr)?, 0));
568        }
569    }
570    Ok(())
571}
572
573#[cfg(test)]
574fn expand_tilde(p: Partial) -> Result<Vec<Comparator>, SemverError> {
575    let mut out = Vec::with_capacity(2);
576    expand_tilde_into(&mut out, p)?;
577    Ok(out)
578}
579
580/// Expand a caret range: `^1.2.3` → `>=1.2.3 <2.0.0-0`.
581fn expand_caret_into(out: &mut Vec<Comparator>, p: Partial) -> Result<(), SemverError> {
582    match (p.major, p.minor, p.patch) {
583        (None, _, _) => {}
584        (Some(0), None, _) => push_canonical_comparator(out, comparator_lt_upper_bound(1, 0, 0)),
585        (Some(maj), None, _) => {
586            push_canonical_comparator(
587                out,
588                comparator_gte(version_with_pre_release(maj, 0, 0, PreRelease::default())),
589            );
590            push_canonical_comparator(out, comparator_lt_upper_bound(next_component(maj)?, 0, 0));
591        }
592        (Some(maj), Some(mnr), None) => {
593            if maj > 0 {
594                push_canonical_comparator(
595                    out,
596                    comparator_gte(version_with_pre_release(maj, mnr, 0, PreRelease::default())),
597                );
598                push_canonical_comparator(
599                    out,
600                    comparator_lt_upper_bound(next_component(maj)?, 0, 0),
601                );
602            } else if mnr > 0 {
603                push_canonical_comparator(
604                    out,
605                    comparator_gte(version_with_pre_release(0, mnr, 0, PreRelease::default())),
606                );
607                push_canonical_comparator(
608                    out,
609                    comparator_lt_upper_bound(0, next_component(mnr)?, 0),
610                );
611            } else {
612                push_canonical_comparator(
613                    out,
614                    comparator_gte(version_with_pre_release(0, 0, 0, PreRelease::default())),
615                );
616                push_canonical_comparator(out, comparator_lt_upper_bound(0, 1, 0));
617            }
618        }
619        (Some(maj), Some(mnr), Some(patch)) => {
620            let floor = version_with_pre_release(maj, mnr, patch, p.pre_release);
621            push_canonical_comparator(out, comparator_gte(floor));
622            if maj > 0 {
623                push_canonical_comparator(
624                    out,
625                    comparator_lt_upper_bound(next_component(maj)?, 0, 0),
626                );
627            } else if mnr > 0 {
628                push_canonical_comparator(
629                    out,
630                    comparator_lt_upper_bound(0, next_component(mnr)?, 0),
631                );
632            } else {
633                push_canonical_comparator(
634                    out,
635                    comparator_lt_upper_bound(0, 0, next_component(patch)?),
636                );
637            }
638        }
639    }
640    Ok(())
641}
642
643#[cfg(test)]
644fn expand_caret(p: Partial) -> Result<Vec<Comparator>, SemverError> {
645    let mut out = Vec::with_capacity(2);
646    expand_caret_into(&mut out, p)?;
647    Ok(out)
648}
649
650/// Expand an x-range or primitive comparator to concrete [`Comparator`]s.
651fn expand_primitive_into(
652    out: &mut Vec<Comparator>,
653    op: Option<Operator>,
654    p: Partial,
655) -> Result<(), SemverError> {
656    if (p.major.is_none() && p.minor.is_some()) || (p.minor.is_none() && p.patch.is_some()) {
657        return Err(SemverErrorKind::MissingVersionSegment.into());
658    }
659    match op {
660        None | Some(Operator::Equal) => expand_equal_primitive(out, p)?,
661        Some(Operator::GreaterThan) => expand_greater_than_primitive(out, p)?,
662        Some(Operator::GreaterThanOrEqual) => expand_greater_than_or_equal_primitive(out, p),
663        Some(Operator::LessThan) => expand_less_than_primitive(out, p),
664        Some(Operator::LessThanOrEqual) => expand_less_than_or_equal_primitive(out, p)?,
665    }
666    Ok(())
667}
668
669fn version_from_partial(p: Partial, major: u64, minor: u64, patch: u64) -> Version {
670    version_with_pre_release(major, minor, patch, p.pre_release)
671}
672
673fn expand_equal_primitive(out: &mut Vec<Comparator>, p: Partial) -> Result<(), SemverError> {
674    match (p.major, p.minor, p.patch) {
675        (None, _, _) => {}
676        (Some(0), None, _) => {
677            push_canonical_comparator(out, comparator_lt_upper_bound(1, 0, 0));
678        }
679        (Some(maj), None, _) => {
680            push_canonical_comparator(
681                out,
682                comparator_gte(version_with_pre_release(maj, 0, 0, PreRelease::default())),
683            );
684            push_canonical_comparator(out, comparator_lt_upper_bound(next_component(maj)?, 0, 0));
685        }
686        (Some(maj), Some(mnr), None) => {
687            push_canonical_comparator(
688                out,
689                comparator_gte(version_with_pre_release(maj, mnr, 0, PreRelease::default())),
690            );
691            push_canonical_comparator(out, comparator_lt_upper_bound(maj, next_component(mnr)?, 0));
692        }
693        (Some(maj), Some(mnr), Some(patch)) => {
694            push_canonical_comparator(out, comparator_eq(version_from_partial(p, maj, mnr, patch)));
695        }
696    }
697    Ok(())
698}
699
700fn expand_greater_than_primitive(out: &mut Vec<Comparator>, p: Partial) -> Result<(), SemverError> {
701    match (p.major, p.minor, p.patch) {
702        (None, _, _) => push_canonical_comparator(out, comparator_lt_upper_bound(0, 0, 0)),
703        (Some(maj), None, _) => {
704            push_canonical_comparator(
705                out,
706                comparator_gte(version_with_pre_release(
707                    next_component(maj)?,
708                    0,
709                    0,
710                    PreRelease::default(),
711                )),
712            );
713        }
714        (Some(maj), Some(mnr), None) => {
715            push_canonical_comparator(
716                out,
717                comparator_gte(version_with_pre_release(
718                    maj,
719                    next_component(mnr)?,
720                    0,
721                    PreRelease::default(),
722                )),
723            );
724        }
725        (Some(maj), Some(mnr), Some(patch)) => {
726            push_canonical_comparator(out, comparator_gt(version_from_partial(p, maj, mnr, patch)));
727        }
728    }
729    Ok(())
730}
731
732fn expand_greater_than_or_equal_primitive(out: &mut Vec<Comparator>, p: Partial) {
733    match (p.major, p.minor, p.patch) {
734        (None, _, _) | (Some(0), None, _) => {}
735        (Some(maj), None, _) => {
736            push_canonical_comparator(
737                out,
738                comparator_gte(version_with_pre_release(maj, 0, 0, PreRelease::default())),
739            );
740        }
741        (Some(maj), Some(mnr), None) => {
742            push_canonical_comparator(
743                out,
744                comparator_gte(version_with_pre_release(maj, mnr, 0, PreRelease::default())),
745            );
746        }
747        (Some(maj), Some(mnr), Some(patch)) => {
748            push_canonical_comparator(
749                out,
750                comparator_gte(version_from_partial(p, maj, mnr, patch)),
751            );
752        }
753    }
754}
755
756fn expand_less_than_primitive(out: &mut Vec<Comparator>, p: Partial) {
757    match (p.major, p.minor, p.patch) {
758        (None, _, _) => push_canonical_comparator(out, comparator_lt_upper_bound(0, 0, 0)),
759        (Some(maj), None, _) => {
760            push_canonical_comparator(out, comparator_lt_upper_bound(maj, 0, 0));
761        }
762        (Some(maj), Some(mnr), None) => {
763            push_canonical_comparator(out, comparator_lt_upper_bound(maj, mnr, 0));
764        }
765        (Some(maj), Some(mnr), Some(patch)) => {
766            push_canonical_comparator(out, comparator_lt(version_from_partial(p, maj, mnr, patch)));
767        }
768    }
769}
770
771fn expand_less_than_or_equal_primitive(
772    out: &mut Vec<Comparator>,
773    p: Partial,
774) -> Result<(), SemverError> {
775    match (p.major, p.minor, p.patch) {
776        (None, _, _) => {}
777        (Some(maj), None, _) => {
778            push_canonical_comparator(out, comparator_lt_upper_bound(next_component(maj)?, 0, 0));
779        }
780        (Some(maj), Some(mnr), None) => {
781            push_canonical_comparator(out, comparator_lt_upper_bound(maj, next_component(mnr)?, 0));
782        }
783        (Some(maj), Some(mnr), Some(patch)) => {
784            push_canonical_comparator(
785                out,
786                comparator_lte(version_from_partial(p, maj, mnr, patch)),
787            );
788        }
789    }
790    Ok(())
791}
792
793#[cfg(test)]
794fn expand_primitive(op: Option<Operator>, p: Partial) -> Result<Vec<Comparator>, SemverError> {
795    let mut out = Vec::with_capacity(2);
796    expand_primitive_into(&mut out, op, p)?;
797    Ok(out)
798}
799
800/// Expand a hyphen range `a - b` to comparators.
801fn expand_hyphen_into(
802    out: &mut Vec<Comparator>,
803    a: Partial,
804    b: Partial,
805) -> Result<(), SemverError> {
806    push_canonical_comparator(out, comparator_gte(a.floor()));
807    match (b.major, b.minor, b.patch) {
808        (None, _, _) => {}
809        (Some(maj), None, _) => {
810            push_canonical_comparator(out, comparator_lt_upper_bound(next_component(maj)?, 0, 0));
811        }
812        (Some(maj), Some(mnr), None) => {
813            push_canonical_comparator(out, comparator_lt_upper_bound(maj, next_component(mnr)?, 0));
814        }
815        (Some(maj), Some(mnr), Some(patch)) => {
816            let ver = version_with_pre_release(maj, mnr, patch, b.pre_release);
817            push_canonical_comparator(out, comparator_lte(ver));
818        }
819    }
820    Ok(())
821}
822
823fn expand_hyphen(a: Partial, b: Partial) -> Result<Vec<Comparator>, SemverError> {
824    let mut out = Vec::with_capacity(2);
825    expand_hyphen_into(&mut out, a, b)?;
826    Ok(out)
827}
828
829// --------------------------------------------------------------------------
830// Range parsing
831// --------------------------------------------------------------------------
832
833fn parse_range(s: &str) -> Result<Range, SemverError> {
834    let s = s.trim();
835    let exceeds_max_length =
836        s.len() > MAX_LENGTH && range_len_without_build_metadata(s) > MAX_LENGTH && {
837            let trimmed_prefix = s.trim_start_matches(['v', '=', '^', '~', '>', '<']);
838            trimmed_prefix.len() > MAX_LENGTH
839                && range_len_without_build_metadata(trimmed_prefix) > MAX_LENGTH
840        };
841
842    let bytes = s.as_bytes();
843    let group_count = count_or_groups(bytes);
844    if group_count == 1 {
845        let comparator_set = parse_comparator_set(s)?;
846        if !comparator_set.comparators.is_empty() && exceeds_max_length {
847            return Err(SemverErrorKind::MaxLengthExceeded.into());
848        }
849        return Ok(Range {
850            set: ComparatorSets::One(comparator_set),
851        });
852    }
853
854    let mut set = Vec::with_capacity(group_count);
855    let mut start = 0;
856    let mut i = 0;
857    while i < bytes.len() {
858        if i + 1 < bytes.len() && bytes[i] == b'|' && bytes[i + 1] == b'|' {
859            let comparator_set = parse_comparator_set(s[start..i].trim())?;
860            set.push(comparator_set);
861            i += 2;
862            start = i;
863        } else {
864            i += 1;
865        }
866    }
867    set.push(parse_comparator_set(s[start..].trim())?);
868
869    let has_unbounded_set = set
870        .iter()
871        .any(|comparator_set| comparator_set.comparators.is_empty());
872
873    if has_unbounded_set && set.len() > 1 && exceeds_max_length {
874        return Err(SemverErrorKind::MaxLengthExceeded.into());
875    }
876
877    if has_unbounded_set {
878        return Ok(Range {
879            set: ComparatorSets::One(ComparatorSet {
880                comparators: vec![],
881            }),
882        });
883    }
884
885    if exceeds_max_length {
886        return Err(SemverErrorKind::MaxLengthExceeded.into());
887    }
888
889    set.dedup();
890
891    if set.len() == 1 {
892        let comparator_set = set.remove(0);
893        return Ok(Range {
894            set: ComparatorSets::One(comparator_set),
895        });
896    }
897
898    Ok(Range {
899        set: ComparatorSets::Many(set),
900    })
901}
902
903fn range_len_without_build_metadata(s: &str) -> usize {
904    let bytes = s.as_bytes();
905    let mut len = 0;
906    let mut pos = 0;
907    while pos < bytes.len() {
908        if bytes[pos] == b'+' {
909            pos += 1;
910            while pos < bytes.len()
911                && matches!(bytes[pos], b'0'..=b'9' | b'A'..=b'Z' | b'a'..=b'z' | b'-' | b'.')
912            {
913                pos += 1;
914            }
915        } else {
916            len += 1;
917            pos += 1;
918        }
919    }
920    len
921}
922
923fn parse_comparator_set(s: &str) -> Result<ComparatorSet, SemverError> {
924    if s.is_empty() || s == "*" {
925        return Ok(ComparatorSet {
926            comparators: vec![],
927        });
928    }
929
930    let bytes = s.as_bytes();
931    if !bytes.iter().any(u8::is_ascii_whitespace) {
932        let mut comparators = Vec::with_capacity(2);
933        parse_token_into(&mut comparators, s)?;
934        return Ok(ComparatorSet { comparators });
935    }
936
937    if let Some(comps) = try_hyphen(s)? {
938        return Ok(ComparatorSet { comparators: comps });
939    }
940
941    let mut all = Vec::with_capacity(count_whitespace_tokens(bytes).saturating_mul(2));
942    let mut pos = 0;
943    while let Some(t) = next_whitespace_token(s, bytes, &mut pos) {
944        let is_op_only = matches!(t, ">" | ">=" | "<" | "<=" | "=" | "^" | "~" | "~=" | "~>");
945        if is_op_only {
946            if let Some(next) = next_whitespace_token(s, bytes, &mut pos) {
947                let mut buf = [0u8; 258];
948                let op = t.as_bytes();
949                let ver = strip_build_metadata(next)?.as_bytes();
950                let len = op.len() + ver.len();
951                if len > buf.len() {
952                    return Err(SemverErrorKind::MaxLengthExceeded.into());
953                }
954                buf[..op.len()].copy_from_slice(op);
955                buf[op.len()..len].copy_from_slice(ver);
956                // SAFETY: `t` and `next` are slices of the original `&str`, so their bytes are
957                // valid UTF-8 after concatenation as well.
958                let merged = unsafe { core::str::from_utf8_unchecked(&buf[..len]) };
959                parse_token_into(&mut all, merged)?;
960            } else {
961                parse_token_into(&mut all, t)?;
962            }
963        } else {
964            parse_token_into(&mut all, t)?;
965        }
966    }
967    Ok(ComparatorSet { comparators: all })
968}
969
970fn next_whitespace_token<'a>(s: &'a str, bytes: &[u8], pos: &mut usize) -> Option<&'a str> {
971    while *pos < bytes.len() && bytes[*pos].is_ascii_whitespace() {
972        *pos += 1;
973    }
974    if *pos >= bytes.len() {
975        return None;
976    }
977    let start = *pos;
978    while *pos < bytes.len() && !bytes[*pos].is_ascii_whitespace() {
979        *pos += 1;
980    }
981    Some(&s[start..*pos])
982}
983
984fn count_or_groups(bytes: &[u8]) -> usize {
985    let mut count = 1;
986    let mut pos = 0;
987    while pos + 1 < bytes.len() {
988        if bytes[pos] == b'|' && bytes[pos + 1] == b'|' {
989            count += 1;
990            pos += 2;
991        } else {
992            pos += 1;
993        }
994    }
995    count
996}
997
998fn count_whitespace_tokens(bytes: &[u8]) -> usize {
999    let mut count = 0;
1000    let mut pos = 0;
1001    while pos < bytes.len() {
1002        while pos < bytes.len() && bytes[pos].is_ascii_whitespace() {
1003            pos += 1;
1004        }
1005        if pos >= bytes.len() {
1006            break;
1007        }
1008        count += 1;
1009        while pos < bytes.len() && !bytes[pos].is_ascii_whitespace() {
1010            pos += 1;
1011        }
1012    }
1013    count
1014}
1015
1016/// Return `Some(comparators)` if `s` is a hyphen range `X - Y`, else `None`.
1017fn try_hyphen(s: &str) -> Result<Option<Vec<Comparator>>, SemverError> {
1018    let bytes = s.as_bytes();
1019    let mut i = 0;
1020    while i + 2 < bytes.len() {
1021        if bytes[i] == b' ' && bytes[i + 1] == b'-' && bytes[i + 2] == b' ' {
1022            let left = s[..i].trim();
1023            let right = s[i + 3..].trim();
1024            let is_op = |c: char| matches!(c, '>' | '<' | '=' | '~' | '^');
1025            if !left.starts_with(is_op) && !right.starts_with(is_op) {
1026                let a = parse_partial(left)?;
1027                let b = parse_partial(right)?;
1028                return Ok(Some(expand_hyphen(a, b)?));
1029            }
1030        }
1031        i += 1;
1032    }
1033    Ok(None)
1034}
1035
1036fn parse_token_into(all: &mut Vec<Comparator>, s: &str) -> Result<(), SemverError> {
1037    let s = s.trim();
1038    if s.is_empty() || s == "*" {
1039        return Ok(());
1040    }
1041    if s.starts_with('+') {
1042        strip_build_metadata(s)?;
1043        return Ok(());
1044    }
1045
1046    if let Some(rest) = s.strip_prefix('~') {
1047        let rest = rest.trim_start_matches(['=', '>']); // ~= and ~> are aliases for ~
1048        return expand_tilde_into(all, parse_required_partial(rest, "~")?);
1049    }
1050    if let Some(rest) = s.strip_prefix('^') {
1051        return expand_caret_into(all, parse_required_partial(rest, "^")?);
1052    }
1053    if let Some(rest) = s.strip_prefix(">=") {
1054        return expand_primitive_into(
1055            all,
1056            Some(Operator::GreaterThanOrEqual),
1057            parse_required_partial(rest, ">=")?,
1058        );
1059    }
1060    if let Some(rest) = s.strip_prefix("<=") {
1061        return expand_primitive_into(
1062            all,
1063            Some(Operator::LessThanOrEqual),
1064            parse_required_partial(rest, "<=")?,
1065        );
1066    }
1067    if let Some(rest) = s.strip_prefix('>') {
1068        return expand_primitive_into(
1069            all,
1070            Some(Operator::GreaterThan),
1071            parse_required_partial(rest, ">")?,
1072        );
1073    }
1074    if let Some(rest) = s.strip_prefix('<') {
1075        return expand_primitive_into(
1076            all,
1077            Some(Operator::LessThan),
1078            parse_required_partial(rest, "<")?,
1079        );
1080    }
1081    if let Some(rest) = s.strip_prefix('=') {
1082        return expand_primitive_into(
1083            all,
1084            Some(Operator::Equal),
1085            parse_required_partial(rest, "=")?,
1086        );
1087    }
1088
1089    expand_primitive_into(all, None, parse_partial(s)?)
1090}
1091
1092fn parse_required_partial(s: &str, operator: &'static str) -> Result<Partial, SemverError> {
1093    let s = s.trim();
1094    if s.is_empty() {
1095        return Err(SemverErrorKind::MissingVersionAfterOperator(operator).into());
1096    }
1097    parse_partial(s)
1098}
1099
1100fn push_canonical_comparator(all: &mut Vec<Comparator>, new: Comparator) {
1101    if is_impossible_comparator(&new) {
1102        all.clear();
1103        all.push(new);
1104        return;
1105    }
1106    for existing in &mut *all {
1107        if is_impossible_comparator(existing) {
1108            return;
1109        }
1110        match (existing.op, new.op) {
1111            (
1112                Operator::LessThan | Operator::LessThanOrEqual,
1113                Operator::LessThan | Operator::LessThanOrEqual,
1114            ) if existing.version.major == new.version.major
1115                && existing.version.minor == new.version.minor
1116                && existing.version.patch == new.version.patch =>
1117            {
1118                let ordering = compare_core_and_prerelease(&existing.version, &new.version);
1119                if ordering == core::cmp::Ordering::Greater
1120                    || (ordering == core::cmp::Ordering::Equal
1121                        && existing.op == Operator::LessThanOrEqual
1122                        && new.op == Operator::LessThan)
1123                {
1124                    *existing = new;
1125                }
1126                return;
1127            }
1128            (Operator::Equal, Operator::Equal)
1129                if compare_core_and_prerelease(&existing.version, &new.version)
1130                    == core::cmp::Ordering::Equal =>
1131            {
1132                return;
1133            }
1134            _ => {}
1135        }
1136    }
1137    all.push(new);
1138}
1139
1140fn is_impossible_comparator(comparator: &Comparator) -> bool {
1141    comparator.op == Operator::LessThan
1142        && comparator.version.major == 0
1143        && comparator.version.minor == 0
1144        && comparator.version.patch == 0
1145        && comparator.version.pre_release == PreRelease::zero()
1146}
1147
1148// --------------------------------------------------------------------------
1149// Tests
1150// --------------------------------------------------------------------------
1151
1152#[cfg(test)]
1153mod tests {
1154    #[cfg(not(feature = "std"))]
1155    use alloc::string::ToString;
1156
1157    use super::*;
1158
1159    #[test]
1160    fn try_hyphen_rejects_non_hyphen_forms() {
1161        assert!(try_hyphen(">=1.0.0 - 2.0.0").unwrap().is_none());
1162        assert!(try_hyphen("1.0.0 - <=2.0.0").unwrap().is_none());
1163        assert!(try_hyphen("1.2.3").unwrap().is_none());
1164        assert!(try_hyphen("1.2.3 -").unwrap().is_none());
1165        assert!(try_hyphen("- 1.2.3").unwrap().is_none());
1166    }
1167
1168    // --- Operator Display ---
1169
1170    #[test]
1171    fn operator_display() {
1172        assert_eq!(Operator::LessThan.to_string(), "<");
1173        assert_eq!(Operator::LessThanOrEqual.to_string(), "<=");
1174        assert_eq!(Operator::GreaterThan.to_string(), ">");
1175        assert_eq!(Operator::GreaterThanOrEqual.to_string(), ">=");
1176        assert_eq!(Operator::Equal.to_string(), "=");
1177    }
1178
1179    #[test]
1180    fn helper_count_and_expand_tilde_caret_coverage() {
1181        assert_eq!(parse_partial("1.2").unwrap().minor, Some(2));
1182        assert_eq!(count_whitespace_tokens(b""), 0);
1183        assert_eq!(count_whitespace_tokens(b">=1.0.0 <2.0.0"), 2);
1184        assert_eq!(count_whitespace_tokens(b"  >=1.0.0   <2.0.0  "), 2);
1185
1186        assert_eq!(expand_tilde(parse_partial("1").unwrap()).unwrap().len(), 2);
1187        assert_eq!(
1188            expand_tilde(parse_partial("1.2").unwrap()).unwrap().len(),
1189            2
1190        );
1191
1192        assert_eq!(expand_caret(parse_partial("1").unwrap()).unwrap().len(), 2);
1193        assert_eq!(
1194            expand_caret(parse_partial("1.2").unwrap()).unwrap().len(),
1195            2
1196        );
1197        assert_eq!(
1198            expand_caret(parse_partial("1.2.3").unwrap()).unwrap().len(),
1199            2
1200        );
1201        assert_eq!(
1202            expand_caret(parse_partial("0.2.3").unwrap()).unwrap().len(),
1203            2
1204        );
1205    }
1206
1207    #[test]
1208    fn helper_build_metadata_stripping_and_length_coverage() {
1209        assert!(has_fully_qualified_numeric_core_after_full_strip("v1.2.3"));
1210        assert!(!has_fully_qualified_numeric_core_after_full_strip("v1.2.x"));
1211        assert!(!has_fully_qualified_numeric_core_after_full_strip("v1..3"));
1212        assert!(!has_fully_qualified_numeric_core_after_full_strip("v1.2"));
1213        assert_eq!(strip_build_metadata("1.2.3+A0-z.9").unwrap(), "1.2.3");
1214        assert_eq!(strip_build_metadata("1.2.3").unwrap(), "1.2.3");
1215        assert!(strip_build_metadata("1.2.3+").is_err());
1216        assert!(strip_build_metadata("1.2.3+bad!").is_err());
1217        assert_eq!(
1218            strip_build_metadata_and_find_prerelease("1.2.3-alpha-1+build").unwrap(),
1219            ("1.2.3-alpha-1", Some(5))
1220        );
1221        assert_eq!(
1222            strip_build_metadata_and_find_prerelease("1.2.3").unwrap(),
1223            ("1.2.3", None)
1224        );
1225        assert!(strip_build_metadata_and_find_prerelease("1.2.3+").is_err());
1226        assert!(strip_build_metadata_and_find_prerelease("1.2.3+bad!").is_err());
1227        assert_eq!(range_len_without_build_metadata("1.2.3+A0-z.9"), 5);
1228        assert_eq!(range_len_without_build_metadata("1.2.3+!"), 6);
1229    }
1230
1231    #[test]
1232    fn helper_expand_primitive_equal_coverage() {
1233        assert_eq!(
1234            expand_primitive(None, parse_partial("1").unwrap())
1235                .unwrap()
1236                .len(),
1237            2
1238        );
1239        assert_eq!(
1240            expand_primitive(None, parse_partial("1.2").unwrap())
1241                .unwrap()
1242                .len(),
1243            2
1244        );
1245        assert_eq!(
1246            expand_primitive(None, parse_partial("0").unwrap())
1247                .unwrap()
1248                .len(),
1249            1
1250        );
1251        assert_eq!(
1252            expand_primitive(None, parse_partial("1.2.3-alpha").unwrap())
1253                .unwrap()
1254                .len(),
1255            1
1256        );
1257    }
1258
1259    #[test]
1260    fn helper_expand_primitive_greater_coverage() {
1261        assert_eq!(
1262            expand_primitive(Some(Operator::GreaterThan), parse_partial("1").unwrap())
1263                .unwrap()
1264                .len(),
1265            1
1266        );
1267        assert_eq!(
1268            expand_primitive(Some(Operator::GreaterThan), parse_partial("1.2").unwrap())
1269                .unwrap()
1270                .len(),
1271            1
1272        );
1273        assert_eq!(
1274            expand_primitive(Some(Operator::GreaterThan), parse_partial("*").unwrap())
1275                .unwrap()
1276                .len(),
1277            1
1278        );
1279        assert_eq!(
1280            expand_primitive(
1281                Some(Operator::GreaterThan),
1282                parse_partial("1.2.3-alpha").unwrap()
1283            )
1284            .unwrap()
1285            .len(),
1286            1
1287        );
1288    }
1289
1290    #[test]
1291    fn helper_expand_primitive_greater_equal_coverage() {
1292        assert_eq!(
1293            expand_primitive(
1294                Some(Operator::GreaterThanOrEqual),
1295                parse_partial("1").unwrap()
1296            )
1297            .unwrap()
1298            .len(),
1299            1
1300        );
1301        assert_eq!(
1302            expand_primitive(
1303                Some(Operator::GreaterThanOrEqual),
1304                parse_partial("1.2").unwrap()
1305            )
1306            .unwrap()
1307            .len(),
1308            1
1309        );
1310        assert_eq!(
1311            expand_primitive(
1312                Some(Operator::GreaterThanOrEqual),
1313                parse_partial("*").unwrap()
1314            )
1315            .unwrap()
1316            .len(),
1317            0
1318        );
1319        assert_eq!(
1320            expand_primitive(
1321                Some(Operator::GreaterThanOrEqual),
1322                parse_partial("1.2.3-alpha").unwrap()
1323            )
1324            .unwrap()
1325            .len(),
1326            1
1327        );
1328    }
1329
1330    #[test]
1331    fn helper_expand_primitive_less_coverage() {
1332        assert_eq!(
1333            expand_primitive(Some(Operator::LessThan), parse_partial("1").unwrap())
1334                .unwrap()
1335                .len(),
1336            1
1337        );
1338        assert_eq!(
1339            expand_primitive(
1340                Some(Operator::LessThan),
1341                parse_partial("1.2.3-alpha").unwrap()
1342            )
1343            .unwrap()
1344            .len(),
1345            1
1346        );
1347    }
1348
1349    #[test]
1350    fn helper_expand_primitive_less_equal_coverage() {
1351        assert_eq!(
1352            expand_primitive(Some(Operator::LessThanOrEqual), parse_partial("1").unwrap())
1353                .unwrap()
1354                .len(),
1355            1
1356        );
1357        assert_eq!(
1358            expand_primitive(
1359                Some(Operator::LessThanOrEqual),
1360                parse_partial("1.2").unwrap()
1361            )
1362            .unwrap()
1363            .len(),
1364            1
1365        );
1366        assert_eq!(
1367            expand_primitive(
1368                Some(Operator::LessThanOrEqual),
1369                parse_partial("1.2.3-alpha").unwrap()
1370            )
1371            .unwrap()
1372            .len(),
1373            1
1374        );
1375    }
1376
1377    #[test]
1378    fn helper_expand_hyphen_and_parse_range_coverage() {
1379        assert_eq!(
1380            expand_hyphen(parse_partial("1.0.0").unwrap(), parse_partial("2").unwrap())
1381                .unwrap()
1382                .len(),
1383            2
1384        );
1385        assert_eq!(
1386            expand_hyphen(
1387                parse_partial("1.0.0").unwrap(),
1388                parse_partial("2.5").unwrap()
1389            )
1390            .unwrap()
1391            .len(),
1392            2
1393        );
1394        assert!(try_hyphen("1.0.0 - 2.0.0").unwrap().is_some());
1395    }
1396
1397    #[test]
1398    fn helper_expand_error_paths() {
1399        let partial = parse_partial("1.2").unwrap();
1400        assert_eq!(partial.major, Some(1));
1401        assert_eq!(partial.minor, Some(2));
1402        assert_eq!(partial.patch, None);
1403        assert!(expand_tilde(parse_partial("9007199254740991").unwrap()).is_err());
1404        assert!(expand_tilde(parse_partial("1.9007199254740991").unwrap()).is_err());
1405        assert!(expand_tilde(parse_partial("1.9007199254740991.0").unwrap()).is_err());
1406
1407        assert!(expand_caret(parse_partial("9007199254740991").unwrap()).is_err());
1408        assert!(expand_caret(parse_partial("9007199254740991.1").unwrap()).is_err());
1409        assert!(expand_caret(parse_partial("0.9007199254740991").unwrap()).is_err());
1410        assert!(expand_caret(parse_partial("0.9007199254740991.1").unwrap()).is_err());
1411        assert!(expand_caret(parse_partial("0.0.9007199254740991").unwrap()).is_err());
1412        assert_eq!(
1413            expand_caret(parse_partial("1.2").unwrap()).unwrap().len(),
1414            2
1415        );
1416        assert_eq!(
1417            expand_caret(parse_partial("0.2").unwrap()).unwrap().len(),
1418            2
1419        );
1420        let mut out = Vec::new();
1421        expand_caret_into(&mut out, parse_partial("0.2").unwrap()).unwrap();
1422        assert_eq!(out.len(), 2);
1423
1424        assert!(expand_primitive(None, parse_partial("9007199254740991").unwrap()).is_err());
1425        assert!(expand_primitive(None, parse_partial("1.9007199254740991").unwrap()).is_err());
1426        assert!(
1427            expand_primitive(
1428                Some(Operator::GreaterThan),
1429                parse_partial("9007199254740991").unwrap()
1430            )
1431            .is_err()
1432        );
1433        assert!(
1434            expand_primitive(
1435                Some(Operator::GreaterThan),
1436                parse_partial("1.9007199254740991").unwrap()
1437            )
1438            .is_err()
1439        );
1440        assert!(
1441            expand_primitive(
1442                Some(Operator::LessThanOrEqual),
1443                parse_partial("9007199254740991").unwrap()
1444            )
1445            .is_err()
1446        );
1447        assert!(
1448            expand_primitive(
1449                Some(Operator::LessThanOrEqual),
1450                parse_partial("1.9007199254740991").unwrap()
1451            )
1452            .is_err()
1453        );
1454
1455        assert!(
1456            expand_hyphen(
1457                parse_partial("1.0.0").unwrap(),
1458                parse_partial("9007199254740991").unwrap()
1459            )
1460            .is_err()
1461        );
1462        assert!(
1463            expand_hyphen(
1464                parse_partial("1.0.0").unwrap(),
1465                parse_partial("1.9007199254740991").unwrap()
1466            )
1467            .is_err()
1468        );
1469        assert!(parse_partial("1.bad").is_err());
1470        assert!(parse_partial("1.bad-alpha").is_err());
1471        assert!(parse_partial("1.bad.3-alpha").is_err());
1472        assert!(parse_partial("1-alpha").is_err());
1473        assert!(parse_partial("bad-alpha").is_err());
1474        assert!(parse_partial("-alpha").is_err());
1475        assert!(parse_partial("1.-alpha").is_err());
1476        assert!(parse_partial("1..2-alpha").is_err());
1477        assert!(parse_partial("1.2.3.4-alpha").is_err());
1478        assert!(parse_partial("1.2-rc.0").is_err());
1479        assert!(parse_partial("2.x-rc.0").is_err());
1480        assert!(parse_partial("1.2.3+").is_err());
1481        assert!(parse_partial("10000000000000000").is_err());
1482        assert_eq!(parse_range("^1.0.0").unwrap().set.len(), 1);
1483        assert_eq!(parse_range("1.0.0 || 2.0.0").unwrap().set.len(), 2);
1484        assert_eq!(parse_range("1.0.0 || 2.0.0 || 3.0.0").unwrap().set.len(), 3);
1485        assert!(parse_range(">= || 1.0.0").is_err());
1486        assert!(parse_range("1.0.0 || >=").is_err());
1487        assert!(parse_comparator_set(">= ").is_err());
1488        let mut long_bounded_range = "1.0.0 || ".repeat(29);
1489        long_bounded_range.push_str("1.0.0");
1490        assert!(parse_range(&long_bounded_range).is_err());
1491        assert_eq!(try_hyphen("1.0.0 - 2.0.0").unwrap().unwrap().len(), 2);
1492        assert!(try_hyphen("1.0.0 - 9007199254740991").is_err());
1493    }
1494
1495    #[test]
1496    fn public_and_comparator_helpers_are_used_in_crate_tests() {
1497        let version = Version::parse("1.2.3").unwrap();
1498        let prerelease = Version::parse("1.2.3-alpha.1").unwrap();
1499
1500        let eq = Comparator {
1501            op: Operator::Equal,
1502            version: version.clone(),
1503        };
1504        let lt = Comparator {
1505            op: Operator::LessThan,
1506            version: Version::parse("2.0.0").unwrap(),
1507        };
1508        let set = ComparatorSet {
1509            comparators: vec![eq.clone(), lt.clone()],
1510        };
1511        let range = Range::parse("1.2.3").unwrap();
1512
1513        assert!(eq.test(&version));
1514        assert!(
1515            Comparator {
1516                op: Operator::GreaterThan,
1517                version: Version::parse("1.2.2").unwrap(),
1518            }
1519            .test(&version)
1520        );
1521        assert!(
1522            Comparator {
1523                op: Operator::LessThanOrEqual,
1524                version: version.clone(),
1525            }
1526            .test(&version)
1527        );
1528        assert_eq!(eq.to_string(), "1.2.3");
1529        assert!(set.test(&version));
1530        assert!(!set.test(&prerelease));
1531        assert_eq!(
1532            Range::parse("^1.2.3").unwrap().to_string(),
1533            ">=1.2.3 <2.0.0-0"
1534        );
1535        assert!(range.satisfies(&version));
1536        assert!(!Range::parse("2.x || 3.x").unwrap().satisfies(&version));
1537        assert_eq!(
1538            compare_core_and_prerelease(&version, &Version::parse("1.2.4").unwrap()),
1539            core::cmp::Ordering::Less
1540        );
1541    }
1542
1543    #[test]
1544    fn comparator_set_test_covers_release_and_prerelease_paths() {
1545        let release = Version::parse("1.2.3").unwrap();
1546        let prerelease = Version::parse("1.2.3-alpha.1").unwrap();
1547        let next_release = Version::parse("1.2.4").unwrap();
1548        let matching_pre = Version::parse("1.2.3-alpha.0").unwrap();
1549
1550        let empty = ComparatorSet {
1551            comparators: Vec::new(),
1552        };
1553        assert!(empty.test(&release));
1554        assert!(!empty.test(&prerelease));
1555
1556        let release_ok = ComparatorSet {
1557            comparators: vec![Comparator {
1558                op: Operator::Equal,
1559                version: release.clone(),
1560            }],
1561        };
1562        assert!(release_ok.test(&release));
1563        assert!(!release_ok.test(&next_release));
1564
1565        let prerelease_without_match = ComparatorSet {
1566            comparators: vec![Comparator {
1567                op: Operator::GreaterThanOrEqual,
1568                version: release.clone(),
1569            }],
1570        };
1571        assert!(!prerelease_without_match.test(&prerelease));
1572
1573        let prerelease_passes_but_tuple_does_not_match = ComparatorSet {
1574            comparators: vec![
1575                Comparator {
1576                    op: Operator::GreaterThan,
1577                    version: Version::parse("1.0.0").unwrap(),
1578                },
1579                Comparator {
1580                    op: Operator::LessThanOrEqual,
1581                    version: Version::parse("2.0.0").unwrap(),
1582                },
1583            ],
1584        };
1585        assert!(!prerelease_passes_but_tuple_does_not_match.test(&prerelease));
1586
1587        let prerelease_with_match = ComparatorSet {
1588            comparators: vec![Comparator {
1589                op: Operator::GreaterThanOrEqual,
1590                version: matching_pre,
1591            }],
1592        };
1593        assert!(prerelease_with_match.test(&prerelease));
1594    }
1595
1596    #[test]
1597    fn compare_core_and_prerelease_covers_all_major_paths() {
1598        assert_eq!(
1599            compare_core_and_prerelease(
1600                &Version::parse("2.0.0").unwrap(),
1601                &Version::parse("1.9.9").unwrap()
1602            ),
1603            core::cmp::Ordering::Greater
1604        );
1605        assert_eq!(
1606            compare_core_and_prerelease(
1607                &Version::parse("1.3.0").unwrap(),
1608                &Version::parse("1.2.9").unwrap()
1609            ),
1610            core::cmp::Ordering::Greater
1611        );
1612        assert_eq!(
1613            compare_core_and_prerelease(
1614                &Version::parse("1.2.4").unwrap(),
1615                &Version::parse("1.2.3").unwrap()
1616            ),
1617            core::cmp::Ordering::Greater
1618        );
1619        assert_eq!(
1620            compare_core_and_prerelease(
1621                &Version::parse("1.2.3").unwrap(),
1622                &Version::parse("1.2.3-alpha.1").unwrap()
1623            ),
1624            core::cmp::Ordering::Greater
1625        );
1626        assert_eq!(
1627            compare_core_and_prerelease(
1628                &Version::parse("1.2.3-alpha.1").unwrap(),
1629                &Version::parse("1.2.3-alpha.2").unwrap()
1630            ),
1631            core::cmp::Ordering::Less
1632        );
1633        assert_eq!(
1634            compare_core_and_prerelease(
1635                &Version::parse("1.2.3-alpha.1").unwrap(),
1636                &Version::parse("1.2.3-alpha.1").unwrap()
1637            ),
1638            core::cmp::Ordering::Equal
1639        );
1640    }
1641}