1use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8
9#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
11pub enum EventType {
12 #[serde(rename = "scan_started")]
13 ScanStarted,
14 #[serde(rename = "scan_completed")]
15 ScanCompleted,
16 #[serde(rename = "scan_failed")]
17 ScanFailed,
18 #[serde(rename = "finding_found")]
19 FindingFound,
20 #[serde(rename = "finding_dismissed")]
21 FindingDismissed,
22 #[serde(rename = "proxy_request")]
23 ProxyRequest,
24 #[serde(rename = "proxy_response")]
25 ProxyResponse,
26 #[serde(rename = "worker_started")]
27 WorkerStarted,
28 #[serde(rename = "worker_finished")]
29 WorkerFinished,
30 #[serde(rename = "worker_failed")]
31 WorkerFailed,
32 #[serde(rename = "plugin_loaded")]
33 PluginLoaded,
34 #[serde(rename = "plugin_executed")]
35 PluginExecuted,
36 #[serde(rename = "phase_started")]
37 PhaseStarted,
38 #[serde(rename = "phase_completed")]
39 PhaseCompleted,
40 #[serde(rename = "phase_failed")]
41 PhaseFailed,
42 #[serde(rename = "alert_triggered")]
43 AlertTriggered,
44 #[serde(rename = "config_reloaded")]
45 ConfigReloaded,
46}
47
48impl EventType {
49 pub fn as_str(&self) -> &str {
51 match self {
52 EventType::ScanStarted => "scan_started",
53 EventType::ScanCompleted => "scan_completed",
54 EventType::ScanFailed => "scan_failed",
55 EventType::FindingFound => "finding_found",
56 EventType::FindingDismissed => "finding_dismissed",
57 EventType::ProxyRequest => "proxy_request",
58 EventType::ProxyResponse => "proxy_response",
59 EventType::WorkerStarted => "worker_started",
60 EventType::WorkerFinished => "worker_finished",
61 EventType::WorkerFailed => "worker_failed",
62 EventType::PluginLoaded => "plugin_loaded",
63 EventType::PluginExecuted => "plugin_executed",
64 EventType::PhaseStarted => "phase_started",
65 EventType::PhaseCompleted => "phase_completed",
66 EventType::PhaseFailed => "phase_failed",
67 EventType::AlertTriggered => "alert_triggered",
68 EventType::ConfigReloaded => "config_reloaded",
69 }
70 }
71}
72
73#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
75pub enum EventSeverity {
76 #[serde(rename = "debug")]
77 Debug = 0,
78 #[serde(rename = "info")]
79 Info = 1,
80 #[serde(rename = "warning")]
81 Warning = 2,
82 #[serde(rename = "error")]
83 Error = 3,
84 #[serde(rename = "critical")]
85 Critical = 4,
86}
87
88#[derive(Debug, Clone, Serialize, Deserialize)]
105pub struct Event {
106 pub event_type: EventType,
107 pub version: u16,
108 pub timestamp_ms: u64,
109 pub correlation_id: String,
110 pub source: String,
111 pub data: HashMap<String, String>,
112 pub severity: EventSeverity,
113 pub event_id: String,
114}
115
116impl Event {
117 pub fn new(event_type: EventType, source: impl Into<String>) -> Self {
119 EventBuilder::new(event_type, source).build()
120 }
121
122 pub fn builder(event_type: EventType, source: impl Into<String>) -> EventBuilder {
124 EventBuilder::new(event_type, source)
125 }
126
127 pub fn with_correlation_id(mut self, scan_id: impl Into<String>) -> Self {
129 self.correlation_id = scan_id.into();
130 self
131 }
132
133 pub fn with_data(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
135 self.data.insert(key.into(), value.into());
136 self
137 }
138
139 pub fn with_severity(mut self, severity: EventSeverity) -> Self {
141 self.severity = severity;
142 self
143 }
144
145 pub fn with_version(mut self, version: u16) -> Self {
147 self.version = version;
148 self
149 }
150
151 pub fn timestamp_str(&self) -> String {
153 let secs = self.timestamp_ms / 1000;
154 let millis = self.timestamp_ms % 1000;
155 format!("{secs}.{millis:03}")
156 }
157}
158
159pub struct EventBuilder {
161 event_type: EventType,
162 version: u16,
163 timestamp_ms: u64,
164 correlation_id: String,
165 source: String,
166 data: HashMap<String, String>,
167 severity: EventSeverity,
168 event_id: String,
169}
170
171impl EventBuilder {
172 pub fn new(event_type: EventType, source: impl Into<String>) -> Self {
174 Self {
175 event_type,
176 version: 1,
177 timestamp_ms: now_ms(),
178 correlation_id: uuid::Uuid::new_v4().to_string(),
179 source: source.into(),
180 data: HashMap::new(),
181 severity: EventSeverity::Info,
182 event_id: uuid::Uuid::new_v4().to_string(),
183 }
184 }
185
186 pub fn correlation_id(mut self, scan_id: impl Into<String>) -> Self {
187 self.correlation_id = scan_id.into();
188 self
189 }
190
191 pub fn version(mut self, version: u16) -> Self {
192 self.version = version;
193 self
194 }
195
196 pub fn data(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
197 self.data.insert(key.into(), value.into());
198 self
199 }
200
201 pub fn severity(mut self, severity: EventSeverity) -> Self {
202 self.severity = severity;
203 self
204 }
205
206 pub fn timestamp_ms(mut self, timestamp_ms: u64) -> Self {
207 self.timestamp_ms = timestamp_ms;
208 self
209 }
210
211 pub fn build(self) -> Event {
212 Event {
213 event_type: self.event_type,
214 version: self.version,
215 timestamp_ms: self.timestamp_ms,
216 correlation_id: self.correlation_id,
217 source: self.source,
218 data: self.data,
219 severity: self.severity,
220 event_id: self.event_id,
221 }
222 }
223}
224
225fn now_ms() -> u64 {
226 std::time::SystemTime::now()
227 .duration_since(std::time::UNIX_EPOCH)
228 .unwrap_or_default()
229 .as_millis() as u64
230}
231
232#[cfg(test)]
233mod tests {
234 use super::{Event, EventType};
235
236 #[test]
237 fn timestamp_string_preserves_three_millisecond_digits() {
238 let event = Event::builder(EventType::ScanStarted, "test")
239 .timestamp_ms(1_007)
240 .build();
241
242 assert_eq!(event.timestamp_str(), "1.007");
243 }
244}