haematite 0.6.2

Content-addressed, branchable, actor-native storage engine
Documentation
use std::fmt;

/// Durable role of a named branch.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BranchKind {
    /// A component namespace: a permanent isolation boundary that never merges
    /// away into another branch.
    Namespace,
    /// A mutable work branch. Pre-marker ref records decode to this permissive
    /// compatibility default.
    Work,
}

impl fmt::Display for BranchKind {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Namespace => formatter.write_str("Namespace"),
            Self::Work => formatter.write_str("Work"),
        }
    }
}

/// Typed engine refusals at namespace boundaries.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BranchPolicyError {
    /// A fork would change an existing Namespace lineage.
    ForkAcrossNamespaceBoundary {
        /// Durable source branch name.
        source: String,
        /// Durable source branch kind.
        source_kind: BranchKind,
        /// Namespace lineage resolved from the source ref record.
        source_lineage: Option<String>,
        /// Durable destination branch name.
        destination: String,
        /// Durable destination branch kind.
        destination_kind: BranchKind,
        /// Namespace lineage the destination record would resolve to.
        destination_lineage: Option<String>,
    },
    /// A merge would move content between different Namespace lineages.
    MergeAcrossNamespaceBoundary {
        /// Durable source branch name.
        source: String,
        /// Durable source branch kind.
        source_kind: BranchKind,
        /// Namespace lineage resolved from the source ref record.
        source_lineage: Option<String>,
        /// Durable destination branch name.
        destination: String,
        /// Durable destination branch kind.
        destination_kind: BranchKind,
        /// Namespace lineage resolved from the destination ref record.
        destination_lineage: Option<String>,
    },
    /// A fork attempted to create a Namespace child, which is direct-create-only.
    NamespaceFork {
        /// Durable source branch name.
        source: String,
        /// Durable source branch kind.
        source_kind: BranchKind,
        /// Durable destination branch name.
        destination: String,
        /// Durable destination branch kind.
        destination_kind: BranchKind,
    },
    /// A Namespace branch was supplied as a merge source.
    NamespaceMerge {
        /// Durable source branch name.
        source: String,
        /// Durable source branch kind.
        source_kind: BranchKind,
        /// Durable destination branch name.
        destination: String,
        /// Durable destination branch kind.
        destination_kind: BranchKind,
    },
}

impl fmt::Display for BranchPolicyError {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::ForkAcrossNamespaceBoundary {
                source,
                source_kind,
                destination,
                destination_kind,
                ..
            }
            | Self::NamespaceFork {
                source,
                source_kind,
                destination,
                destination_kind,
            } => write!(
                formatter,
                "policy refused fork-into from branch '{source}' ({source_kind}) into branch \
                 '{destination}' ({destination_kind})",
            ),
            Self::MergeAcrossNamespaceBoundary {
                source,
                source_kind,
                destination,
                destination_kind,
                ..
            }
            | Self::NamespaceMerge {
                source,
                source_kind,
                destination,
                destination_kind,
            } => write!(
                formatter,
                "policy refused merge from branch '{source}' ({source_kind}) into branch \
                 '{destination}' ({destination_kind})",
            ),
        }
    }
}

impl std::error::Error for BranchPolicyError {}

#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
pub(crate) fn check_fork_into(
    source: &str,
    source_kind: BranchKind,
    source_lineage: Option<&str>,
    destination: &str,
    destination_kind: BranchKind,
    destination_lineage: Option<&str>,
) -> Result<(), BranchPolicyError> {
    if destination_kind == BranchKind::Namespace {
        return Err(BranchPolicyError::NamespaceFork {
            source: source.to_owned(),
            source_kind,
            destination: destination.to_owned(),
            destination_kind,
        });
    }
    if crosses_namespace_boundary(source_lineage, destination_lineage) {
        return Err(BranchPolicyError::ForkAcrossNamespaceBoundary {
            source: source.to_owned(),
            source_kind,
            source_lineage: source_lineage.map(str::to_owned),
            destination: destination.to_owned(),
            destination_kind,
            destination_lineage: destination_lineage.map(str::to_owned),
        });
    }
    Ok(())
}

#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
pub(crate) fn check_merge(
    source: &str,
    source_kind: BranchKind,
    source_lineage: Option<&str>,
    destination: &str,
    destination_kind: BranchKind,
    destination_lineage: Option<&str>,
) -> Result<(), BranchPolicyError> {
    if source_kind == BranchKind::Namespace {
        return Err(BranchPolicyError::NamespaceMerge {
            source: source.to_owned(),
            source_kind,
            destination: destination.to_owned(),
            destination_kind,
        });
    }
    if crosses_namespace_boundary(source_lineage, destination_lineage) {
        return Err(BranchPolicyError::MergeAcrossNamespaceBoundary {
            source: source.to_owned(),
            source_kind,
            source_lineage: source_lineage.map(str::to_owned),
            destination: destination.to_owned(),
            destination_kind,
            destination_lineage: destination_lineage.map(str::to_owned),
        });
    }
    Ok(())
}

#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
fn crosses_namespace_boundary(source: Option<&str>, destination: Option<&str>) -> bool {
    source != destination && (source.is_some() || destination.is_some())
}