coil-ops 0.1.1

Operations and release-management capabilities for the Coil framework.
Documentation
use crate::error::OpsModelError;
use crate::identifiers::RecoveryWorkflowId;
use crate::validation::require_non_empty;
use coil_auth::Capability;
use coil_jobs::RetryPolicy;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RecoveryStage {
    RestoreDatabase,
    ReattachManagedObjectStore,
    RestoreLocalOnlySensitive,
    RebuildDerivedState,
    RedeployStaticAssets,
    ValidateReadiness,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RecoveryWorkflowDefinition {
    pub id: RecoveryWorkflowId,
    pub title: String,
    pub description: Option<String>,
    pub required_capability: Capability,
    pub retry_policy: RetryPolicy,
    pub requires_idempotency_key: bool,
    pub requires_local_only_sensitive_ack: bool,
    pub default_stages: Vec<RecoveryStage>,
}

impl RecoveryWorkflowDefinition {
    pub fn new(
        id: RecoveryWorkflowId,
        title: impl Into<String>,
        description: Option<String>,
        required_capability: Capability,
        retry_policy: RetryPolicy,
        requires_idempotency_key: bool,
        requires_local_only_sensitive_ack: bool,
        default_stages: Vec<RecoveryStage>,
    ) -> Result<Self, OpsModelError> {
        let title = require_non_empty("recovery_title", title.into())?;
        if default_stages.is_empty() {
            return Err(OpsModelError::InvalidRecoveryWorkflow {
                workflow_id: id.to_string(),
                reason: "recovery workflow must declare at least one stage".to_string(),
            });
        }
        if retry_policy.is_retrying() && !requires_idempotency_key {
            return Err(OpsModelError::InvalidRecoveryWorkflow {
                workflow_id: id.to_string(),
                reason: "retrying recovery workflows must require an idempotency key".to_string(),
            });
        }

        Ok(Self {
            id,
            title,
            description,
            required_capability,
            retry_policy,
            requires_idempotency_key,
            requires_local_only_sensitive_ack,
            default_stages,
        })
    }

    pub fn allows(&self, capabilities: &[Capability]) -> bool {
        capabilities.contains(&self.required_capability)
    }
}