use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Advisory {
pub id: String,
pub summary: Option<String>,
pub details: Option<String>,
#[serde(default)]
pub affected: Vec<Affected>,
#[serde(default)]
pub references: Vec<Reference>,
pub published: Option<DateTime<Utc>>,
pub modified: Option<DateTime<Utc>>,
pub aliases: Option<Vec<String>>,
pub database_specific: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub enrichment: Option<Enrichment>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Enrichment {
#[serde(skip_serializing_if = "Option::is_none")]
pub epss_score: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub epss_percentile: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub epss_date: Option<DateTime<Utc>>,
#[serde(default)]
pub is_kev: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub kev_due_date: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub kev_date_added: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub kev_ransomware: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cvss_v3_score: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cvss_v3_severity: Option<Severity>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Affected {
pub package: Package,
#[serde(default)]
pub ranges: Vec<Range>,
#[serde(default)]
pub versions: Vec<String>,
pub ecosystem_specific: Option<serde_json::Value>,
pub database_specific: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Package {
pub ecosystem: String,
pub name: String,
pub purl: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Range {
#[serde(rename = "type")]
pub range_type: RangeType,
pub events: Vec<Event>,
pub repo: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum RangeTranslationStatus {
Exact,
Lossy,
Unsupported,
Invalid,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RangeTranslation {
pub source: String,
pub raw: Option<String>,
pub status: RangeTranslationStatus,
#[serde(skip_serializing_if = "Option::is_none")]
pub reason: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum RangeType {
Semver,
Ecosystem,
Git,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Event {
Introduced(String),
Fixed(String),
LastAffected(String),
Limit(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Reference {
#[serde(rename = "type")]
pub reference_type: ReferenceType,
pub url: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(rename_all = "UPPERCASE")]
pub enum ReferenceType {
Advisory,
Article,
Detection,
Discussion,
Evidence,
Fix,
Git,
Introduced,
Package,
Report,
Web,
#[default]
#[serde(other)]
Other,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum Severity {
None,
Low,
Medium,
High,
Critical,
}
impl Severity {
pub fn from_cvss_score(score: f64) -> Self {
match score {
s if s >= 9.0 => Self::Critical,
s if s >= 7.0 => Self::High,
s if s >= 4.0 => Self::Medium,
s if s > 0.0 => Self::Low,
_ => Self::None,
}
}
pub fn min_score(&self) -> f64 {
match self {
Self::None => 0.0,
Self::Low => 0.1,
Self::Medium => 4.0,
Self::High => 7.0,
Self::Critical => 9.0,
}
}
}