use indexmap::IndexMap;
use semver::Version;
use crate::cargo_meta::CargoMeta;
use crate::crate_diff_info::CrateDiffInfo;
use crate::crate_diff_request::CrateDiffRequest;
use crate::package_id_info::PackageIdInfo;
use crate::registry_manager::RegistryManager;
pub struct CrateDiffBuilder {
registry_manager: RegistryManager,
diff_rs: bool,
}
impl CrateDiffBuilder {
pub fn new(registry_manager: RegistryManager, diff_rs: bool) -> Self {
Self {
registry_manager,
diff_rs,
}
}
pub fn build_from_crate(
&mut self,
cargo_meta: &CargoMeta,
) -> IndexMap<String, Vec<CrateDiffInfo>> {
let mut target_version_diffs = IndexMap::new();
let dependencies = cargo_meta.workspace_member_dependencies();
for (target_name, deps) in dependencies {
let mut diff_info = vec![];
for dep in deps {
let info = self.registry_manager.get_crate_info(&dep.name, None);
if self.diff_rs {
if let Some(latest_version) = &info.version
&& dep.version == *latest_version
{
continue;
}
diff_info.push(CrateDiffInfo {
name: dep.name,
from_version: Some(dep.version),
from_hash: None,
to_version: info.version,
to_hash: None,
repository: None,
});
} else if let Some(latest_version) = info.version {
if dep.version != latest_version {
let from_hash = self.registry_manager.get_pkg_hash(&dep);
let to_hash = self
.registry_manager
.get_crate_hash(&dep.name, &latest_version);
diff_info.push(CrateDiffInfo {
name: dep.name,
from_version: Some(dep.version),
from_hash,
to_version: Some(latest_version),
to_hash,
repository: info.repository, });
}
} else {
let from_hash = self.registry_manager.get_pkg_hash(&dep);
diff_info.push(CrateDiffInfo {
name: dep.name,
from_version: Some(dep.version),
from_hash,
to_version: None,
to_hash: None,
repository: info.repository, });
}
}
target_version_diffs.insert(target_name, diff_info);
}
target_version_diffs
}
pub fn build_from_crate_deps(
&mut self,
crates: &[CrateDiffRequest],
cargo_meta: &CargoMeta,
) -> IndexMap<String, Vec<CrateDiffInfo>> {
let mut target_version_diffs = IndexMap::new();
for pkg in crates {
let mut from_versions = match &pkg.from_version {
Some(version) => IndexMap::from([("".to_string(), Some(version.clone()))]),
None => {
cargo_meta
.get_dependency_info(&pkg.crate_name)
.into_iter()
.map(|(name, info)| (name, Some(info.version)))
.collect()
}
};
let info = self
.registry_manager
.get_crate_info(&pkg.crate_name, pkg.to_version.as_ref());
let to_version = info.version;
let repository = if self.diff_rs { None } else { info.repository };
if from_versions.is_empty() {
from_versions.insert("".into(), None);
}
for (target_name, from_version) in from_versions {
self.add_diff(
&target_name,
&pkg.crate_name,
from_version,
to_version.clone(),
repository.clone(),
&mut target_version_diffs,
);
}
}
target_version_diffs
}
pub fn build_from_crates(
&mut self,
crates: &[CrateDiffRequest],
) -> IndexMap<String, Vec<CrateDiffInfo>> {
let mut target_version_diffs = IndexMap::new();
for pkg in crates {
let info = self
.registry_manager
.get_crate_info(&pkg.crate_name, pkg.to_version.as_ref());
let to_version = info.version;
let repository = if self.diff_rs { None } else { info.repository };
self.add_diff(
"", &pkg.crate_name,
pkg.from_version.clone(),
to_version,
repository,
&mut target_version_diffs,
);
}
target_version_diffs
}
pub fn build_nested_deps(
&mut self,
diff: &CrateDiffInfo,
) -> (Vec<CrateDiffInfo>, Vec<CrateDiffInfo>, Vec<CrateDiffInfo>) {
let from_nested_packages = self.get_nested_packages(&diff.name, diff.from_version.as_ref());
let mut to_nested_packages = self.get_nested_packages(&diff.name, diff.to_version.as_ref());
let mut removed_deps = vec![];
let mut updated_deps = vec![];
for from_pkg in from_nested_packages {
let pkg_idx = to_nested_packages
.iter()
.enumerate()
.find_map(|(i, to_pkg)| {
if from_pkg.name == to_pkg.name {
Some(i)
} else {
None
}
});
let info = self
.registry_manager
.get_crate_info(&from_pkg.name, Some(&from_pkg.version));
let repository = if self.diff_rs { None } else { info.repository };
if let Some(index) = pkg_idx {
let to_pkg = to_nested_packages.remove(index);
if from_pkg.version != to_pkg.version {
let (from_hash, to_hash) = if !self.diff_rs {
let from_hash = self.registry_manager.get_pkg_hash(&from_pkg);
let to_hash = self.registry_manager.get_pkg_hash(&to_pkg);
(from_hash, to_hash)
} else {
(None, None)
};
updated_deps.push(CrateDiffInfo {
name: from_pkg.name,
from_version: Some(from_pkg.version),
from_hash,
to_version: Some(to_pkg.version),
to_hash,
repository,
});
}
} else {
let from_hash = if !self.diff_rs {
self.registry_manager.get_pkg_hash(&from_pkg)
} else {
None
};
removed_deps.push(CrateDiffInfo {
name: from_pkg.name,
from_version: Some(from_pkg.version),
from_hash,
to_version: None,
to_hash: None,
repository,
});
}
}
let mut added_deps = vec![];
for dep in to_nested_packages {
let (to_hash, repository) = if !self.diff_rs {
let to_hash = self.registry_manager.get_pkg_hash(&dep);
let info = self
.registry_manager
.get_crate_info(&dep.name, Some(&dep.version));
(to_hash, info.repository)
} else {
(None, None)
};
added_deps.push(CrateDiffInfo {
name: dep.name,
from_version: None,
from_hash: None,
to_version: Some(dep.version),
to_hash,
repository,
});
}
(removed_deps, added_deps, updated_deps)
}
fn get_nested_packages(
&self,
crate_name: &str,
version: Option<&Version>,
) -> Vec<PackageIdInfo> {
let Some(version) = version else {
return vec![];
};
let from_registry_path = self.registry_manager.get_crate_path(crate_name, version);
let cargo_meta = match CargoMeta::new(&from_registry_path) {
Ok(cargo_meta) => cargo_meta,
Err(err) => {
eprintln!(
"[ERROR] Cannot get cargo metadata for '{crate_name}' crate. Error: {err}"
);
return vec![];
}
};
cargo_meta.workspace_nested_packages()
}
fn add_diff(
&self,
target_name: &str,
crate_name: &str,
from_version: Option<Version>,
to_version: Option<Version>,
repository: Option<String>,
target_version_diffs: &mut IndexMap<String, Vec<CrateDiffInfo>>,
) {
if let Some(from_version) = &from_version
&& let Some(to_version) = &to_version
&& from_version == to_version
{
return;
}
let (from_hash, to_hash, repository) = if !self.diff_rs {
let from_hash = from_version
.as_ref()
.and_then(|version| self.registry_manager.get_crate_hash(crate_name, version));
let to_hash = to_version
.as_ref()
.and_then(|version| self.registry_manager.get_crate_hash(crate_name, version));
(from_hash, to_hash, repository)
} else {
(None, None, None)
};
let deps = target_version_diffs.entry(target_name.into()).or_default();
deps.push(CrateDiffInfo {
name: crate_name.into(),
from_version,
from_hash,
to_version,
to_hash,
repository, });
}
}