use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
use crate::index_metadata::IndexDep;
use crate::publish_metadata::RegistryDep;
pub const CRATESIO_INDEX: &str = "https://github.com/rust-lang/crates.io-index";
#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
pub struct CrateData {
pub name: String,
pub owners: Vec<String>,
pub max_version: String,
pub total_downloads: i64,
pub last_updated: String,
pub homepage: Option<String>,
pub description: Option<String>,
pub repository: Option<String>,
pub categories: Vec<String>,
pub keywords: Vec<String>,
pub authors: Vec<String>,
pub versions: Vec<CrateVersionData>,
}
#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
pub struct CrateVersionData {
pub version: String,
pub created: String,
pub downloads: i64,
pub readme: Option<String>,
pub license: Option<String>,
pub license_file: Option<String>,
pub documentation: Option<String>,
pub dependencies: Vec<CrateRegistryDep>,
pub checksum: String,
pub features: BTreeMap<String, Vec<String>>,
pub yanked: bool,
pub links: Option<String>,
pub v: i32,
pub size: i64,
pub rust_version: Option<String>,
pub publisher: Option<String>,
pub publisher_token: Option<String>,
}
#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
pub struct CrateRegistryDep {
pub name: String,
pub description: Option<String>,
pub version_req: String,
pub features: Option<Vec<String>>,
pub optional: bool,
pub default_features: bool,
pub target: Option<String>,
pub kind: Option<String>,
pub registry: Option<String>,
pub explicit_name_in_toml: Option<String>,
pub latest_version: Option<String>,
pub outdated: bool,
}
impl From<RegistryDep> for CrateRegistryDep {
fn from(dep: RegistryDep) -> Self {
CrateRegistryDep {
name: dep.name,
description: None,
version_req: dep.version_req,
features: dep.features,
optional: dep.optional,
default_features: dep.default_features,
target: dep.target,
kind: dep.kind,
registry: dep.registry,
explicit_name_in_toml: dep.explicit_name_in_toml,
latest_version: None,
outdated: false,
}
}
}
impl CrateRegistryDep {
pub fn from_index(desc: Option<String>, latest_version: Option<String>, dep: IndexDep) -> Self {
let outdated = latest_version
.as_deref()
.is_some_and(|latest| is_outdated(&dep.req, latest));
CrateRegistryDep {
name: match dep.package {
Some(ref package) => package.clone(),
None => dep.name.clone(),
},
description: desc,
version_req: dep.req,
features: Some(dep.features),
optional: dep.optional,
default_features: dep.default_features,
target: dep.target,
kind: dep.kind.map(|k| k.to_string()),
registry: dep.registry,
explicit_name_in_toml: match dep.package {
Some(_) => Some(dep.name),
None => None,
},
latest_version,
outdated,
}
}
}
pub fn is_outdated(req: &str, latest: &str) -> bool {
let (Ok(req), Ok(latest)) = (
semver::VersionReq::parse(req),
semver::Version::parse(latest),
) else {
return false;
};
if req.matches(&latest) {
return false;
}
let below_lower_bound = req.comparators.iter().any(|c| {
let bound = semver::Version::new(c.major, c.minor.unwrap_or(0), c.patch.unwrap_or(0));
match c.op {
semver::Op::Less | semver::Op::LessEq => false,
semver::Op::Greater => latest <= bound,
_ => latest < bound,
}
});
!below_lower_bound
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn latest_version_outside_the_requirement_is_outdated() {
assert!(is_outdated("^2.0", "3.1.0"));
assert!(is_outdated("2.0", "3.1.0"));
assert!(is_outdated("~1.2", "1.3.0"));
assert!(is_outdated("=1.0.0", "1.0.1"));
assert!(is_outdated(">=1, <2", "2.0.0"));
assert!(is_outdated("^0.3", "0.4.0"));
assert!(is_outdated("<2", "3.0.0"));
}
#[test]
fn latest_version_inside_the_requirement_is_not_outdated() {
assert!(!is_outdated("^2.0", "2.4.0"));
assert!(!is_outdated("*", "9.0.0"));
assert!(!is_outdated(">=1", "3.0.0"));
}
#[test]
fn requirement_newer_than_the_latest_version_is_not_outdated() {
assert!(!is_outdated("^5", "4.0.0"));
assert!(!is_outdated(">=2.1", "2.0.0"));
assert!(!is_outdated(">2.0.0", "2.0.0"));
assert!(!is_outdated("=2.0.0", "1.0.0"));
}
#[test]
fn unparsable_versions_are_not_outdated() {
assert!(!is_outdated("not a req", "1.0.0"));
assert!(!is_outdated("^1", "latest"));
}
}