use std::cmp::Ordering;
use cargo_metadata::semver::{BuildMetadata, Comparator, Op, Prerelease, Version, VersionReq};
pub fn compare_version_req(old: &VersionReq, new: &VersionReq) -> (Ordering, Ordering) {
const fn version(major: u64, minor: u64, patch: u64, pre: Prerelease) -> Version {
Version {
major,
minor,
patch,
pre,
build: BuildMetadata::EMPTY,
}
}
fn lower_bound(c: &Comparator) -> Option<Version> {
match c.op {
Op::Exact | Op::GreaterEq => Some(version(
c.major,
c.minor.unwrap_or(0),
c.patch.unwrap_or(0),
c.pre.clone(),
)),
Op::Less => None,
_ => unreachable!(),
}
}
fn upper_bound(c: &Comparator) -> Option<Version> {
match c.op {
Op::Exact | Op::Less => Some(version(
c.major,
c.minor.unwrap_or(0),
c.patch.unwrap_or(0),
c.pre.clone(),
)),
Op::GreaterEq => None,
_ => unreachable!(),
}
}
if old == new {
return (Ordering::Equal, Ordering::Equal);
}
let old_comps: Vec<_> = old.comparators.iter().flat_map(normalize_version_req).collect();
let new_comps: Vec<_> = new.comparators.iter().flat_map(normalize_version_req).collect();
let old_lower_bound = old_comps.iter().filter_map(lower_bound).max();
let new_lower_bound = new_comps.iter().filter_map(lower_bound).max();
let old_upper_bound = old_comps.iter().filter_map(upper_bound).min();
let new_upper_bound = new_comps.iter().filter_map(upper_bound).min();
let lower_bound_cmp = match (old_lower_bound, new_lower_bound) {
(None, None) => Ordering::Equal,
(Some(old_bound), Some(new_bound)) => new_bound.cmp(&old_bound),
(Some(_), None) => Ordering::Less,
(None, Some(_)) => Ordering::Greater,
};
let upper_bound_cmp = match (old_upper_bound, new_upper_bound) {
(None, None) => Ordering::Equal,
(Some(old_bound), Some(new_bound)) => new_bound.cmp(&old_bound),
(Some(_), None) => Ordering::Greater,
(None, Some(_)) => Ordering::Less,
};
(lower_bound_cmp, upper_bound_cmp)
}
#[expect(clippy::too_many_lines)]
fn normalize_version_req(comp: &Comparator) -> Vec<Comparator> {
const fn comparator(op: Op, major: u64, minor: Option<u64>, patch: Option<u64>, pre: Prerelease) -> Comparator {
Comparator {
op,
major,
minor,
patch,
pre,
}
}
let mut result = Vec::with_capacity(2);
match comp.op {
Op::Exact => match (comp.minor, comp.patch) {
(None, None) => {
result.push(comparator(
Op::GreaterEq,
comp.major,
Some(0),
Some(0),
Prerelease::EMPTY,
));
result.push(comparator(
Op::Less,
comp.major + 1,
Some(0),
Some(0),
Prerelease::EMPTY,
));
},
(Some(minor), None) => {
result.push(comparator(
Op::GreaterEq,
comp.major,
Some(minor),
Some(0),
Prerelease::EMPTY,
));
result.push(comparator(
Op::Less,
comp.major,
Some(minor + 1),
Some(0),
Prerelease::EMPTY,
));
},
(Some(minor), Some(patch)) => {
result.push(comparator(
Op::Exact,
comp.major,
Some(minor),
Some(patch),
comp.pre.clone(),
));
},
(None, Some(_)) => unreachable!(),
},
Op::Greater => match (comp.minor, comp.patch) {
(None, None) => {
result.push(comparator(
Op::GreaterEq,
comp.major + 1,
Some(0),
Some(0),
Prerelease::EMPTY,
));
},
(Some(minor), None) => {
result.push(comparator(
Op::GreaterEq,
comp.major,
Some(minor + 1),
Some(0),
Prerelease::EMPTY,
));
},
(Some(minor), Some(patch)) => {
result.push(comparator(
Op::GreaterEq,
comp.major,
Some(minor),
Some(patch + 1),
Prerelease::EMPTY,
));
},
(None, Some(_)) => unreachable!(),
},
Op::GreaterEq => match (comp.minor, comp.patch) {
(None, None) => {
result.push(comparator(
Op::GreaterEq,
comp.major,
Some(0),
Some(0),
Prerelease::EMPTY,
));
},
(Some(minor), None) => {
result.push(comparator(
Op::GreaterEq,
comp.major,
Some(minor),
Some(0),
Prerelease::EMPTY,
));
},
(Some(minor), Some(patch)) => {
result.push(comparator(
Op::GreaterEq,
comp.major,
Some(minor),
Some(patch),
comp.pre.clone(),
));
},
(None, Some(_)) => unreachable!(),
},
Op::Less => match (comp.minor, comp.patch) {
(None, None) => {
result.push(comparator(Op::Less, comp.major, Some(0), Some(0), Prerelease::EMPTY));
},
(Some(minor), None) => {
result.push(comparator(
Op::Less,
comp.major,
Some(minor),
Some(0),
Prerelease::EMPTY,
));
},
(Some(minor), Some(patch)) => {
result.push(comparator(
Op::Less,
comp.major,
Some(minor),
Some(patch),
comp.pre.clone(),
));
},
(None, Some(_)) => unreachable!(),
},
Op::LessEq => match (comp.minor, comp.patch) {
(None, None) => {
result.push(comparator(
Op::Less,
comp.major + 1,
Some(0),
Some(0),
Prerelease::EMPTY,
));
},
(Some(minor), None) => {
result.push(comparator(
Op::Less,
comp.major,
Some(minor + 1),
Some(0),
Prerelease::EMPTY,
));
},
(Some(minor), Some(patch)) => {
result.push(comparator(
Op::Less,
comp.major,
Some(minor),
Some(patch + 1),
Prerelease::EMPTY,
));
},
(None, Some(_)) => unreachable!(),
},
Op::Tilde => match (comp.minor, comp.patch) {
(None, None) => {
result.extend(normalize_version_req(&comparator(
Op::Exact,
comp.major,
None,
None,
Prerelease::EMPTY,
)));
},
(Some(minor), None) => {
result.extend(normalize_version_req(&comparator(
Op::Exact,
comp.major,
Some(minor),
None,
Prerelease::EMPTY,
)));
},
(Some(minor), Some(patch)) => {
result.push(comparator(
Op::GreaterEq,
comp.major,
Some(minor),
Some(patch),
comp.pre.clone(),
));
result.push(comparator(
Op::Less,
comp.major,
Some(minor + 1),
Some(0),
Prerelease::EMPTY,
));
},
(None, Some(_)) => unreachable!(),
},
Op::Caret => match (comp.major, comp.minor, comp.patch) {
(major, None, None) => {
result.extend(normalize_version_req(&comparator(
Op::Exact,
major,
None,
None,
Prerelease::EMPTY,
)));
},
(0, Some(0), None) => {
result.extend(normalize_version_req(&comparator(
Op::Exact,
0,
Some(0),
None,
Prerelease::EMPTY,
)));
},
(major, Some(minor), None) => {
result.extend(normalize_version_req(&comparator(
Op::Caret,
major,
Some(minor),
Some(0),
Prerelease::EMPTY,
)));
},
(0, Some(0), Some(patch)) => {
result.extend(normalize_version_req(&comparator(
Op::Exact,
0,
Some(0),
Some(patch),
comp.pre.clone(),
)));
},
(0, Some(minor), Some(patch)) => {
result.push(comparator(Op::GreaterEq, 0, Some(minor), Some(patch), comp.pre.clone()));
result.push(comparator(Op::Less, 0, Some(minor + 1), Some(0), Prerelease::EMPTY));
},
(major, Some(minor), Some(patch)) => {
result.push(comparator(
Op::GreaterEq,
major,
Some(minor),
Some(patch),
comp.pre.clone(),
));
result.push(comparator(Op::Less, major + 1, Some(0), Some(0), Prerelease::EMPTY));
},
(_, None, Some(_)) => unreachable!(),
},
Op::Wildcard => match (comp.minor, comp.patch) {
(None, None) => {
result.extend(normalize_version_req(&comparator(
Op::Exact,
comp.major,
None,
None,
Prerelease::EMPTY,
)));
},
(Some(minor), None) => {
result.extend(normalize_version_req(&comparator(
Op::Exact,
comp.major,
Some(minor),
None,
Prerelease::EMPTY,
)));
},
(Some(_) | None, Some(_)) => unreachable!(),
},
_ => unimplemented!(),
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_compare_version_req() {
let vr = |s| VersionReq::parse(s).unwrap();
assert_eq!(
compare_version_req(&vr("^1.0"), &vr("^2.0")),
(Ordering::Greater, Ordering::Greater)
);
assert_eq!(
compare_version_req(&vr("^2.0"), &vr("^1.0")),
(Ordering::Less, Ordering::Less)
);
assert_eq!(
compare_version_req(&vr(">=1.0,<3"), &vr("^2.0")),
(Ordering::Greater, Ordering::Equal)
);
assert_eq!(
compare_version_req(&vr("^2.0"), &vr(">=1.0,<3")),
(Ordering::Less, Ordering::Equal)
);
}
#[test]
fn test_normalize_version_req() {
let ct = |s| Comparator::parse(s).unwrap();
assert_eq!(normalize_version_req(&ct("^1.0")), vec![ct(">=1.0.0"), ct("<2.0.0")]);
assert_eq!(normalize_version_req(&ct("^0.1")), vec![ct(">=0.1.0"), ct("<0.2.0")]);
assert_eq!(normalize_version_req(&ct("^0.0.1")), vec![ct("=0.0.1")]);
assert_eq!(normalize_version_req(&ct("~1.1")), vec![ct(">=1.1.0"), ct("<1.2.0")]);
assert_eq!(normalize_version_req(&ct("~0.1")), vec![ct(">=0.1.0"), ct("<0.2.0")]);
assert_eq!(normalize_version_req(&ct("=1")), vec![ct(">=1.0.0"), ct("<2.0.0")]);
assert_eq!(normalize_version_req(&ct("=1.1")), vec![ct(">=1.1.0"), ct("<1.2.0")]);
assert_eq!(normalize_version_req(&ct("=1.1.0")), vec![ct("=1.1.0")]);
}
}