coil_ops/recovery/
definition.rs1use 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}