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 #[serde(default)]
23 pub affected: Vec<Affected>,
24 #[serde(default)]
26 pub references: Vec<Reference>,
27 pub published: Option<DateTime<Utc>>,
29 pub modified: Option<DateTime<Utc>>,
31 pub aliases: Option<Vec<String>>,
33 pub database_specific: Option<serde_json::Value>,
35 #[serde(default, skip_serializing_if = "Option::is_none")]
37 pub enrichment: Option<Enrichment>,
38}
39
40#[derive(Debug, Clone, Default, Serialize, Deserialize)]
46pub struct Enrichment {
47 #[serde(skip_serializing_if = "Option::is_none")]
50 pub epss_score: Option<f64>,
51 #[serde(skip_serializing_if = "Option::is_none")]
53 pub epss_percentile: Option<f64>,
54 #[serde(skip_serializing_if = "Option::is_none")]
56 pub epss_date: Option<DateTime<Utc>>,
57 #[serde(default)]
59 pub is_kev: bool,
60 #[serde(skip_serializing_if = "Option::is_none")]
62 pub kev_due_date: Option<DateTime<Utc>>,
63 #[serde(skip_serializing_if = "Option::is_none")]
65 pub kev_date_added: Option<DateTime<Utc>>,
66 #[serde(skip_serializing_if = "Option::is_none")]
68 pub kev_ransomware: Option<bool>,
69 #[serde(skip_serializing_if = "Option::is_none")]
71 pub cvss_v3_score: Option<f64>,
72 #[serde(skip_serializing_if = "Option::is_none")]
74 pub cvss_v3_severity: Option<Severity>,
75}
76
77#[derive(Debug, Clone, Serialize, Deserialize)]
78pub struct Affected {
79 pub package: Package,
80 #[serde(default)]
82 pub ranges: Vec<Range>,
83 #[serde(default)]
85 pub versions: Vec<String>,
86 pub ecosystem_specific: Option<serde_json::Value>,
87 pub database_specific: Option<serde_json::Value>,
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize)]
91pub struct Package {
92 pub ecosystem: String,
93 pub name: String,
94 pub purl: Option<String>,
95}
96
97#[derive(Debug, Clone, Serialize, Deserialize)]
98pub struct Range {
99 #[serde(rename = "type")]
100 pub range_type: RangeType,
101 pub events: Vec<Event>,
102 pub repo: Option<String>,
103}
104
105#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
106#[serde(rename_all = "UPPERCASE")]
107pub enum RangeType {
108 Semver,
109 Ecosystem,
110 Git,
111}
112
113#[derive(Debug, Clone, Serialize, Deserialize)]
114#[serde(rename_all = "snake_case")]
115pub enum Event {
116 Introduced(String),
117 Fixed(String),
118 LastAffected(String),
119 Limit(String),
120}
121
122#[derive(Debug, Clone, Serialize, Deserialize)]
123pub struct Reference {
124 #[serde(rename = "type")]
125 pub reference_type: ReferenceType,
126 pub url: String,
127}
128
129#[derive(Debug, Clone, Serialize, Deserialize, Default)]
132#[serde(rename_all = "UPPERCASE")]
133pub enum ReferenceType {
134 Advisory,
135 Article,
136 Detection,
137 Discussion,
138 Evidence,
139 Fix,
140 Git,
141 Introduced,
142 Package,
143 Report,
144 Web,
145 #[default]
147 #[serde(other)]
148 Other,
149}
150
151#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
153#[serde(rename_all = "UPPERCASE")]
154pub enum Severity {
155 None,
157 Low,
159 Medium,
161 High,
163 Critical,
165}
166
167impl Severity {
168 pub fn from_cvss_score(score: f64) -> Self {
170 match score {
171 s if s >= 9.0 => Self::Critical,
172 s if s >= 7.0 => Self::High,
173 s if s >= 4.0 => Self::Medium,
174 s if s > 0.0 => Self::Low,
175 _ => Self::None,
176 }
177 }
178
179 pub fn min_score(&self) -> f64 {
181 match self {
182 Self::None => 0.0,
183 Self::Low => 0.1,
184 Self::Medium => 4.0,
185 Self::High => 7.0,
186 Self::Critical => 9.0,
187 }
188 }
189}