1use crate::common::model::message::{TaskErrorEvent, TaskEvent, TaskParserEvent};
2use crate::common::model::{Request, Response};
3use crate::engine::task::module::Module;
4use crate::errors::{Error, ErrorKind};
5use serde::{Deserialize, Serialize};
6use uuid::Uuid;
7
8#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
10pub enum EventDomain {
11 Engine,
12 System,
13}
14
15#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
17pub enum EventType {
18 TaskModel,
19 ParserTaskModel,
20 ParserTaskProduced,
21 ErrorTaskProduced,
22 ModuleStepAdvanced,
23 ModuleStepFallback,
24 TaskTerminatedByThreshold,
25 RequestPublish,
26 RequestMiddleware,
27 Download,
28 ResponseMiddleware,
29 ResponsePublish,
30 ModuleGenerate,
31 Parser,
32 MiddlewareBefore,
33 DataStore,
34 SystemError,
35 SystemHealth,
36}
37
38#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
40pub enum EventPhase {
41 Started,
42 Completed,
43 Failed,
44 Retry,
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct EventError {
50 pub kind: ErrorKind,
51 pub message: String,
52}
53
54#[derive(Debug, Clone, Serialize, Deserialize)]
56pub struct EventEnvelope {
57 pub domain: EventDomain,
58 pub event_type: EventType,
59 pub phase: EventPhase,
60 pub payload: serde_json::Value,
61 pub error: Option<EventError>,
62 pub timestamp_ms: u128,
63 pub trace_id: Option<String>,
64}
65
66impl EventDomain {
67 pub fn as_str(&self) -> &'static str {
75 match self {
76 EventDomain::Engine => "engine",
77 EventDomain::System => "system",
78 }
79 }
80}
81
82impl EventType {
83 pub fn as_str(&self) -> &'static str {
90 match self {
91 EventType::TaskModel => "task_model",
92 EventType::ParserTaskModel => "parser_task_model",
93 EventType::ParserTaskProduced => "parser_task_produced",
94 EventType::ErrorTaskProduced => "error_task_produced",
95 EventType::ModuleStepAdvanced => "module_step_advanced",
96 EventType::ModuleStepFallback => "module_step_fallback",
97 EventType::TaskTerminatedByThreshold => "task_terminated_by_threshold",
98 EventType::RequestPublish => "request_publish",
99 EventType::RequestMiddleware => "request_middleware",
100 EventType::Download => "download",
101 EventType::ResponseMiddleware => "response_middleware",
102 EventType::ResponsePublish => "response_publish",
103 EventType::ModuleGenerate => "module_generate",
104 EventType::Parser => "parser",
105 EventType::MiddlewareBefore => "middleware_before",
106 EventType::DataStore => "data_store",
107 EventType::SystemError => "system_error",
108 EventType::SystemHealth => "system_health",
109 }
110 }
111}
112
113impl EventPhase {
114 pub fn as_str(&self) -> &'static str {
121 match self {
122 EventPhase::Started => "started",
123 EventPhase::Completed => "completed",
124 EventPhase::Failed => "failed",
125 EventPhase::Retry => "retry",
126 }
127 }
128}
129
130impl EventEnvelope {
131 pub fn engine<T: Serialize>(event_type: EventType, phase: EventPhase, payload: T) -> Self {
132 Self {
133 domain: EventDomain::Engine,
134 event_type,
135 phase,
136 payload: serde_json::to_value(payload).unwrap_or_else(|_| serde_json::json!({})),
137 error: None,
138 timestamp_ms: Self::now_ms(),
139 trace_id: None,
140 }
141 }
142
143 pub fn engine_error<T: Serialize>(
144 event_type: EventType,
145 phase: EventPhase,
146 payload: T,
147 err: &Error,
148 ) -> Self {
149 Self {
150 domain: EventDomain::Engine,
151 event_type,
152 phase,
153 payload: serde_json::to_value(payload).unwrap_or_else(|_| serde_json::json!({})),
154 error: Some(EventError {
155 kind: err.kind().clone(),
156 message: err.to_string(),
157 }),
158 timestamp_ms: Self::now_ms(),
159 trace_id: None,
160 }
161 }
162
163 pub fn system_error(message: impl Into<String>, phase: EventPhase) -> Self {
164 Self {
165 domain: EventDomain::System,
166 event_type: EventType::SystemError,
167 phase,
168 payload: serde_json::json!({ "message": message.into() }),
169 error: None,
170 timestamp_ms: Self::now_ms(),
171 trace_id: None,
172 }
173 }
174
175 pub fn system_health(payload: HealthCheckEvent, phase: EventPhase) -> Self {
176 Self {
177 domain: EventDomain::System,
178 event_type: EventType::SystemHealth,
179 phase,
180 payload: serde_json::to_value(payload).unwrap_or_else(|_| serde_json::json!({})),
181 error: None,
182 timestamp_ms: Self::now_ms(),
183 trace_id: None,
184 }
185 }
186
187 pub fn event_key(&self) -> String {
195 format!(
196 "{}.{}.{}",
197 self.domain.as_str(),
198 self.event_type.as_str(),
199 self.phase.as_str()
200 )
201 }
202
203 pub fn now_ms() -> u128 {
204 std::time::SystemTime::now()
205 .duration_since(std::time::UNIX_EPOCH)
206 .unwrap()
207 .as_millis()
208 }
209}
210
211#[cfg(test)]
212mod tests {
213 use super::{EventEnvelope, EventPhase, EventType, ParserTaskModelEvent};
214 use crate::common::model::ExecutionMark;
215 use crate::common::model::message::{TaskErrorEvent, TaskEvent, TaskParserEvent};
216 use serde_json::json;
217 use uuid::Uuid;
218
219 #[test]
220 fn threshold_termination_event_type_has_stable_name() {
221 assert_eq!(
222 EventType::TaskTerminatedByThreshold.as_str(),
223 "task_terminated_by_threshold"
224 );
225 }
226
227 #[test]
228 fn task_model_chain_semantic_event_types_have_stable_names() {
229 assert_eq!(
230 EventType::ParserTaskProduced.as_str(),
231 "parser_task_produced"
232 );
233 assert_eq!(EventType::ErrorTaskProduced.as_str(), "error_task_produced");
234 assert_eq!(
235 EventType::ModuleStepAdvanced.as_str(),
236 "module_step_advanced"
237 );
238 assert_eq!(
239 EventType::ModuleStepFallback.as_str(),
240 "module_step_fallback"
241 );
242 }
243
244 #[test]
245 fn parser_task_model_event_mapping_is_consistent_for_parser_and_error_inputs() {
246 let run_id = Uuid::now_v7();
247 let task_model = TaskEvent {
248 account: "acc".to_string(),
249 platform: "pf".to_string(),
250 module: Some(vec!["m1".to_string()]),
251 priority: Default::default(),
252 run_id,
253 };
254
255 let parser_task = TaskParserEvent {
256 id: Uuid::now_v7(),
257 account_task: task_model.clone(),
258 timestamp: 0,
259 metadata: json!({"k":"v"}).as_object().cloned().unwrap_or_default(),
260 context: ExecutionMark::default(),
261 run_id,
262 prefix_request: Uuid::now_v7(),
263 };
264 let parser_evt = ParserTaskModelEvent::from(&parser_task);
265 assert_eq!(parser_evt.account, "acc");
266 assert_eq!(parser_evt.platform, "pf");
267 assert_eq!(parser_evt.modules, Some(vec!["m1".to_string()]));
268
269 let error_task = TaskErrorEvent {
270 id: Uuid::now_v7(),
271 account_task: task_model,
272 error_msg: "err".to_string(),
273 timestamp: 0,
274 metadata: json!({"e":"x"}).as_object().cloned().unwrap_or_default(),
275 context: ExecutionMark::default(),
276 run_id,
277 prefix_request: Uuid::now_v7(),
278 };
279 let error_evt = ParserTaskModelEvent::from(&error_task);
280 assert_eq!(error_evt.account, "acc");
281 assert_eq!(error_evt.platform, "pf");
282 assert_eq!(error_evt.modules, Some(vec!["m1".to_string()]));
283 }
284
285 #[test]
286 fn parser_task_model_event_envelope_key_is_stable() {
287 let payload = json!({"account":"acc","platform":"pf"});
288 let evt = EventEnvelope::engine(EventType::ParserTaskModel, EventPhase::Completed, payload);
289 assert_eq!(evt.event_key(), "engine.parser_task_model.completed");
290 }
291}
292
293#[derive(Debug, Clone, Serialize, Deserialize)]
294pub struct TaskModelEvent {
295 pub account: String,
296 pub platform: String,
297 pub modules: Option<Vec<String>>,
298}
299impl From<&TaskEvent> for TaskModelEvent {
300 fn from(value: &TaskEvent) -> Self {
301 TaskModelEvent {
302 account: value.account.clone(),
303 platform: value.platform.clone(),
304 modules: None,
305 }
306 }
307}
308
309#[derive(Debug, Clone, Serialize, Deserialize)]
311pub struct ParserTaskModelEvent {
312 pub account: String,
313 pub platform: String,
314 pub modules: Option<Vec<String>>,
315 #[serde(skip)]
316 pub metadata: Option<serde_json::Value>,
317}
318impl From<&TaskParserEvent> for ParserTaskModelEvent {
319 fn from(value: &TaskParserEvent) -> Self {
320 ParserTaskModelEvent {
321 account: value.account_task.account.clone(),
322 platform: value.account_task.platform.clone(),
323 modules: value.account_task.module.clone(),
324 metadata: Some(serde_json::to_value(value.metadata.clone()).unwrap_or_default()),
325 }
326 }
327}
328impl From<&TaskErrorEvent> for ParserTaskModelEvent {
329 fn from(value: &TaskErrorEvent) -> Self {
330 Self {
331 account: value.account_task.account.clone(),
332 platform: value.account_task.platform.clone(),
333 modules: value.account_task.module.clone(),
334 metadata: Some(serde_json::to_value(value.metadata.clone()).unwrap_or_default()),
335 }
336 }
337}
338
339#[derive(Debug, Clone, Serialize, Deserialize)]
341pub struct ModuleGenerateEvent {
342 pub account: String,
343 pub platform: String,
344 pub module: String,
345}
346
347impl From<&Module> for ModuleGenerateEvent {
348 fn from(value: &Module) -> Self {
349 Self {
350 account: value.account.name.clone(),
351 platform: value.platform.name.clone(),
352 module: value.module.name().clone(),
353 }
354 }
355}
356
357#[derive(Debug, Clone, Serialize, Deserialize)]
359pub struct RequestEvent {
360 pub request_id: Uuid,
361 pub url: String,
362 pub account: String,
363 pub platform: String,
364 pub module: String,
365 pub method: String,
366 #[serde(skip)]
367 pub metadata: serde_json::Value,
368}
369
370impl From<&Request> for RequestEvent {
371 fn from(value: &Request) -> Self {
372 RequestEvent {
373 request_id: value.id,
374 url: value.url.clone(),
375 account: value.account.clone(),
376 platform: value.platform.clone(),
377 module: value.module.clone(),
378 method: value.method.clone(),
379 metadata: serde_json::to_value(value.meta.clone()).unwrap_or_default(),
380 }
381 }
382}
383
384#[derive(Debug, Clone, Serialize, Deserialize)]
386pub struct DownloadEvent {
387 pub request_id: Uuid,
388 pub url: String,
389 pub account: String,
390 pub platform: String,
391 pub module: String,
392 pub method: String,
393 pub duration_ms: Option<u64>,
394 pub response_size: Option<u32>,
395 pub status_code: Option<u16>,
396}
397impl From<&Request> for DownloadEvent {
398 fn from(value: &Request) -> Self {
399 DownloadEvent {
400 request_id: value.id,
401 url: value.url.clone(),
402 account: value.account.clone(),
403 platform: value.platform.clone(),
404 module: value.module.clone(),
405 method: value.method.clone(),
406 duration_ms: None,
407 response_size: None,
408 status_code: None,
409 }
410 }
411}
412
413#[derive(Debug, Clone, Serialize, Deserialize)]
415pub struct ParserEvent {
416 pub account: String,
417 pub platform: String,
418 pub module: String,
419 pub request_id: String,
420}
421
422impl From<&Response> for ParserEvent {
423 fn from(value: &Response) -> Self {
424 Self {
425 account: value.account.clone(),
426 platform: value.platform.clone(),
427 module: value.module.clone(),
428 request_id: value.id.to_string(),
429 }
430 }
431}
432
433#[derive(Debug, Clone, Serialize, Deserialize)]
435pub struct DataMiddlewareEvent {
436 pub account: String,
437 pub platform: String,
438 pub module: String,
439 pub request_id: String,
440 pub schema_size: usize,
441 pub after_size: Option<usize>,
442}
443impl From<&crate::common::model::data::DataEvent> for DataMiddlewareEvent {
444 fn from(value: &crate::common::model::data::DataEvent) -> Self {
445 Self {
446 account: value.account.clone(),
447 platform: value.platform.clone(),
448 module: value.module.clone(),
449 request_id: value.request_id.to_string(),
450 schema_size: 0,
451 after_size: None,
452 }
453 }
454}
455
456#[derive(Debug, Clone, Serialize, Deserialize)]
458pub struct DataStoreEvent {
459 pub account: String,
460 pub platform: String,
461 pub module: String,
462 pub request_id: String,
463 pub schema_size: (usize, usize),
464 pub store_middleware: Option<String>,
465}
466impl From<&crate::common::model::data::DataEvent> for DataStoreEvent {
467 fn from(value: &crate::common::model::data::DataEvent) -> Self {
468 Self {
469 account: value.account.clone(),
470 platform: value.platform.clone(),
471 module: value.module.clone(),
472 request_id: value.request_id.to_string(),
473 schema_size: (0, 0),
474 store_middleware: None,
475 }
476 }
477}
478
479#[derive(Debug, Clone, Serialize, Deserialize)]
481pub struct RequestMiddlewareEvent {
482 pub request_id: Uuid,
483 pub url: String,
484 pub account: String,
485 pub platform: String,
486 pub module: String,
487 pub method: String,
488 pub middleware: Vec<String>,
489}
490impl From<&Request> for RequestMiddlewareEvent {
491 fn from(value: &Request) -> Self {
492 RequestMiddlewareEvent {
493 request_id: value.id,
494 url: value.url.clone(),
495 account: value.account.clone(),
496 platform: value.platform.clone(),
497 module: value.module.clone(),
498 method: value.method.clone(),
499 middleware: value.download_middleware.clone(),
500 }
501 }
502}
503
504#[derive(Debug, Clone, Serialize, Deserialize)]
506pub struct ResponseEvent {
507 pub response_id: Uuid,
508 pub account: String,
509 pub platform: String,
510 pub module: String,
511 pub status_code: Option<u16>,
512 pub middleware: Vec<String>,
513}
514impl From<&Response> for ResponseEvent {
515 fn from(value: &Response) -> Self {
516 ResponseEvent {
517 response_id: value.id,
518 account: value.account.clone(),
519 platform: value.platform.clone(),
520 module: value.module.clone(),
521 status_code: Some(value.status_code),
522 middleware: value.data_middleware.clone(),
523 }
524 }
525}
526
527#[derive(Debug, Clone, Serialize, Deserialize)]
529pub struct HealthCheckEvent {
530 pub component: String,
531 pub status: String,
532 pub metrics: serde_json::Value,
533 pub timestamp: u64,
534}
535impl From<&Response> for ModuleGenerateEvent {
536 fn from(value: &Response) -> Self {
537 Self {
538 account: value.account.clone(),
539 platform: value.platform.clone(),
540 module: value.module.clone(),
541 }
542 }
543}