1use crate::common::interface::StoreTrait;
2use crate::common::interface::storage::{BlobStorage, Offloadable};
3use crate::common::model::data::DataEvent;
4use crate::common::model::{ExecutionMark, Response};
5use async_trait::async_trait;
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8use std::sync::Arc;
9use uuid::Uuid;
10
11#[derive(Debug, Clone, Copy)]
12pub enum TopicType {
13 Task,
15 Request,
17 Response,
19 ParserTask,
21 Error,
23}
24
25impl TopicType {
26 pub(crate) fn suffix(&self) -> &'static str {
28 match self {
29 TopicType::Response => "response",
30 TopicType::ParserTask => "parser_task",
31 TopicType::Error => "error",
32 TopicType::Task => "task",
33 TopicType::Request => "request",
34 }
35 }
36 pub fn get_name(&self, name: &str) -> String {
37 format!("crawler-{}-{}", name, self.suffix())
38 }
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct TaskParserEvent {
47 pub id: Uuid,
49 pub account_task: TaskEvent,
51 pub timestamp: u64,
53 pub metadata: serde_json::Map<String, Value>,
55 pub context: ExecutionMark,
57 #[serde(default = "default_run_id")]
59 pub run_id: Uuid,
60 pub prefix_request: Uuid,
62}
63
64#[async_trait]
65impl Offloadable for TaskParserEvent {
66 fn should_offload(&self, _threshold: usize) -> bool {
67 false
68 }
69 async fn offload(&mut self, _storage: &Arc<dyn BlobStorage>) -> crate::errors::Result<()> {
70 Ok(())
71 }
72 async fn reload(&mut self, _storage: &Arc<dyn BlobStorage>) -> crate::errors::Result<()> {
73 Ok(())
74 }
75}
76
77impl TaskParserEvent {
78 pub fn with_context(mut self, ctx: ExecutionMark) -> Self {
84 self.context = ctx;
85 self
86 }
87 pub fn stay_current_step(mut self) -> Self {
93 self.context.stay_current_step = true;
95 self
96 }
97 pub fn get_context(&self) -> &ExecutionMark {
98 &self.context
99 }
100 pub fn with_meta<T>(mut self, meta: T) -> Self
101 where
102 T: serde::Serialize,
103 {
104 if let Ok(Value::Object(map)) = serde_json::to_value(meta) {
105 self.metadata = map;
106 }
107 self
108 }
109 pub fn add_meta<T>(mut self, key: impl AsRef<str>, value: T) -> Self
110 where
111 T: serde::Serialize,
112 {
113 if let Ok(val) = serde_json::to_value(value) {
114 self.metadata.insert(key.as_ref().to_string(), val);
115 }
116 self
117 }
118 pub fn with_prefix_request(mut self, prefix: Uuid) -> Self {
123 self.prefix_request = prefix;
124 self
125 }
126 pub fn start_other_module(response: &Response, module_name: impl AsRef<str>) -> Self {
128 debug_assert!(
129 !module_name.as_ref().is_empty(),
130 "module_name must not be empty"
131 );
132 TaskParserEvent {
133 id: Uuid::now_v7(),
134 account_task: TaskEvent {
135 account: response.account.clone(),
136 platform: response.platform.clone(),
137 module: Some(vec![module_name.as_ref().to_string()]),
138 priority: response.priority,
139 run_id: Uuid::now_v7(),
140 },
141 timestamp: chrono::Utc::now().timestamp() as u64,
142 metadata: serde_json::Map::new(),
143 context: ExecutionMark::default(),
145 run_id: Uuid::now_v7(),
146 prefix_request: response.prefix_request,
147 }
148 }
149 pub fn start_other_module_with_ctx(
155 response: &Response,
156 module_name: impl AsRef<str>,
157 ctx: ExecutionMark,
158 ) -> Self {
159 debug_assert!(
160 !module_name.as_ref().is_empty(),
161 "module_name must not be empty"
162 );
163 TaskParserEvent {
164 id: Uuid::now_v7(),
165 account_task: TaskEvent {
166 account: response.account.clone(),
167 platform: response.platform.clone(),
168 module: Some(vec![module_name.as_ref().to_string()]),
169 priority: response.priority,
170 run_id: Uuid::now_v7(),
171 },
172 timestamp: chrono::Utc::now().timestamp() as u64,
173 metadata: serde_json::Map::new(),
174 context: ctx,
175 run_id: Uuid::now_v7(),
176 prefix_request: response.prefix_request,
177 }
178 }
179}
180
181#[derive(Debug, Clone, Serialize, Deserialize)]
185pub struct TaskErrorEvent {
186 pub id: Uuid,
188 pub account_task: TaskEvent,
190 pub error_msg: String,
192 pub timestamp: u64,
194 pub metadata: serde_json::Map<String, Value>,
196 pub context: ExecutionMark,
198 #[serde(default = "default_run_id")]
200 pub run_id: Uuid,
201 pub prefix_request: Uuid,
203}
204
205#[async_trait]
206impl Offloadable for TaskErrorEvent {
207 fn should_offload(&self, _threshold: usize) -> bool {
208 false
209 }
210 async fn offload(&mut self, _storage: &Arc<dyn BlobStorage>) -> crate::errors::Result<()> {
211 Ok(())
212 }
213 async fn reload(&mut self, _storage: &Arc<dyn BlobStorage>) -> crate::errors::Result<()> {
214 Ok(())
215 }
216}
217
218impl TaskErrorEvent {
219 pub fn get_context(&self) -> &ExecutionMark {
220 &self.context
221 }
222 pub fn with_meta<T>(mut self, meta: T) -> Self
223 where
224 T: serde::Serialize,
225 {
226 if let Ok(Value::Object(map)) = serde_json::to_value(meta) {
227 self.metadata = map;
228 }
229 self
230 }
231 pub fn add_meta<T>(mut self, key: impl AsRef<str>, value: T) -> Self
232 where
233 T: serde::Serialize,
234 {
235 if let Ok(val) = serde_json::to_value(value) {
236 self.metadata.insert(key.as_ref().to_string(), val);
237 }
238 self
239 }
240 pub fn with_context(mut self, ctx: ExecutionMark) -> Self {
241 self.context = ctx;
242 self
243 }
244}
245#[derive(Debug, Clone, Serialize, Deserialize)]
249pub struct TaskEvent {
250 pub account: String,
252 pub platform: String,
254 pub module: Option<Vec<String>>,
256 #[serde(default)]
258 pub priority: crate::common::model::Priority,
259 #[serde(default = "default_run_id")]
261 pub run_id: Uuid,
262}
263
264#[derive(Debug, Clone, Serialize, Deserialize)]
265#[serde(tag = "type", content = "payload", rename_all = "snake_case")]
266pub enum UnifiedTaskInput {
267 Task(TaskEvent),
268 ParserTask(TaskParserEvent),
269 ErrorTask(TaskErrorEvent),
270}
271
272impl UnifiedTaskInput {
273 pub fn run_id(&self) -> Uuid {
274 match self {
275 UnifiedTaskInput::Task(value) => value.run_id,
276 UnifiedTaskInput::ParserTask(value) => value.run_id,
277 UnifiedTaskInput::ErrorTask(value) => value.run_id,
278 }
279 }
280}
281
282impl From<TaskEvent> for UnifiedTaskInput {
283 fn from(value: TaskEvent) -> Self {
284 UnifiedTaskInput::Task(value)
285 }
286}
287
288impl From<TaskParserEvent> for UnifiedTaskInput {
289 fn from(value: TaskParserEvent) -> Self {
290 UnifiedTaskInput::ParserTask(value)
291 }
292}
293
294impl From<TaskErrorEvent> for UnifiedTaskInput {
295 fn from(value: TaskErrorEvent) -> Self {
296 UnifiedTaskInput::ErrorTask(value)
297 }
298}
299
300#[async_trait]
301impl Offloadable for TaskEvent {
302 fn should_offload(&self, _threshold: usize) -> bool {
303 false
304 }
305 async fn offload(&mut self, _storage: &Arc<dyn BlobStorage>) -> crate::errors::Result<()> {
306 Ok(())
307 }
308 async fn reload(&mut self, _storage: &Arc<dyn BlobStorage>) -> crate::errors::Result<()> {
309 Ok(())
310 }
311}
312
313impl crate::common::model::priority::Prioritizable for TaskEvent {
314 fn get_priority(&self) -> crate::common::model::Priority {
315 self.priority
316 }
317}
318
319impl crate::common::model::priority::Prioritizable for TaskParserEvent {
320 fn get_priority(&self) -> crate::common::model::Priority {
321 self.account_task.priority
322 }
323}
324
325impl crate::common::model::priority::Prioritizable for TaskErrorEvent {
326 fn get_priority(&self) -> crate::common::model::Priority {
327 self.account_task.priority
328 }
329}
330
331impl From<&Response> for TaskParserEvent {
332 fn from(value: &Response) -> Self {
333 let metadata = value.metadata.task.as_object().cloned().unwrap_or_default();
336
337 TaskParserEvent {
338 id: Uuid::now_v7(),
339 account_task: TaskEvent {
340 account: value.account.clone(),
341 platform: value.platform.clone(),
342 module: Some(vec![value.module.clone()]),
343 priority: value.priority,
344 run_id: value.run_id,
345 },
346 timestamp: chrono::Utc::now().timestamp() as u64,
347 metadata,
348 context: value.context.clone(),
349 run_id: value.run_id,
350 prefix_request: value.prefix_request,
351 }
352 }
353}
354
355impl From<&Response> for TaskErrorEvent {
356 fn from(value: &Response) -> Self {
357 let metadata = value.metadata.task.as_object().cloned().unwrap_or_default();
360
361 TaskErrorEvent {
362 id: Uuid::now_v7(),
363 account_task: TaskEvent {
364 account: value.account.clone(),
365 platform: value.platform.clone(),
366 module: Some(vec![value.module.clone()]),
367 priority: value.priority,
368 run_id: value.run_id,
369 },
370 error_msg: "".to_string(),
371 timestamp: chrono::Utc::now().timestamp() as u64,
372 metadata,
373 context: value.context.clone(),
374 run_id: value.run_id,
375 prefix_request: value.prefix_request,
376 }
377 }
378}
379
380fn default_run_id() -> Uuid {
382 Uuid::now_v7()
383}
384
385#[derive(Debug, Default, Clone)]
389pub struct TaskOutputEvent {
390 pub data: Vec<DataEvent>,
392 pub parser_task: Vec<TaskParserEvent>,
394 pub error_task: Option<TaskErrorEvent>,
396 pub stop: Option<bool>,
398}
399impl TaskOutputEvent {
400 pub fn with_data(mut self, data: Vec<impl StoreTrait>) -> Self {
401 self.data = data.into_iter().map(|d| d.build()).collect();
402 self
403 }
404 pub fn with_task(mut self, task: TaskParserEvent) -> Self {
405 self.parser_task.push(task);
406 self
407 }
408 pub fn with_tasks(mut self, tasks: Vec<TaskParserEvent>) -> Self {
409 self.parser_task.extend(tasks);
410 self
411 }
412 pub fn with_error(mut self, error: TaskErrorEvent) -> Self {
413 self.error_task = Some(error);
414 self
415 }
416
417 pub fn with_stop(mut self, stop: bool) -> Self {
420 self.stop = Some(stop);
421 self
422 }
423}
424
425#[cfg(test)]
426mod tests {
427 use super::*;
428 use crate::common::model::ExecutionMark;
429
430 #[test]
431 fn test_topic_type() {
432 assert_eq!(TopicType::Task.suffix(), "task");
433 assert_eq!(TopicType::Request.suffix(), "request");
434 assert_eq!(TopicType::Response.suffix(), "response");
435
436 let name = TopicType::Task.get_name("test_mod");
437 assert_eq!(name, "crawler-test_mod-task");
438 }
439
440 #[test]
441 fn test_parser_task_model_builder() {
442 let id = Uuid::now_v7();
443 let run_id = Uuid::now_v7();
444 let prefix_req = Uuid::now_v7();
445
446 let task = TaskParserEvent {
447 id,
448 account_task: TaskEvent {
449 account: "acc".into(),
450 platform: "plat".into(),
451 module: None,
452 priority: Default::default(),
453 run_id,
454 },
455 timestamp: 123456,
456 metadata: serde_json::Map::new(),
457 context: ExecutionMark::default(),
458 run_id,
459 prefix_request: prefix_req,
460 };
461
462 let ctx = ExecutionMark::default().with_step_idx(5);
463 let task = task.with_context(ctx.clone());
464 assert_eq!(task.context.step_idx, Some(5));
465
466 let task = task.stay_current_step();
467 assert!(task.context.stay_current_step);
468
469 let new_prefix = Uuid::now_v7();
470 let task = task.with_prefix_request(new_prefix);
471 assert_eq!(task.prefix_request, new_prefix);
472
473 let task = task.add_meta("foo", "bar");
474 assert_eq!(task.metadata["foo"], "bar");
475 }
476
477 #[test]
478 fn test_response_conversion() {
479 let run_id = Uuid::now_v7();
480 let prefix_request = Uuid::now_v7();
481 let response = Response {
482 id: Uuid::now_v7(),
483 platform: "plat".into(),
484 account: "acc".into(),
485 module: "mod".into(),
486 status_code: 200,
487 cookies: Default::default(),
488 content: vec![],
489 storage_path: None,
490 headers: vec![],
491 task_retry_times: 0,
492 metadata: crate::common::model::meta::MetaData::default(),
493 download_middleware: vec![],
494 data_middleware: vec![],
495 task_finished: false,
496 context: ExecutionMark::default(),
497 run_id,
498 prefix_request,
499 request_hash: None,
500 priority: Default::default(),
501 };
502
503 let parser_task: TaskParserEvent = (&response).into();
504 assert_eq!(parser_task.account_task.account, "acc");
505 assert_eq!(parser_task.account_task.platform, "plat");
506 assert_eq!(parser_task.run_id, run_id);
507 assert_eq!(parser_task.prefix_request, prefix_request);
508
509 let error_task: TaskErrorEvent = (&response).into();
510 assert_eq!(error_task.account_task.account, "acc");
511 assert_eq!(error_task.run_id, run_id);
512 }
513}