Skip to main content

coil_ops/recovery/
definition.rs

1use crate::error::OpsModelError;
2use crate::identifiers::RecoveryWorkflowId;
3use crate::validation::require_non_empty;
4use coil_auth::Capability;
5use coil_jobs::RetryPolicy;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum RecoveryStage {
9    RestoreDatabase,
10    ReattachManagedObjectStore,
11    RestoreLocalOnlySensitive,
12    RebuildDerivedState,
13    RedeployStaticAssets,
14    ValidateReadiness,
15}
16
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct RecoveryWorkflowDefinition {
19    pub id: RecoveryWorkflowId,
20    pub title: String,
21    pub description: Option<String>,
22    pub required_capability: Capability,
23    pub retry_policy: RetryPolicy,
24    pub requires_idempotency_key: bool,
25    pub requires_local_only_sensitive_ack: bool,
26    pub default_stages: Vec<RecoveryStage>,
27}
28
29impl RecoveryWorkflowDefinition {
30    pub fn new(
31        id: RecoveryWorkflowId,
32        title: impl Into<String>,
33        description: Option<String>,
34        required_capability: Capability,
35        retry_policy: RetryPolicy,
36        requires_idempotency_key: bool,
37        requires_local_only_sensitive_ack: bool,
38        default_stages: Vec<RecoveryStage>,
39    ) -> Result<Self, OpsModelError> {
40        let title = require_non_empty("recovery_title", title.into())?;
41        if default_stages.is_empty() {
42            return Err(OpsModelError::InvalidRecoveryWorkflow {
43                workflow_id: id.to_string(),
44                reason: "recovery workflow must declare at least one stage".to_string(),
45            });
46        }
47        if retry_policy.is_retrying() && !requires_idempotency_key {
48            return Err(OpsModelError::InvalidRecoveryWorkflow {
49                workflow_id: id.to_string(),
50                reason: "retrying recovery workflows must require an idempotency key".to_string(),
51            });
52        }
53
54        Ok(Self {
55            id,
56            title,
57            description,
58            required_capability,
59            retry_policy,
60            requires_idempotency_key,
61            requires_local_only_sensitive_ack,
62            default_stages,
63        })
64    }
65
66    pub fn allows(&self, capabilities: &[Capability]) -> bool {
67        capabilities.contains(&self.required_capability)
68    }
69}