1use serde::{Deserialize, Serialize};
15use std::sync::Arc;
16use chrono;
17use backbone_core::flow::{WorkflowStep, WorkflowContext};
18
19#[derive(Debug, Clone, thiserror::Error)]
21pub enum FlowError {
22 #[error("No current step to execute")]
23 NoCurrentStep,
24
25 #[error("Step execution failed: {0}")]
26 StepFailed(String),
27
28 #[error("Condition evaluation failed: {0}")]
29 ConditionFailed(String),
30
31 #[error("Compensation failed: {0}")]
32 CompensationFailed(String),
33
34 #[error("Flow timed out")]
35 Timeout,
36
37 #[error("Flow cancelled")]
38 Cancelled,
39
40 #[error("Invalid state transition: {from} -> {to}")]
41 InvalidTransition { from: String, to: String },
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
46#[serde(rename_all = "snake_case")]
47pub enum FileProcessingFlowStatus {
48 Pending,
50 Running,
52 Waiting,
54 Completed,
56 Failed,
58 Cancelled,
60 Compensating,
62}
63
64#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
66#[serde(rename_all = "snake_case")]
67pub enum FileProcessingFlowStep {
68 LoadJob,
70 MarkRunning,
72 LoadFile,
74 LoadBucket,
76 RouteByJobType,
78 ProcessImageThumbnail,
80 StoreImageThumbnail,
82 UpdateFileThumbnail,
84 ProcessVideoThumbnail,
86 StoreVideoThumbnail,
88 UpdateFileVideoThumbnail,
90 ProcessDocumentPreview,
92 StoreDocumentPreview,
94 UpdateFileDocumentPreview,
96 ProcessCompression,
98 StoreCompressedFile,
100 UpdateFileCompression,
102 UpdateBucketCompressionStats,
104 ProcessVirusScan,
106 CheckThreatLevel,
108 UpdateScanSafe,
110 UpdateScanWarning,
112 NotifyScanWarning,
114 UpdateScanThreat,
116 NotifyThreatDetected,
118 ProcessDeduplication,
120 CheckExistingHash,
122 UpdateExistingHash,
124 LinkFileHash,
126 CreateNewHash,
128 MarkComplete,
130 EmitCompleteEvent,
132 Complete,
134 RetryOrFail,
136 ScheduleRetry,
138 RetryTerminal,
140 MarkFailed,
142 EmitFailedEvent,
144 FailedTerminal,
146 JobNotFound,
148 FileNotFound,
150 UnknownJobType,
152}
153
154#[derive(Debug, Clone, Serialize, Deserialize)]
156pub struct FileProcessingFlowInstance {
157 pub id: String,
159 pub status: FileProcessingFlowStatus,
161 pub current_step: Option<FileProcessingFlowStep>,
163 pub context: serde_json::Value,
165 pub completed_steps: Vec<FileProcessingFlowStep>,
167 pub error: Option<String>,
169 pub created_at: chrono::DateTime<chrono::Utc>,
171 pub updated_at: chrono::DateTime<chrono::Utc>,
173}
174
175impl FileProcessingFlowInstance {
176 pub fn new(id: impl Into<String>) -> Self {
178 let now = chrono::Utc::now();
179 Self {
180 id: id.into(),
181 status: FileProcessingFlowStatus::Pending,
182 current_step: None,
183 context: serde_json::json!({}),
184 completed_steps: Vec::new(),
185 error: None,
186 created_at: now,
187 updated_at: now,
188 }
189 }
190
191 pub fn is_complete(&self) -> bool {
193 matches!(
194 self.status,
195 FileProcessingFlowStatus::Completed | FileProcessingFlowStatus::Failed | FileProcessingFlowStatus::Cancelled
196 )
197 }
198
199 pub fn is_running(&self) -> bool {
201 matches!(
202 self.status,
203 FileProcessingFlowStatus::Running | FileProcessingFlowStatus::Waiting
204 )
205 }
206
207 pub fn set_context(&mut self, key: &str, value: serde_json::Value) {
209 if let serde_json::Value::Object(ref mut map) = self.context {
210 map.insert(key.to_string(), value);
211 }
212 self.updated_at = chrono::Utc::now();
213 }
214
215 pub fn get_context(&self, key: &str) -> Option<&serde_json::Value> {
217 if let serde_json::Value::Object(ref map) = self.context {
218 map.get(key)
219 } else {
220 None
221 }
222 }
223}
224
225#[allow(unused_variables)]
229impl WorkflowContext<FileProcessingFlowInstance> for FileProcessingFlowInstance {
230 fn entity(&self) -> &FileProcessingFlowInstance { self }
231 fn set_var(&mut self, key: &str, value: serde_json::Value) {
232 self.set_context(key, value);
233 }
234 fn get_var(&self, key: &str) -> Option<&serde_json::Value> {
235 self.get_context(key)
236 }
237}
238
239#[async_trait::async_trait]
241pub trait FileProcessingStepHandler: Send + Sync {
242 async fn handle_load_job(
244 &self,
245 instance: &mut FileProcessingFlowInstance,
246 ) -> Result<Option<FileProcessingFlowStep>, FlowError>;
247
248 async fn handle_mark_running(
250 &self,
251 instance: &mut FileProcessingFlowInstance,
252 ) -> Result<Option<FileProcessingFlowStep>, FlowError>;
253
254 async fn handle_load_file(
256 &self,
257 instance: &mut FileProcessingFlowInstance,
258 ) -> Result<Option<FileProcessingFlowStep>, FlowError>;
259
260 async fn handle_load_bucket(
262 &self,
263 instance: &mut FileProcessingFlowInstance,
264 ) -> Result<Option<FileProcessingFlowStep>, FlowError>;
265
266 async fn evaluate_route_by_job_type(
268 &self,
269 instance: &FileProcessingFlowInstance,
270 ) -> Result<FileProcessingFlowStep, FlowError>;
271
272 async fn handle_process_image_thumbnail(
274 &self,
275 instance: &mut FileProcessingFlowInstance,
276 ) -> Result<Option<FileProcessingFlowStep>, FlowError>;
277
278 async fn handle_store_image_thumbnail(
280 &self,
281 instance: &mut FileProcessingFlowInstance,
282 ) -> Result<Option<FileProcessingFlowStep>, FlowError>;
283
284 async fn handle_update_file_thumbnail(
286 &self,
287 instance: &mut FileProcessingFlowInstance,
288 ) -> Result<Option<FileProcessingFlowStep>, FlowError>;
289
290 async fn handle_process_video_thumbnail(
292 &self,
293 instance: &mut FileProcessingFlowInstance,
294 ) -> Result<Option<FileProcessingFlowStep>, FlowError>;
295
296 async fn handle_store_video_thumbnail(
298 &self,
299 instance: &mut FileProcessingFlowInstance,
300 ) -> Result<Option<FileProcessingFlowStep>, FlowError>;
301
302 async fn handle_update_file_video_thumbnail(
304 &self,
305 instance: &mut FileProcessingFlowInstance,
306 ) -> Result<Option<FileProcessingFlowStep>, FlowError>;
307
308 async fn handle_process_document_preview(
310 &self,
311 instance: &mut FileProcessingFlowInstance,
312 ) -> Result<Option<FileProcessingFlowStep>, FlowError>;
313
314 async fn handle_store_document_preview(
316 &self,
317 instance: &mut FileProcessingFlowInstance,
318 ) -> Result<Option<FileProcessingFlowStep>, FlowError>;
319
320 async fn handle_update_file_document_preview(
322 &self,
323 instance: &mut FileProcessingFlowInstance,
324 ) -> Result<Option<FileProcessingFlowStep>, FlowError>;
325
326 async fn handle_process_compression(
328 &self,
329 instance: &mut FileProcessingFlowInstance,
330 ) -> Result<Option<FileProcessingFlowStep>, FlowError>;
331
332 async fn handle_store_compressed_file(
334 &self,
335 instance: &mut FileProcessingFlowInstance,
336 ) -> Result<Option<FileProcessingFlowStep>, FlowError>;
337
338 async fn handle_update_file_compression(
340 &self,
341 instance: &mut FileProcessingFlowInstance,
342 ) -> Result<Option<FileProcessingFlowStep>, FlowError>;
343
344 async fn handle_update_bucket_compression_stats(
346 &self,
347 instance: &mut FileProcessingFlowInstance,
348 ) -> Result<Option<FileProcessingFlowStep>, FlowError>;
349
350 async fn handle_process_virus_scan(
352 &self,
353 instance: &mut FileProcessingFlowInstance,
354 ) -> Result<Option<FileProcessingFlowStep>, FlowError>;
355
356 async fn evaluate_check_threat_level(
358 &self,
359 instance: &FileProcessingFlowInstance,
360 ) -> Result<FileProcessingFlowStep, FlowError>;
361
362 async fn handle_update_scan_safe(
364 &self,
365 instance: &mut FileProcessingFlowInstance,
366 ) -> Result<Option<FileProcessingFlowStep>, FlowError>;
367
368 async fn handle_update_scan_warning(
370 &self,
371 instance: &mut FileProcessingFlowInstance,
372 ) -> Result<Option<FileProcessingFlowStep>, FlowError>;
373
374 async fn handle_notify_scan_warning(
376 &self,
377 instance: &mut FileProcessingFlowInstance,
378 ) -> Result<Option<FileProcessingFlowStep>, FlowError>;
379
380 async fn handle_update_scan_threat(
382 &self,
383 instance: &mut FileProcessingFlowInstance,
384 ) -> Result<Option<FileProcessingFlowStep>, FlowError>;
385
386 async fn handle_notify_threat_detected(
388 &self,
389 instance: &mut FileProcessingFlowInstance,
390 ) -> Result<Option<FileProcessingFlowStep>, FlowError>;
391
392 async fn handle_process_deduplication(
394 &self,
395 instance: &mut FileProcessingFlowInstance,
396 ) -> Result<Option<FileProcessingFlowStep>, FlowError>;
397
398 async fn handle_check_existing_hash(
400 &self,
401 instance: &mut FileProcessingFlowInstance,
402 ) -> Result<Option<FileProcessingFlowStep>, FlowError>;
403
404 async fn handle_update_existing_hash(
406 &self,
407 instance: &mut FileProcessingFlowInstance,
408 ) -> Result<Option<FileProcessingFlowStep>, FlowError>;
409
410 async fn handle_link_file_hash(
412 &self,
413 instance: &mut FileProcessingFlowInstance,
414 ) -> Result<Option<FileProcessingFlowStep>, FlowError>;
415
416 async fn handle_create_new_hash(
418 &self,
419 instance: &mut FileProcessingFlowInstance,
420 ) -> Result<Option<FileProcessingFlowStep>, FlowError>;
421
422 async fn handle_mark_complete(
424 &self,
425 instance: &mut FileProcessingFlowInstance,
426 ) -> Result<Option<FileProcessingFlowStep>, FlowError>;
427
428 async fn handle_emit_complete_event(
430 &self,
431 instance: &mut FileProcessingFlowInstance,
432 ) -> Result<Option<FileProcessingFlowStep>, FlowError>;
433
434 async fn handle_complete(
436 &self,
437 instance: &mut FileProcessingFlowInstance,
438 ) -> Result<(), FlowError>;
439
440 async fn evaluate_retry_or_fail(
442 &self,
443 instance: &FileProcessingFlowInstance,
444 ) -> Result<FileProcessingFlowStep, FlowError>;
445
446 async fn handle_schedule_retry(
448 &self,
449 instance: &mut FileProcessingFlowInstance,
450 ) -> Result<Option<FileProcessingFlowStep>, FlowError>;
451
452 async fn handle_retry_terminal(
454 &self,
455 instance: &mut FileProcessingFlowInstance,
456 ) -> Result<(), FlowError>;
457
458 async fn handle_mark_failed(
460 &self,
461 instance: &mut FileProcessingFlowInstance,
462 ) -> Result<Option<FileProcessingFlowStep>, FlowError>;
463
464 async fn handle_emit_failed_event(
466 &self,
467 instance: &mut FileProcessingFlowInstance,
468 ) -> Result<Option<FileProcessingFlowStep>, FlowError>;
469
470 async fn handle_failed_terminal(
472 &self,
473 instance: &mut FileProcessingFlowInstance,
474 ) -> Result<(), FlowError>;
475
476 async fn handle_job_not_found(
478 &self,
479 instance: &mut FileProcessingFlowInstance,
480 ) -> Result<(), FlowError>;
481
482 async fn handle_file_not_found(
484 &self,
485 instance: &mut FileProcessingFlowInstance,
486 ) -> Result<(), FlowError>;
487
488 async fn handle_unknown_job_type(
490 &self,
491 instance: &mut FileProcessingFlowInstance,
492 ) -> Result<(), FlowError>;
493
494 async fn compensate(
496 &self,
497 instance: &mut FileProcessingFlowInstance,
498 ) -> Result<(), FlowError>;
499
500}
501
502pub struct FileProcessingFlowExecutor<H: FileProcessingStepHandler> {
504 handler: Arc<H>,
505}
506
507impl<H: FileProcessingStepHandler> FileProcessingFlowExecutor<H> {
508 pub fn new(handler: Arc<H>) -> Self {
510 Self { handler }
511 }
512
513 pub async fn start(&self, instance_id: impl Into<String>) -> Result<FileProcessingFlowInstance, FlowError> {
515 let mut instance = FileProcessingFlowInstance::new(instance_id);
516 instance.status = FileProcessingFlowStatus::Running;
517 instance.current_step = Some(FileProcessingFlowStep::LoadJob);
518 Ok(instance)
519 }
520
521 pub async fn execute_step(&self, instance: &mut FileProcessingFlowInstance) -> Result<(), FlowError> {
523 let current_step = match instance.current_step {
524 Some(step) => step,
525 None => return Err(FlowError::NoCurrentStep),
526 };
527
528 let next_step = match current_step {
529 FileProcessingFlowStep::LoadJob => {
530 self.handler.handle_load_job(instance).await?
531 }
532 FileProcessingFlowStep::MarkRunning => {
533 self.handler.handle_mark_running(instance).await?
534 }
535 FileProcessingFlowStep::LoadFile => {
536 self.handler.handle_load_file(instance).await?
537 }
538 FileProcessingFlowStep::LoadBucket => {
539 self.handler.handle_load_bucket(instance).await?
540 }
541 FileProcessingFlowStep::RouteByJobType => {
542 let next = self.handler.evaluate_route_by_job_type(instance).await?;
543 Some(next)
544 }
545 FileProcessingFlowStep::ProcessImageThumbnail => {
546 self.handler.handle_process_image_thumbnail(instance).await?
547 }
548 FileProcessingFlowStep::StoreImageThumbnail => {
549 self.handler.handle_store_image_thumbnail(instance).await?
550 }
551 FileProcessingFlowStep::UpdateFileThumbnail => {
552 self.handler.handle_update_file_thumbnail(instance).await?
553 }
554 FileProcessingFlowStep::ProcessVideoThumbnail => {
555 self.handler.handle_process_video_thumbnail(instance).await?
556 }
557 FileProcessingFlowStep::StoreVideoThumbnail => {
558 self.handler.handle_store_video_thumbnail(instance).await?
559 }
560 FileProcessingFlowStep::UpdateFileVideoThumbnail => {
561 self.handler.handle_update_file_video_thumbnail(instance).await?
562 }
563 FileProcessingFlowStep::ProcessDocumentPreview => {
564 self.handler.handle_process_document_preview(instance).await?
565 }
566 FileProcessingFlowStep::StoreDocumentPreview => {
567 self.handler.handle_store_document_preview(instance).await?
568 }
569 FileProcessingFlowStep::UpdateFileDocumentPreview => {
570 self.handler.handle_update_file_document_preview(instance).await?
571 }
572 FileProcessingFlowStep::ProcessCompression => {
573 self.handler.handle_process_compression(instance).await?
574 }
575 FileProcessingFlowStep::StoreCompressedFile => {
576 self.handler.handle_store_compressed_file(instance).await?
577 }
578 FileProcessingFlowStep::UpdateFileCompression => {
579 self.handler.handle_update_file_compression(instance).await?
580 }
581 FileProcessingFlowStep::UpdateBucketCompressionStats => {
582 self.handler.handle_update_bucket_compression_stats(instance).await?
583 }
584 FileProcessingFlowStep::ProcessVirusScan => {
585 self.handler.handle_process_virus_scan(instance).await?
586 }
587 FileProcessingFlowStep::CheckThreatLevel => {
588 let next = self.handler.evaluate_check_threat_level(instance).await?;
589 Some(next)
590 }
591 FileProcessingFlowStep::UpdateScanSafe => {
592 self.handler.handle_update_scan_safe(instance).await?
593 }
594 FileProcessingFlowStep::UpdateScanWarning => {
595 self.handler.handle_update_scan_warning(instance).await?
596 }
597 FileProcessingFlowStep::NotifyScanWarning => {
598 self.handler.handle_notify_scan_warning(instance).await?
599 }
600 FileProcessingFlowStep::UpdateScanThreat => {
601 self.handler.handle_update_scan_threat(instance).await?
602 }
603 FileProcessingFlowStep::NotifyThreatDetected => {
604 self.handler.handle_notify_threat_detected(instance).await?
605 }
606 FileProcessingFlowStep::ProcessDeduplication => {
607 self.handler.handle_process_deduplication(instance).await?
608 }
609 FileProcessingFlowStep::CheckExistingHash => {
610 self.handler.handle_check_existing_hash(instance).await?
611 }
612 FileProcessingFlowStep::UpdateExistingHash => {
613 self.handler.handle_update_existing_hash(instance).await?
614 }
615 FileProcessingFlowStep::LinkFileHash => {
616 self.handler.handle_link_file_hash(instance).await?
617 }
618 FileProcessingFlowStep::CreateNewHash => {
619 self.handler.handle_create_new_hash(instance).await?
620 }
621 FileProcessingFlowStep::MarkComplete => {
622 self.handler.handle_mark_complete(instance).await?
623 }
624 FileProcessingFlowStep::EmitCompleteEvent => {
625 self.handler.handle_emit_complete_event(instance).await?
626 }
627 FileProcessingFlowStep::Complete => {
628 self.handler.handle_complete(instance).await?;
629 None }
631 FileProcessingFlowStep::RetryOrFail => {
632 let next = self.handler.evaluate_retry_or_fail(instance).await?;
633 Some(next)
634 }
635 FileProcessingFlowStep::ScheduleRetry => {
636 self.handler.handle_schedule_retry(instance).await?
637 }
638 FileProcessingFlowStep::RetryTerminal => {
639 self.handler.handle_retry_terminal(instance).await?;
640 None }
642 FileProcessingFlowStep::MarkFailed => {
643 self.handler.handle_mark_failed(instance).await?
644 }
645 FileProcessingFlowStep::EmitFailedEvent => {
646 self.handler.handle_emit_failed_event(instance).await?
647 }
648 FileProcessingFlowStep::FailedTerminal => {
649 self.handler.handle_failed_terminal(instance).await?;
650 None }
652 FileProcessingFlowStep::JobNotFound => {
653 self.handler.handle_job_not_found(instance).await?;
654 None }
656 FileProcessingFlowStep::FileNotFound => {
657 self.handler.handle_file_not_found(instance).await?;
658 None }
660 FileProcessingFlowStep::UnknownJobType => {
661 self.handler.handle_unknown_job_type(instance).await?;
662 None }
664 };
665
666 instance.completed_steps.push(current_step);
668
669 match next_step {
671 Some(next) => {
672 instance.current_step = Some(next);
673 }
674 None => {
675 instance.current_step = None;
676 instance.status = FileProcessingFlowStatus::Completed;
677 }
678 }
679
680 instance.updated_at = chrono::Utc::now();
681 Ok(())
682 }
683
684 pub async fn run(&self, instance: &mut FileProcessingFlowInstance) -> Result<(), FlowError> {
686 while !instance.is_complete() && instance.status != FileProcessingFlowStatus::Waiting {
687 self.execute_step(instance).await?;
688 }
689 Ok(())
690 }
691
692 pub fn cancel(&self, instance: &mut FileProcessingFlowInstance) {
694 instance.status = FileProcessingFlowStatus::Cancelled;
695 instance.updated_at = chrono::Utc::now();
696 }
697
698 pub fn fail(&self, instance: &mut FileProcessingFlowInstance, error: impl Into<String>) {
700 instance.status = FileProcessingFlowStatus::Failed;
701 instance.error = Some(error.into());
702 instance.updated_at = chrono::Utc::now();
703 }
704}
705
706#[cfg(test)]
707mod tests {
708 use super::*;
709
710 #[test]
711 fn test_flow_instance_creation() {
712 let instance = FileProcessingFlowInstance::new("test-1");
713 assert_eq!(instance.id, "test-1");
714 assert_eq!(instance.status, FileProcessingFlowStatus::Pending);
715 assert!(!instance.is_complete());
716 }
717
718 #[test]
719 fn test_flow_context() {
720 let mut instance = FileProcessingFlowInstance::new("test-2");
721 instance.set_context("key", serde_json::json!("value"));
722 let value = instance.get_context("key");
723 assert_eq!(value, Some(&serde_json::json!("value")));
724 }
725
726 #[test]
727 fn test_flow_status_transitions() {
728 let mut instance = FileProcessingFlowInstance::new("test-3");
729 assert!(!instance.is_running());
730
731 instance.status = FileProcessingFlowStatus::Running;
732 assert!(instance.is_running());
733 assert!(!instance.is_complete());
734
735 instance.status = FileProcessingFlowStatus::Completed;
736 assert!(instance.is_complete());
737 assert!(!instance.is_running());
738 }
739}