use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
use crate::crate_data::is_outdated;
use crate::index_metadata::IndexDep;
#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
pub struct CrateDependents {
pub latest_version: Option<String>,
pub dependents: Vec<CrateDependent>,
pub restricted: u64,
}
#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
pub struct CrateDependent {
pub name: String,
pub version: String,
pub requirements: Vec<DependentRequirement>,
pub outdated: bool,
}
#[derive(
Default, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, ToSchema,
)]
pub struct DependentRequirement {
pub version_req: String,
pub kind: String,
pub outdated: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LocalDependency {
pub name: String,
pub version_req: String,
pub kind: String,
}
impl LocalDependency {
pub fn from_index_dep(dep: &IndexDep) -> Option<Self> {
if dep.registry.is_some() {
return None;
}
Some(LocalDependency {
name: dep.package.as_ref().unwrap_or(&dep.name).to_lowercase(),
version_req: dep.req.clone(),
kind: dep
.kind
.as_ref()
.map_or_else(|| "normal".to_string(), ToString::to_string),
})
}
}
impl CrateDependent {
pub fn new(
name: String,
version: String,
requirements: impl IntoIterator<Item = (String, String)>,
latest_version: Option<&str>,
) -> Self {
let mut requirements: Vec<DependentRequirement> = requirements
.into_iter()
.map(|(version_req, kind)| DependentRequirement {
outdated: latest_version.is_some_and(|latest| is_outdated(&version_req, latest)),
version_req,
kind,
})
.collect();
requirements.sort();
requirements.dedup();
CrateDependent {
name,
version,
outdated: requirements.iter().any(|r| r.outdated),
requirements,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::index_metadata::DependencyKind;
fn dep(name: &str, req: &str, kind: Option<DependencyKind>) -> IndexDep {
IndexDep {
name: name.to_string(),
req: req.to_string(),
features: vec![],
optional: false,
default_features: true,
target: None,
kind,
registry: None,
package: None,
}
}
fn dependent(requirements: &[(&str, &str)], latest: Option<&str>) -> CrateDependent {
CrateDependent::new(
"bar".to_string(),
"1.0.0".to_string(),
requirements
.iter()
.map(|(req, kind)| ((*req).to_string(), (*kind).to_string())),
latest,
)
}
#[test]
fn dependent_on_old_major_is_outdated() {
let dependent = dependent(&[("^1", "normal")], Some("2.0.0"));
assert_eq!("bar", dependent.name);
assert_eq!("1.0.0", dependent.version);
assert!(dependent.outdated);
assert_eq!(
vec![DependentRequirement {
version_req: "^1".to_string(),
kind: "normal".to_string(),
outdated: true,
}],
dependent.requirements
);
}
#[test]
fn dependent_on_latest_is_not_outdated() {
let dependent = dependent(&[("^2", "normal")], Some("2.0.0"));
assert!(!dependent.outdated);
}
#[test]
fn dependent_is_outdated_if_any_requirement_is() {
let dependent = dependent(
&[("^2", "normal"), ("^1", "dev"), ("^1", "dev")],
Some("2.0.0"),
);
assert!(dependent.outdated);
assert_eq!(2, dependent.requirements.len());
assert_eq!("dev", dependent.requirements[0].kind);
assert!(!dependent.requirements[1].outdated);
}
#[test]
fn without_latest_version_nothing_is_outdated() {
assert!(!dependent(&[("^1", "normal")], None).outdated);
}
#[test]
fn local_dependency_uses_normalized_package_name() {
let mut renamed = dep("foo_renamed", "^2", Some(DependencyKind::Dev));
renamed.package = Some("Foo".to_string());
assert_eq!(
Some(LocalDependency {
name: "foo".to_string(),
version_req: "^2".to_string(),
kind: "dev".to_string(),
}),
LocalDependency::from_index_dep(&renamed)
);
}
#[test]
fn local_dependency_without_kind_is_normal() {
let local = LocalDependency::from_index_dep(&dep("foo", "^1", None)).unwrap();
assert_eq!("normal", local.kind);
}
#[test]
fn dependency_from_other_registry_is_not_local() {
let mut cratesio = dep("foo", "^1", None);
cratesio.registry = Some("https://github.com/rust-lang/crates.io-index".to_string());
assert_eq!(None, LocalDependency::from_index_dep(&cratesio));
}
}