use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Ecosystem {
Npm,
Cargo,
PyPI,
Go,
Ruby,
Php,
Maven,
NuGet,
}
impl Ecosystem {
pub const ALL: [Ecosystem; 8] = [
Ecosystem::Npm,
Ecosystem::Cargo,
Ecosystem::PyPI,
Ecosystem::Go,
Ecosystem::Ruby,
Ecosystem::Php,
Ecosystem::Maven,
Ecosystem::NuGet,
];
pub fn deps_dev_system(self) -> &'static str {
match self {
Ecosystem::Npm => "npm",
Ecosystem::Cargo => "cargo",
Ecosystem::PyPI => "pypi",
Ecosystem::Go => "go",
Ecosystem::Ruby => "rubygems",
Ecosystem::Php => "packagist", Ecosystem::Maven => "maven",
Ecosystem::NuGet => "nuget",
}
}
pub fn osv_ecosystem(self) -> &'static str {
match self {
Ecosystem::Npm => "npm",
Ecosystem::Cargo => "crates.io",
Ecosystem::PyPI => "PyPI",
Ecosystem::Go => "Go",
Ecosystem::Ruby => "RubyGems",
Ecosystem::Php => "Packagist",
Ecosystem::Maven => "Maven",
Ecosystem::NuGet => "NuGet",
}
}
pub fn label(self) -> &'static str {
match self {
Ecosystem::Npm => "npm",
Ecosystem::Cargo => "crates.io",
Ecosystem::PyPI => "PyPI",
Ecosystem::Go => "Go",
Ecosystem::Ruby => "RubyGems",
Ecosystem::Php => "Packagist",
Ecosystem::Maven => "Maven",
Ecosystem::NuGet => "NuGet",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Dependency {
pub name: String,
pub requested: Option<String>,
pub ecosystem: Ecosystem,
pub direct: bool,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct Vuln {
pub id: String,
pub cvss: Option<f64>,
pub title: Option<String>,
#[serde(default)]
pub aliases: Vec<String>,
#[serde(default)]
pub fixed_version: Option<String>,
#[serde(default)]
pub reference: Option<String>,
#[serde(default)]
pub severity_label: Option<String>,
}
impl Vuln {
pub fn severity(&self) -> Severity {
if let Some(s) = self.cvss {
return if s >= 9.0 {
Severity::Critical
} else if s >= 7.0 {
Severity::High
} else if s >= 4.0 {
Severity::Medium
} else {
Severity::Low
};
}
match self.severity_label.as_deref().map(str::to_ascii_uppercase) {
Some(l) if l == "CRITICAL" => Severity::Critical,
Some(l) if l == "HIGH" => Severity::High,
Some(l) if l == "MODERATE" || l == "MEDIUM" => Severity::Medium,
Some(l) if l == "LOW" => Severity::Low,
_ => Severity::Medium,
}
}
pub fn is_fixable(&self) -> bool {
self.fixed_version.is_some()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Severity {
Low,
Medium,
High,
Critical,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Facts {
pub analyzed_version: Option<String>,
pub latest_published: Option<DateTime<Utc>>,
pub releases_last_year: Option<u32>,
pub total_versions: Option<u32>,
pub deprecated: bool,
pub deprecated_reason: Option<String>,
pub archived: bool,
pub licenses: Vec<String>,
pub vulns: Vec<Vuln>,
pub repo: Option<String>,
pub stars: Option<u64>,
pub open_issues: Option<u64>,
pub scorecard_maintained: Option<f64>,
pub scorecard_overall: Option<f64>,
pub top_contributor_share: Option<f64>,
#[serde(default)]
pub maintainers: Vec<String>,
#[serde(default)]
pub has_install_script: bool,
}
impl Facts {
pub fn is_unresolved(&self) -> bool {
self.total_versions.is_none() && self.latest_published.is_none()
}
}