1use chrono::{DateTime, Utc};
7use serde::{Deserialize, Serialize};
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct Advisory {
15 pub id: String,
17 pub summary: Option<String>,
19 pub details: Option<String>,
21 pub affected: Vec<Affected>,
23 pub references: Vec<Reference>,
25 pub published: Option<DateTime<Utc>>,
27 pub modified: Option<DateTime<Utc>>,
29 pub aliases: Option<Vec<String>>,
31 pub database_specific: Option<serde_json::Value>,
33 #[serde(default, skip_serializing_if = "Option::is_none")]
35 pub enrichment: Option<Enrichment>,
36}
37
38#[derive(Debug, Clone, Default, Serialize, Deserialize)]
44pub struct Enrichment {
45 #[serde(skip_serializing_if = "Option::is_none")]
48 pub epss_score: Option<f64>,
49 #[serde(skip_serializing_if = "Option::is_none")]
51 pub epss_percentile: Option<f64>,
52 #[serde(skip_serializing_if = "Option::is_none")]
54 pub epss_date: Option<DateTime<Utc>>,
55 #[serde(default)]
57 pub is_kev: bool,
58 #[serde(skip_serializing_if = "Option::is_none")]
60 pub kev_due_date: Option<DateTime<Utc>>,
61 #[serde(skip_serializing_if = "Option::is_none")]
63 pub kev_date_added: Option<DateTime<Utc>>,
64 #[serde(skip_serializing_if = "Option::is_none")]
66 pub kev_ransomware: Option<bool>,
67 #[serde(skip_serializing_if = "Option::is_none")]
69 pub cvss_v3_score: Option<f64>,
70 #[serde(skip_serializing_if = "Option::is_none")]
72 pub cvss_v3_severity: Option<Severity>,
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize)]
76pub struct Affected {
77 pub package: Package,
78 pub ranges: Vec<Range>,
79 pub versions: Vec<String>,
80 pub ecosystem_specific: Option<serde_json::Value>,
81 pub database_specific: Option<serde_json::Value>,
82}
83
84#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct Package {
86 pub ecosystem: String,
87 pub name: String,
88 pub purl: Option<String>,
89}
90
91#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct Range {
93 #[serde(rename = "type")]
94 pub range_type: RangeType,
95 pub events: Vec<Event>,
96 pub repo: Option<String>,
97}
98
99#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
100#[serde(rename_all = "UPPERCASE")]
101pub enum RangeType {
102 Semver,
103 Ecosystem,
104 Git,
105}
106
107#[derive(Debug, Clone, Serialize, Deserialize)]
108#[serde(rename_all = "snake_case")]
109pub enum Event {
110 Introduced(String),
111 Fixed(String),
112 LastAffected(String),
113 Limit(String),
114}
115
116#[derive(Debug, Clone, Serialize, Deserialize)]
117pub struct Reference {
118 #[serde(rename = "type")]
119 pub reference_type: ReferenceType,
120 pub url: String,
121}
122
123#[derive(Debug, Clone, Serialize, Deserialize)]
124#[serde(rename_all = "UPPERCASE")]
125pub enum ReferenceType {
126 Advisory,
127 Article,
128 Report,
129 Fix,
130 Git,
131 Package,
132 Web,
133 Other,
134}
135
136#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
138#[serde(rename_all = "UPPERCASE")]
139pub enum Severity {
140 None,
142 Low,
144 Medium,
146 High,
148 Critical,
150}
151
152impl Severity {
153 pub fn from_cvss_score(score: f64) -> Self {
155 match score {
156 s if s >= 9.0 => Self::Critical,
157 s if s >= 7.0 => Self::High,
158 s if s >= 4.0 => Self::Medium,
159 s if s > 0.0 => Self::Low,
160 _ => Self::None,
161 }
162 }
163
164 pub fn min_score(&self) -> f64 {
166 match self {
167 Self::None => 0.0,
168 Self::Low => 0.1,
169 Self::Medium => 4.0,
170 Self::High => 7.0,
171 Self::Critical => 9.0,
172 }
173 }
174}