coil-ops 0.1.0

Operations and release-management capabilities for the Coil framework.
Documentation
use crate::error::OpsModelError;
use crate::identifiers::BulkOperationId;
use crate::validation::require_non_empty;
use coil_auth::Capability;
use coil_core::{
    BulkOperationDefinition as ManifestBulkOperationDefinition,
    BulkOperationKind as ManifestBulkOperationKind,
    BulkOperationScope as ManifestBulkOperationScope,
};
use coil_jobs::RetryPolicy;
use std::fmt;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BulkOperationKind {
    Publish,
    Unpublish,
    Reindex,
    Export,
    Cancel,
    CheckIn,
    Custom,
}

impl fmt::Display for BulkOperationKind {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Publish => f.write_str("publish"),
            Self::Unpublish => f.write_str("unpublish"),
            Self::Reindex => f.write_str("reindex"),
            Self::Export => f.write_str("export"),
            Self::Cancel => f.write_str("cancel"),
            Self::CheckIn => f.write_str("check_in"),
            Self::Custom => f.write_str("custom"),
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BulkOperationScope {
    Cms,
    Commerce,
    Memberships,
    Events,
    Media,
    Search,
    System,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BulkOperationDefinition {
    pub id: BulkOperationId,
    pub source_module: String,
    pub title: String,
    pub description: Option<String>,
    pub required_capability: Capability,
    pub kind: BulkOperationKind,
    pub scope: BulkOperationScope,
    pub retry_policy: RetryPolicy,
    pub max_items: Option<usize>,
    pub requires_idempotency_key: bool,
}

impl BulkOperationDefinition {
    pub fn new(
        id: BulkOperationId,
        source_module: impl Into<String>,
        title: impl Into<String>,
        description: Option<String>,
        required_capability: Capability,
        kind: BulkOperationKind,
        scope: BulkOperationScope,
        retry_policy: RetryPolicy,
        max_items: Option<usize>,
        requires_idempotency_key: bool,
    ) -> Result<Self, OpsModelError> {
        let source_module = require_non_empty("bulk_source_module", source_module.into())?;
        let title = require_non_empty("bulk_title", title.into())?;

        if let Some(max_items) = max_items {
            if max_items == 0 {
                return Err(OpsModelError::InvalidBulkOperation {
                    operation_id: id.to_string(),
                    reason: "max_items must be greater than zero".to_string(),
                });
            }
        }

        if retry_policy.is_retrying() && !requires_idempotency_key {
            return Err(OpsModelError::InvalidBulkOperation {
                operation_id: id.to_string(),
                reason: "retrying bulk operations must require an idempotency key".to_string(),
            });
        }

        Ok(Self {
            id,
            source_module,
            title,
            description,
            required_capability,
            kind,
            scope,
            retry_policy,
            max_items,
            requires_idempotency_key,
        })
    }

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

    pub(crate) fn from_manifest_definition(
        source_module: &str,
        definition: &ManifestBulkOperationDefinition,
    ) -> Result<Self, OpsModelError> {
        Self::new(
            BulkOperationId::new(definition.id.clone())?,
            source_module,
            definition.title.clone(),
            definition.description.clone(),
            definition.required_capability,
            map_bulk_operation_kind(definition.kind),
            map_bulk_operation_scope(definition.scope),
            definition.retry_policy.clone(),
            definition.max_items,
            definition.requires_idempotency_key,
        )
    }
}

pub(crate) fn map_bulk_operation_kind(kind: ManifestBulkOperationKind) -> BulkOperationKind {
    match kind {
        ManifestBulkOperationKind::Publish => BulkOperationKind::Publish,
        ManifestBulkOperationKind::Unpublish => BulkOperationKind::Unpublish,
        ManifestBulkOperationKind::Reindex => BulkOperationKind::Reindex,
        ManifestBulkOperationKind::Export => BulkOperationKind::Export,
        ManifestBulkOperationKind::Cancel => BulkOperationKind::Cancel,
        ManifestBulkOperationKind::CheckIn => BulkOperationKind::CheckIn,
        ManifestBulkOperationKind::Custom => BulkOperationKind::Custom,
    }
}

pub(crate) fn map_bulk_operation_scope(scope: ManifestBulkOperationScope) -> BulkOperationScope {
    match scope {
        ManifestBulkOperationScope::Cms => BulkOperationScope::Cms,
        ManifestBulkOperationScope::Commerce => BulkOperationScope::Commerce,
        ManifestBulkOperationScope::Memberships => BulkOperationScope::Memberships,
        ManifestBulkOperationScope::Events => BulkOperationScope::Events,
        ManifestBulkOperationScope::Media => BulkOperationScope::Media,
        ManifestBulkOperationScope::Search => BulkOperationScope::Search,
        ManifestBulkOperationScope::System => BulkOperationScope::System,
    }
}