1use serde::{Deserialize, Serialize};
17use std::sync::Arc;
18use chrono;
19use backbone_core::flow::{WorkflowStep, WorkflowContext};
20
21#[derive(Debug, Clone, thiserror::Error)]
23pub enum FlowError {
24 #[error("No current step to execute")]
25 NoCurrentStep,
26
27 #[error("Step execution failed: {0}")]
28 StepFailed(String),
29
30 #[error("Condition evaluation failed: {0}")]
31 ConditionFailed(String),
32
33 #[error("Compensation failed: {0}")]
34 CompensationFailed(String),
35
36 #[error("Flow timed out")]
37 Timeout,
38
39 #[error("Flow cancelled")]
40 Cancelled,
41
42 #[error("Invalid state transition: {from} -> {to}")]
43 InvalidTransition { from: String, to: String },
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
48#[serde(rename_all = "snake_case")]
49pub enum FileUploadFlowStatus {
50 Pending,
52 Running,
54 Waiting,
56 Completed,
58 Failed,
60 Cancelled,
62 Compensating,
64}
65
66#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
68#[serde(rename_all = "snake_case")]
69pub enum FileUploadFlowStep {
70 LoadBucket,
72 CheckBucketActive,
74 CheckDeduplicationEnabled,
76 CalculateChecksum,
78 CheckDuplicateContent,
80 UseExistingContent,
82 DetectMimeType,
84 ValidateFileType,
86 ValidateFileSize,
88 LoadQuota,
90 CreateDefaultQuota,
92 CheckQuota,
94 CreateFileRecord,
96 CheckShouldStoreContent,
98 StoreContent,
100 CreateContentHash,
102 LinkContentHash,
104 UpdateFileProcessing,
106 ScanFile,
108 CheckScanResult,
110 UpdateScanComplete,
112 CheckCdnEnabled,
114 GenerateCdnUrl,
116 UpdateCdnInfo,
118 MarkFileActive,
120 UpdateQuota,
122 UpdateBucketStats,
124 EmitUploadedEvent,
126 Complete,
128 QuarantineFile,
130 EmitThreatEvent,
132 NotifyThreat,
134 CompleteQuarantine,
136 BucketNotFound,
138 BucketNotActive,
140 MimeTypeNotAllowed,
142 FileTooLarge,
144 QuotaExceeded,
146 QuotaExceededTerminal,
148 RollbackFileRecord,
150 StorageFailed,
152}
153
154#[derive(Debug, Clone, Serialize, Deserialize)]
156pub struct FileUploadFlowInstance {
157 pub id: String,
159 pub status: FileUploadFlowStatus,
161 pub current_step: Option<FileUploadFlowStep>,
163 pub context: serde_json::Value,
165 pub completed_steps: Vec<FileUploadFlowStep>,
167 pub error: Option<String>,
169 pub created_at: chrono::DateTime<chrono::Utc>,
171 pub updated_at: chrono::DateTime<chrono::Utc>,
173}
174
175impl FileUploadFlowInstance {
176 pub fn new(id: impl Into<String>) -> Self {
178 let now = chrono::Utc::now();
179 Self {
180 id: id.into(),
181 status: FileUploadFlowStatus::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 FileUploadFlowStatus::Completed | FileUploadFlowStatus::Failed | FileUploadFlowStatus::Cancelled
196 )
197 }
198
199 pub fn is_running(&self) -> bool {
201 matches!(
202 self.status,
203 FileUploadFlowStatus::Running | FileUploadFlowStatus::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<FileUploadFlowInstance> for FileUploadFlowInstance {
230 fn entity(&self) -> &FileUploadFlowInstance { 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 FileUploadStepHandler: Send + Sync {
242 async fn handle_load_bucket(
244 &self,
245 instance: &mut FileUploadFlowInstance,
246 ) -> Result<Option<FileUploadFlowStep>, FlowError>;
247
248 async fn evaluate_check_bucket_active(
250 &self,
251 instance: &FileUploadFlowInstance,
252 ) -> Result<FileUploadFlowStep, FlowError>;
253
254 async fn evaluate_check_deduplication_enabled(
256 &self,
257 instance: &FileUploadFlowInstance,
258 ) -> Result<FileUploadFlowStep, FlowError>;
259
260 async fn handle_calculate_checksum(
262 &self,
263 instance: &mut FileUploadFlowInstance,
264 ) -> Result<Option<FileUploadFlowStep>, FlowError>;
265
266 async fn handle_check_duplicate_content(
268 &self,
269 instance: &mut FileUploadFlowInstance,
270 ) -> Result<Option<FileUploadFlowStep>, FlowError>;
271
272 async fn handle_use_existing_content(
274 &self,
275 instance: &mut FileUploadFlowInstance,
276 ) -> Result<Option<FileUploadFlowStep>, FlowError>;
277
278 async fn handle_detect_mime_type(
280 &self,
281 instance: &mut FileUploadFlowInstance,
282 ) -> Result<Option<FileUploadFlowStep>, FlowError>;
283
284 async fn evaluate_validate_file_type(
286 &self,
287 instance: &FileUploadFlowInstance,
288 ) -> Result<FileUploadFlowStep, FlowError>;
289
290 async fn evaluate_validate_file_size(
292 &self,
293 instance: &FileUploadFlowInstance,
294 ) -> Result<FileUploadFlowStep, FlowError>;
295
296 async fn handle_load_quota(
298 &self,
299 instance: &mut FileUploadFlowInstance,
300 ) -> Result<Option<FileUploadFlowStep>, FlowError>;
301
302 async fn handle_create_default_quota(
304 &self,
305 instance: &mut FileUploadFlowInstance,
306 ) -> Result<Option<FileUploadFlowStep>, FlowError>;
307
308 async fn evaluate_check_quota(
310 &self,
311 instance: &FileUploadFlowInstance,
312 ) -> Result<FileUploadFlowStep, FlowError>;
313
314 async fn handle_create_file_record(
316 &self,
317 instance: &mut FileUploadFlowInstance,
318 ) -> Result<Option<FileUploadFlowStep>, FlowError>;
319
320 async fn evaluate_check_should_store_content(
322 &self,
323 instance: &FileUploadFlowInstance,
324 ) -> Result<FileUploadFlowStep, FlowError>;
325
326 async fn handle_store_content(
328 &self,
329 instance: &mut FileUploadFlowInstance,
330 ) -> Result<Option<FileUploadFlowStep>, FlowError>;
331
332 async fn handle_create_content_hash(
334 &self,
335 instance: &mut FileUploadFlowInstance,
336 ) -> Result<Option<FileUploadFlowStep>, FlowError>;
337
338 async fn handle_link_content_hash(
340 &self,
341 instance: &mut FileUploadFlowInstance,
342 ) -> Result<Option<FileUploadFlowStep>, FlowError>;
343
344 async fn handle_update_file_processing(
346 &self,
347 instance: &mut FileUploadFlowInstance,
348 ) -> Result<Option<FileUploadFlowStep>, FlowError>;
349
350 async fn handle_scan_file(
352 &self,
353 instance: &mut FileUploadFlowInstance,
354 ) -> Result<Option<FileUploadFlowStep>, FlowError>;
355
356 async fn evaluate_check_scan_result(
358 &self,
359 instance: &FileUploadFlowInstance,
360 ) -> Result<FileUploadFlowStep, FlowError>;
361
362 async fn handle_update_scan_complete(
364 &self,
365 instance: &mut FileUploadFlowInstance,
366 ) -> Result<Option<FileUploadFlowStep>, FlowError>;
367
368 async fn evaluate_check_cdn_enabled(
370 &self,
371 instance: &FileUploadFlowInstance,
372 ) -> Result<FileUploadFlowStep, FlowError>;
373
374 async fn handle_generate_cdn_url(
376 &self,
377 instance: &mut FileUploadFlowInstance,
378 ) -> Result<Option<FileUploadFlowStep>, FlowError>;
379
380 async fn handle_update_cdn_info(
382 &self,
383 instance: &mut FileUploadFlowInstance,
384 ) -> Result<Option<FileUploadFlowStep>, FlowError>;
385
386 async fn handle_mark_file_active(
388 &self,
389 instance: &mut FileUploadFlowInstance,
390 ) -> Result<Option<FileUploadFlowStep>, FlowError>;
391
392 async fn handle_update_quota(
394 &self,
395 instance: &mut FileUploadFlowInstance,
396 ) -> Result<Option<FileUploadFlowStep>, FlowError>;
397
398 async fn handle_update_bucket_stats(
400 &self,
401 instance: &mut FileUploadFlowInstance,
402 ) -> Result<Option<FileUploadFlowStep>, FlowError>;
403
404 async fn handle_emit_uploaded_event(
406 &self,
407 instance: &mut FileUploadFlowInstance,
408 ) -> Result<Option<FileUploadFlowStep>, FlowError>;
409
410 async fn handle_complete(
412 &self,
413 instance: &mut FileUploadFlowInstance,
414 ) -> Result<(), FlowError>;
415
416 async fn handle_quarantine_file(
418 &self,
419 instance: &mut FileUploadFlowInstance,
420 ) -> Result<Option<FileUploadFlowStep>, FlowError>;
421
422 async fn handle_emit_threat_event(
424 &self,
425 instance: &mut FileUploadFlowInstance,
426 ) -> Result<Option<FileUploadFlowStep>, FlowError>;
427
428 async fn handle_notify_threat(
430 &self,
431 instance: &mut FileUploadFlowInstance,
432 ) -> Result<Option<FileUploadFlowStep>, FlowError>;
433
434 async fn handle_complete_quarantine(
436 &self,
437 instance: &mut FileUploadFlowInstance,
438 ) -> Result<(), FlowError>;
439
440 async fn handle_bucket_not_found(
442 &self,
443 instance: &mut FileUploadFlowInstance,
444 ) -> Result<(), FlowError>;
445
446 async fn handle_bucket_not_active(
448 &self,
449 instance: &mut FileUploadFlowInstance,
450 ) -> Result<(), FlowError>;
451
452 async fn handle_mime_type_not_allowed(
454 &self,
455 instance: &mut FileUploadFlowInstance,
456 ) -> Result<(), FlowError>;
457
458 async fn handle_file_too_large(
460 &self,
461 instance: &mut FileUploadFlowInstance,
462 ) -> Result<(), FlowError>;
463
464 async fn handle_quota_exceeded(
466 &self,
467 instance: &mut FileUploadFlowInstance,
468 ) -> Result<Option<FileUploadFlowStep>, FlowError>;
469
470 async fn handle_quota_exceeded_terminal(
472 &self,
473 instance: &mut FileUploadFlowInstance,
474 ) -> Result<(), FlowError>;
475
476 async fn handle_rollback_file_record(
478 &self,
479 instance: &mut FileUploadFlowInstance,
480 ) -> Result<Option<FileUploadFlowStep>, FlowError>;
481
482 async fn handle_storage_failed(
484 &self,
485 instance: &mut FileUploadFlowInstance,
486 ) -> Result<(), FlowError>;
487
488 async fn compensate(
490 &self,
491 instance: &mut FileUploadFlowInstance,
492 ) -> Result<(), FlowError>;
493
494}
495
496pub struct FileUploadFlowExecutor<H: FileUploadStepHandler> {
498 handler: Arc<H>,
499}
500
501impl<H: FileUploadStepHandler> FileUploadFlowExecutor<H> {
502 pub fn new(handler: Arc<H>) -> Self {
504 Self { handler }
505 }
506
507 pub async fn start(&self, instance_id: impl Into<String>) -> Result<FileUploadFlowInstance, FlowError> {
509 let mut instance = FileUploadFlowInstance::new(instance_id);
510 instance.status = FileUploadFlowStatus::Running;
511 instance.current_step = Some(FileUploadFlowStep::LoadBucket);
512 Ok(instance)
513 }
514
515 pub async fn execute_step(&self, instance: &mut FileUploadFlowInstance) -> Result<(), FlowError> {
517 let current_step = match instance.current_step {
518 Some(step) => step,
519 None => return Err(FlowError::NoCurrentStep),
520 };
521
522 let next_step = match current_step {
523 FileUploadFlowStep::LoadBucket => {
524 self.handler.handle_load_bucket(instance).await?
525 }
526 FileUploadFlowStep::CheckBucketActive => {
527 let next = self.handler.evaluate_check_bucket_active(instance).await?;
528 Some(next)
529 }
530 FileUploadFlowStep::CheckDeduplicationEnabled => {
531 let next = self.handler.evaluate_check_deduplication_enabled(instance).await?;
532 Some(next)
533 }
534 FileUploadFlowStep::CalculateChecksum => {
535 self.handler.handle_calculate_checksum(instance).await?
536 }
537 FileUploadFlowStep::CheckDuplicateContent => {
538 self.handler.handle_check_duplicate_content(instance).await?
539 }
540 FileUploadFlowStep::UseExistingContent => {
541 self.handler.handle_use_existing_content(instance).await?
542 }
543 FileUploadFlowStep::DetectMimeType => {
544 self.handler.handle_detect_mime_type(instance).await?
545 }
546 FileUploadFlowStep::ValidateFileType => {
547 let next = self.handler.evaluate_validate_file_type(instance).await?;
548 Some(next)
549 }
550 FileUploadFlowStep::ValidateFileSize => {
551 let next = self.handler.evaluate_validate_file_size(instance).await?;
552 Some(next)
553 }
554 FileUploadFlowStep::LoadQuota => {
555 self.handler.handle_load_quota(instance).await?
556 }
557 FileUploadFlowStep::CreateDefaultQuota => {
558 self.handler.handle_create_default_quota(instance).await?
559 }
560 FileUploadFlowStep::CheckQuota => {
561 let next = self.handler.evaluate_check_quota(instance).await?;
562 Some(next)
563 }
564 FileUploadFlowStep::CreateFileRecord => {
565 self.handler.handle_create_file_record(instance).await?
566 }
567 FileUploadFlowStep::CheckShouldStoreContent => {
568 let next = self.handler.evaluate_check_should_store_content(instance).await?;
569 Some(next)
570 }
571 FileUploadFlowStep::StoreContent => {
572 self.handler.handle_store_content(instance).await?
573 }
574 FileUploadFlowStep::CreateContentHash => {
575 self.handler.handle_create_content_hash(instance).await?
576 }
577 FileUploadFlowStep::LinkContentHash => {
578 self.handler.handle_link_content_hash(instance).await?
579 }
580 FileUploadFlowStep::UpdateFileProcessing => {
581 self.handler.handle_update_file_processing(instance).await?
582 }
583 FileUploadFlowStep::ScanFile => {
584 self.handler.handle_scan_file(instance).await?
585 }
586 FileUploadFlowStep::CheckScanResult => {
587 let next = self.handler.evaluate_check_scan_result(instance).await?;
588 Some(next)
589 }
590 FileUploadFlowStep::UpdateScanComplete => {
591 self.handler.handle_update_scan_complete(instance).await?
592 }
593 FileUploadFlowStep::CheckCdnEnabled => {
594 let next = self.handler.evaluate_check_cdn_enabled(instance).await?;
595 Some(next)
596 }
597 FileUploadFlowStep::GenerateCdnUrl => {
598 self.handler.handle_generate_cdn_url(instance).await?
599 }
600 FileUploadFlowStep::UpdateCdnInfo => {
601 self.handler.handle_update_cdn_info(instance).await?
602 }
603 FileUploadFlowStep::MarkFileActive => {
604 self.handler.handle_mark_file_active(instance).await?
605 }
606 FileUploadFlowStep::UpdateQuota => {
607 self.handler.handle_update_quota(instance).await?
608 }
609 FileUploadFlowStep::UpdateBucketStats => {
610 self.handler.handle_update_bucket_stats(instance).await?
611 }
612 FileUploadFlowStep::EmitUploadedEvent => {
613 self.handler.handle_emit_uploaded_event(instance).await?
614 }
615 FileUploadFlowStep::Complete => {
616 self.handler.handle_complete(instance).await?;
617 None }
619 FileUploadFlowStep::QuarantineFile => {
620 self.handler.handle_quarantine_file(instance).await?
621 }
622 FileUploadFlowStep::EmitThreatEvent => {
623 self.handler.handle_emit_threat_event(instance).await?
624 }
625 FileUploadFlowStep::NotifyThreat => {
626 self.handler.handle_notify_threat(instance).await?
627 }
628 FileUploadFlowStep::CompleteQuarantine => {
629 self.handler.handle_complete_quarantine(instance).await?;
630 None }
632 FileUploadFlowStep::BucketNotFound => {
633 self.handler.handle_bucket_not_found(instance).await?;
634 None }
636 FileUploadFlowStep::BucketNotActive => {
637 self.handler.handle_bucket_not_active(instance).await?;
638 None }
640 FileUploadFlowStep::MimeTypeNotAllowed => {
641 self.handler.handle_mime_type_not_allowed(instance).await?;
642 None }
644 FileUploadFlowStep::FileTooLarge => {
645 self.handler.handle_file_too_large(instance).await?;
646 None }
648 FileUploadFlowStep::QuotaExceeded => {
649 self.handler.handle_quota_exceeded(instance).await?
650 }
651 FileUploadFlowStep::QuotaExceededTerminal => {
652 self.handler.handle_quota_exceeded_terminal(instance).await?;
653 None }
655 FileUploadFlowStep::RollbackFileRecord => {
656 self.handler.handle_rollback_file_record(instance).await?
657 }
658 FileUploadFlowStep::StorageFailed => {
659 self.handler.handle_storage_failed(instance).await?;
660 None }
662 };
663
664 instance.completed_steps.push(current_step);
666
667 match next_step {
669 Some(next) => {
670 instance.current_step = Some(next);
671 }
672 None => {
673 instance.current_step = None;
674 instance.status = FileUploadFlowStatus::Completed;
675 }
676 }
677
678 instance.updated_at = chrono::Utc::now();
679 Ok(())
680 }
681
682 pub async fn run(&self, instance: &mut FileUploadFlowInstance) -> Result<(), FlowError> {
684 while !instance.is_complete() && instance.status != FileUploadFlowStatus::Waiting {
685 self.execute_step(instance).await?;
686 }
687 Ok(())
688 }
689
690 pub fn cancel(&self, instance: &mut FileUploadFlowInstance) {
692 instance.status = FileUploadFlowStatus::Cancelled;
693 instance.updated_at = chrono::Utc::now();
694 }
695
696 pub fn fail(&self, instance: &mut FileUploadFlowInstance, error: impl Into<String>) {
698 instance.status = FileUploadFlowStatus::Failed;
699 instance.error = Some(error.into());
700 instance.updated_at = chrono::Utc::now();
701 }
702}
703
704#[cfg(test)]
705mod tests {
706 use super::*;
707
708 #[test]
709 fn test_flow_instance_creation() {
710 let instance = FileUploadFlowInstance::new("test-1");
711 assert_eq!(instance.id, "test-1");
712 assert_eq!(instance.status, FileUploadFlowStatus::Pending);
713 assert!(!instance.is_complete());
714 }
715
716 #[test]
717 fn test_flow_context() {
718 let mut instance = FileUploadFlowInstance::new("test-2");
719 instance.set_context("key", serde_json::json!("value"));
720 let value = instance.get_context("key");
721 assert_eq!(value, Some(&serde_json::json!("value")));
722 }
723
724 #[test]
725 fn test_flow_status_transitions() {
726 let mut instance = FileUploadFlowInstance::new("test-3");
727 assert!(!instance.is_running());
728
729 instance.status = FileUploadFlowStatus::Running;
730 assert!(instance.is_running());
731 assert!(!instance.is_complete());
732
733 instance.status = FileUploadFlowStatus::Completed;
734 assert!(instance.is_complete());
735 assert!(!instance.is_running());
736 }
737}