use crate::{
advisory,
database::{Database, Query},
lockfile::Lockfile,
platforms::target::{Arch, OS},
vulnerability::Vulnerability,
warning::Warning,
};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Report {
pub database: DatabaseInfo,
pub lockfile: LockfileInfo,
pub settings: Settings,
pub vulnerabilities: VulnerabilityInfo,
pub warnings: Vec<Warning>,
}
impl Report {
pub fn generate(db: &Database, lockfile: &Lockfile, settings: &Settings) -> Self {
let vulnerabilities = db
.query_vulnerabilities(lockfile, &settings.query())
.into_iter()
.filter(|vuln| !settings.ignore.contains(&vuln.advisory.id))
.collect();
Self {
database: DatabaseInfo::new(db),
lockfile: LockfileInfo::new(lockfile),
settings: settings.clone(),
vulnerabilities: VulnerabilityInfo::new(vulnerabilities),
warnings: find_warnings(db, lockfile, settings),
}
}
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct Settings {
pub target_arch: Option<Arch>,
pub target_os: Option<OS>,
pub severity: Option<advisory::Severity>,
pub ignore: Vec<advisory::Id>,
pub informational_warnings: Vec<advisory::Informational>,
}
impl Settings {
pub fn query(&self) -> Query {
let mut query = Query::crate_scope();
if let Some(target_arch) = self.target_arch {
query = query.target_arch(target_arch);
}
if let Some(target_os) = self.target_os {
query = query.target_os(target_os);
}
if let Some(severity) = self.severity {
query = query.severity(severity);
}
query
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct DatabaseInfo {
#[serde(rename = "advisory-count")]
pub advisory_count: usize,
#[serde(rename = "last-commit")]
pub last_commit: String,
#[serde(rename = "last-updated")]
pub last_updated: DateTime<Utc>,
}
impl DatabaseInfo {
pub fn new(db: &Database) -> Self {
Self {
advisory_count: db.iter().count(),
last_commit: db.latest_commit().commit_id.clone(),
last_updated: db.latest_commit().time,
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct LockfileInfo {
#[serde(rename = "dependency-count")]
dependency_count: usize,
}
impl LockfileInfo {
pub fn new(lockfile: &Lockfile) -> Self {
Self {
dependency_count: lockfile.packages.len(),
}
}
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct VulnerabilityInfo {
pub found: bool,
pub count: usize,
pub list: Vec<Vulnerability>,
}
impl VulnerabilityInfo {
pub fn new(list: Vec<Vulnerability>) -> Self {
Self {
found: !list.is_empty(),
count: list.len(),
list,
}
}
}
pub fn find_warnings(db: &Database, lockfile: &Lockfile, settings: &Settings) -> Vec<Warning> {
let query = settings.query().informational(true);
let mut result = vec![];
for advisory_vuln in db.query_vulnerabilities(lockfile, &query) {
let advisory = &advisory_vuln.advisory;
if settings.ignore.contains(&advisory.id) {
continue;
}
if settings
.informational_warnings
.iter()
.any(|info| Some(info) == advisory.informational.as_ref())
{
result.push(Warning::new(
advisory,
&advisory_vuln.versions,
&advisory_vuln.package,
))
}
}
result
}