use crate::Platform;
use crate::resolve::{
DependencyKind, IncludedDependencyReason, IncludedDependencyVersion, Reasons, Resolved,
SpecificCrateIdent,
};
use semver::Version;
use serde::Serialize;
use std::collections::{BTreeMap, BTreeSet};
#[derive(Serialize, Debug)]
pub struct Added<'a> {
pub ident: SpecificCrateIdent,
pub kind: DependencyKind,
pub has_build_rs: bool,
pub is_proc_macro: bool,
pub platforms: &'a BTreeSet<Platform>,
pub reasons: &'a Reasons,
}
#[derive(Serialize, Debug)]
pub struct Comparison<'a> {
pub ident: SpecificCrateIdent,
pub kind: DependencyKind,
pub has_build_rs: bool,
pub is_proc_macro: bool,
pub platforms: &'a BTreeSet<Platform>,
pub reasons: &'a Reasons,
pub closest_different_old_version: Option<Version>,
pub all_other_old_versions: Vec<Version>,
pub added_in_platforms: BTreeMap<&'a Platform, Vec<&'a IncludedDependencyReason>>,
pub added_in_build: BTreeMap<&'a IncludedDependencyReason, &'a BTreeSet<Platform>>,
pub added_in_non_debug: BTreeMap<&'a IncludedDependencyReason, &'a BTreeSet<Platform>>,
}
impl Comparison<'_> {
fn requires_review(&self) -> bool {
self.closest_different_old_version.is_some()
|| !self.added_in_platforms.is_empty()
|| !self.added_in_build.is_empty()
|| !self.added_in_non_debug.is_empty()
}
}
#[derive(Serialize, Debug)]
pub struct Removed {
pub ident: SpecificCrateIdent,
pub remaining_versions: Vec<Version>,
}
#[derive(Serialize, Debug)]
pub struct Diff<'a> {
pub added: Vec<Added<'a>>,
pub changed: Vec<Comparison<'a>>,
pub removed: Vec<Removed>,
pub filtered_added: Vec<SpecificCrateIdent>,
pub filtered_removed: Vec<SpecificCrateIdent>,
}
impl<'a> Diff<'a> {
fn compare(
name: &'a str,
old: &'a BTreeMap<Version, IncludedDependencyVersion>,
new_version: Version,
new: &'a IncludedDependencyVersion,
) -> Comparison<'a> {
let (closest_old_version, closest_old_info) =
old.range(&new_version..).next().unwrap_or_else(|| {
old.last_key_value()
.expect("Higher ones were already checked, version set is never empty")
});
let closest_different_old_version =
(*closest_old_version != new_version).then(|| closest_old_version.clone());
let all_other_old_versions =
if let Some(ref already_mentioned) = closest_different_old_version {
old.keys()
.filter(|i| *i != already_mentioned)
.cloned()
.collect::<Vec<_>>()
} else {
Vec::new()
};
let added_in_platforms = new
.platforms
.iter()
.filter(|i| !closest_old_info.platforms.contains(i))
.map(|platform| {
let reasons = new
.reasons
.iter()
.filter(|(_, platforms)| platforms.contains(platform))
.map(|(reason, _)| reason)
.collect::<Vec<_>>();
(platform, reasons)
})
.collect();
let added_in_build = if new.kind.run_at_build && !closest_old_info.kind.run_at_build {
new.reasons
.iter()
.filter(|(reason, _)| reason.kind.run_at_build)
.collect()
} else {
BTreeMap::new()
};
let added_in_non_debug =
if !new.kind.only_debug_builds && closest_old_info.kind.only_debug_builds {
new.reasons
.iter()
.filter(|(reason, _)| !reason.kind.only_debug_builds)
.collect()
} else {
BTreeMap::new()
};
Comparison {
ident: SpecificCrateIdent {
name: name.to_owned(),
version: new_version,
},
kind: new.kind,
has_build_rs: new.has_build_rs,
is_proc_macro: new.is_proc_macro,
platforms: &new.platforms,
reasons: &new.reasons,
closest_different_old_version,
all_other_old_versions,
added_in_platforms,
added_in_build,
added_in_non_debug,
}
}
pub fn between(old: &'a Resolved, new: &'a Resolved) -> Self {
let added = new
.included
.iter()
.filter(|(name, _)| !old.included.contains_key(*name))
.flat_map(|(name, versions)| {
versions
.iter()
.map(move |(version, item)| (name, version, item))
})
.map(|(name, version, info)| Added {
ident: SpecificCrateIdent {
name: name.clone(),
version: version.clone(),
},
kind: info.kind,
has_build_rs: info.has_build_rs,
is_proc_macro: info.is_proc_macro,
platforms: &info.platforms,
reasons: &info.reasons,
})
.collect();
let changed = new
.included
.iter()
.filter_map(|(name, new_versions)| {
old.included
.get(name)
.map(|old_versions| (name, old_versions, new_versions))
})
.flat_map(|(name, old_versions, new_versions)| {
new_versions.iter().map(move |(new_version, new_info)| {
Self::compare(name, old_versions, new_version.clone(), new_info)
})
})
.filter(|comparison| comparison.requires_review())
.collect();
let removed = old
.included
.iter()
.filter_map(|(name, versions)| {
let new_versions = new.included.get(name);
let has_change = new_versions
.is_some_and(|new| new.keys().any(|key| !versions.contains_key(key)));
if has_change {
None
} else {
Some((name, versions, new_versions))
}
})
.flat_map(|(name, versions, new_versions)| {
let is_in_new = move |version: &Version| {
new_versions.is_some_and(|new| new.contains_key(version))
};
let remaining_versions = versions
.keys()
.filter(|version| is_in_new(version))
.cloned()
.collect::<Vec<_>>();
versions
.keys()
.filter(move |version| !is_in_new(version))
.map(move |version| Removed {
ident: SpecificCrateIdent {
name: name.clone(),
version: version.clone(),
},
remaining_versions: remaining_versions.clone(),
})
})
.collect();
let in_right_set = |left: &BTreeSet<SpecificCrateIdent>, right: &BTreeSet<_>| {
right
.iter()
.filter(|item| !left.contains(item))
.cloned()
.collect()
};
let filtered_added = in_right_set(&old.filtered, &new.filtered);
let filtered_removed = in_right_set(&old.filtered, &new.filtered);
Diff {
added,
changed,
removed,
filtered_added,
filtered_removed,
}
}
}