use super::{
range::OsvRange,
unaffected_range::{Bound, UnaffectedRange},
};
use crate::{
advisory::{versions::RawVersions, Versions},
Error,
};
use semver::{Prerelease, Version, VersionReq};
pub fn ranges_for_advisory(versions: &Versions) -> Vec<OsvRange> {
unaffected_to_osv_ranges(versions.unaffected(), versions.patched()).unwrap()
}
pub(crate) fn ranges_for_unvalidated_advisory(
versions: &RawVersions,
) -> Result<Vec<OsvRange>, Error> {
unaffected_to_osv_ranges(&versions.unaffected, &versions.patched)
}
fn unaffected_to_osv_ranges(
unaffected_req: &[VersionReq],
patched_req: &[VersionReq],
) -> Result<Vec<OsvRange>, Error> {
let mut unaffected: Vec<UnaffectedRange> = Vec::new();
for req in unaffected_req {
unaffected.push(req.try_into()?);
}
for req in patched_req {
unaffected.push(req.try_into()?);
}
if unaffected.is_empty() {
return Ok(vec![OsvRange {
introduced: None,
fixed: None,
}]);
}
for (idx, a) in unaffected[..unaffected.len() - 1].iter().enumerate() {
for b in unaffected[idx + 1..].iter() {
if a.overlaps(b) {
fail!(
crate::ErrorKind::BadParam,
format!("Overlapping version ranges: {} and {}", a, b)
);
}
}
}
let mut unaffected = unaffected.to_vec();
use std::cmp::Ordering;
unaffected.sort_unstable_by(|a, b| {
match (a.start().version(), b.start().version()) {
(None, _) => Ordering::Less,
(_, None) => Ordering::Greater,
(Some(v1), Some(v2)) => {
assert!(v1 != v2); v1.cmp(v2)
}
}
});
let mut result = Vec::new();
match &unaffected.first().unwrap().start() {
Bound::Unbounded => {} Bound::Exclusive(v) => result.push(OsvRange {
introduced: None,
fixed: Some(increment(v)),
}),
Bound::Inclusive(v) => result.push(OsvRange {
introduced: None,
fixed: Some(v.clone()),
}),
}
for r in unaffected.windows(2) {
let start = match &r[0].end() {
Bound::Unbounded => unreachable!(),
Bound::Exclusive(v) => v.clone(),
Bound::Inclusive(v) => increment(v),
};
let end = match &r[1].start() {
Bound::Unbounded => unreachable!(),
Bound::Exclusive(v) => increment(v),
Bound::Inclusive(v) => v.clone(),
};
result.push(OsvRange {
introduced: Some(start),
fixed: Some(end),
});
}
match &unaffected.last().unwrap().end() {
Bound::Unbounded => {} Bound::Exclusive(v) => result.push(OsvRange {
introduced: Some(v.clone()),
fixed: None,
}),
Bound::Inclusive(v) => result.push(OsvRange {
introduced: Some(increment(v)),
fixed: None,
}),
}
Ok(result)
}
fn increment(v: &Version) -> Version {
let mut v = v.clone();
v.build = Default::default(); if v.pre.is_empty() {
v.patch += 1;
v.pre = Prerelease::new("0").unwrap();
} else {
let incremented = v.pre.to_string() + ".0";
v.pre = Prerelease::new(&incremented).unwrap();
}
v
}
#[cfg(test)]
mod tests {
use super::increment;
use semver::Version;
#[test]
fn increment_simple() {
let input = Version::parse("1.2.3").unwrap();
let incremented = increment(&input);
assert!(incremented > input);
let expected = Version::parse("1.2.4-0").unwrap();
assert_eq!(expected, incremented);
}
#[test]
fn increment_prerelease_numeric() {
let input = Version::parse("1.2.3-9").unwrap();
let incremented = increment(&input);
assert!(incremented > input);
let intuitively_next = Version::parse("1.2.3-10").unwrap();
assert!(incremented < intuitively_next);
let lots_of_zeroes = Version::parse("1.2.3-9.0.0.0").unwrap();
assert!(incremented < lots_of_zeroes);
let expected = Version::parse("1.2.3-9.0").unwrap();
assert_eq!(expected, incremented);
}
#[test]
fn increment_prerelease_numeric_multipart() {
let input = Version::parse("1.2.3-4.5.6").unwrap();
let incremented = increment(&input);
assert!(incremented > input);
let intuitively_next = Version::parse("1.2.3-4.5.7").unwrap();
assert!(incremented < intuitively_next);
let expected = Version::parse("1.2.3-4.5.6.0").unwrap();
assert_eq!(expected, incremented);
}
#[test]
fn increment_prerelease_alphanumeric() {
let input = Version::parse("1.2.3-alpha1").unwrap();
let incremented = increment(&input);
assert!(incremented > input);
let intuitively_next = Version::parse("1.2.3-alpha2").unwrap();
assert!(incremented < intuitively_next);
let lexicographically_next = Version::parse("1.2.3-alpha1-").unwrap();
assert!(incremented < lexicographically_next);
let expected = Version::parse("1.2.3-alpha1.0").unwrap();
assert_eq!(expected, incremented);
}
#[test]
fn increment_prerelease_textual_multipart() {
let input = Version::parse("1.2.3-alpha.1.foo").unwrap();
let incremented = increment(&input);
assert!(incremented > input);
let expected = Version::parse("1.2.3-alpha.1.foo.0").unwrap();
assert_eq!(expected, incremented);
}
}