use serde::Serialize;
#[derive(Debug, Serialize)]
pub(crate) struct Report {
schema: Schema,
summary: Summary,
vulnerabilities: Vec<Vulnerability>,
adverse_statuses: Vec<AdverseStatus>,
}
impl Report {
pub(crate) fn from_findings(
n_packages: usize,
vulnerabilities: &[&uv_audit::Vulnerability],
statuses: &[&uv_audit::ProjectStatus],
) -> Self {
let mut vulnerabilities = vulnerabilities
.iter()
.copied()
.map(Vulnerability::from)
.collect::<Vec<_>>();
vulnerabilities.sort_by(|first, second| {
first
.dependency
.name
.cmp(&second.dependency.name)
.then_with(|| first.dependency.version.cmp(&second.dependency.version))
.then_with(|| first.display_id.cmp(&second.display_id))
});
let mut adverse_statuses = statuses
.iter()
.copied()
.map(AdverseStatus::from)
.collect::<Vec<_>>();
adverse_statuses.sort_by(|first, second| {
first
.name
.cmp(&second.name)
.then_with(|| first.status.cmp(&second.status))
});
Self {
schema: Schema::default(),
summary: Summary {
audited_packages: n_packages,
vulnerabilities: vulnerabilities.len(),
adverse_statuses: adverse_statuses.len(),
},
vulnerabilities,
adverse_statuses,
}
}
}
#[derive(Debug, Serialize, Default)]
struct Schema {
version: SchemaVersion,
}
#[derive(Debug, Serialize, Default)]
#[serde(rename_all = "snake_case")]
enum SchemaVersion {
#[default]
Preview,
}
#[derive(Debug, Serialize)]
struct Summary {
audited_packages: usize,
vulnerabilities: usize,
adverse_statuses: usize,
}
#[derive(Debug, Serialize)]
struct Dependency {
name: String,
version: String,
}
impl From<&uv_audit::Dependency> for Dependency {
fn from(dependency: &uv_audit::Dependency) -> Self {
Self {
name: dependency.name().to_string(),
version: dependency.version().to_string(),
}
}
}
#[derive(Debug, Serialize)]
struct Vulnerability {
dependency: Dependency,
id: String,
display_id: String,
aliases: Vec<String>,
summary: Option<String>,
description: Option<String>,
link: Option<String>,
fix_versions: Vec<String>,
published: Option<String>,
modified: Option<String>,
}
impl From<&uv_audit::Vulnerability> for Vulnerability {
fn from(vulnerability: &uv_audit::Vulnerability) -> Self {
Self {
dependency: Dependency::from(&vulnerability.dependency),
id: vulnerability.id.as_str().to_string(),
display_id: vulnerability.best_id().as_str().to_string(),
aliases: vulnerability
.aliases
.iter()
.map(|id| id.as_str().to_string())
.collect(),
summary: vulnerability.summary.clone(),
description: vulnerability.description.clone(),
link: vulnerability
.link
.as_ref()
.map(|link| link.as_str().to_string()),
fix_versions: vulnerability
.fix_versions
.iter()
.map(std::string::ToString::to_string)
.collect(),
published: vulnerability
.published
.as_ref()
.map(std::string::ToString::to_string),
modified: vulnerability
.modified
.as_ref()
.map(std::string::ToString::to_string),
}
}
}
#[derive(Debug, Serialize)]
struct AdverseStatus {
name: String,
status: String,
reason: Option<String>,
}
impl From<&uv_audit::ProjectStatus> for AdverseStatus {
fn from(status: &uv_audit::ProjectStatus) -> Self {
Self {
name: status.name.to_string(),
status: status.status.to_string(),
reason: status.reason.clone(),
}
}
}