canic-backup 0.36.14

Manifest and orchestration primitives for Canic fleet backup and restore
Documentation
use super::{
    RestoreOrderingDependency, RestoreOrderingRelationship, RestoreOrderingSummary,
    RestorePlanError, RestorePlanMember,
};
use std::collections::BTreeSet;

pub(super) fn order_members(
    members: Vec<RestorePlanMember>,
) -> Result<Vec<RestorePlanMember>, RestorePlanError> {
    let mut remaining = members;
    let group_sources = remaining
        .iter()
        .map(|member| member.source_canister.clone())
        .collect::<BTreeSet<_>>();
    let mut emitted = BTreeSet::new();
    let mut ordered = Vec::with_capacity(remaining.len());

    while !remaining.is_empty() {
        let Some(index) = remaining
            .iter()
            .position(|member| parent_satisfied(member, &group_sources, &emitted))
        else {
            return Err(RestorePlanError::RestoreOrderCycle);
        };

        let mut member = remaining.remove(index);
        member.member_order = ordered.len();
        member.ordering_dependency = ordering_dependency(&member);
        emitted.insert(member.source_canister.clone());
        ordered.push(member);
    }

    Ok(ordered)
}

fn ordering_dependency(member: &RestorePlanMember) -> Option<RestoreOrderingDependency> {
    let parent_source = member.parent_source_canister.as_ref()?;
    let parent_target = member.parent_target_canister.as_ref()?;
    let relationship = RestoreOrderingRelationship::ParentBeforeChild;

    Some(RestoreOrderingDependency {
        source_canister: parent_source.clone(),
        target_canister: parent_target.clone(),
        relationship,
    })
}

pub(super) fn restore_ordering_summary(members: &[RestorePlanMember]) -> RestoreOrderingSummary {
    let mut summary = RestoreOrderingSummary {
        ordered_members: members.len(),
        dependency_free_members: 0,
        parent_edges: 0,
    };

    for member in members {
        if member.ordering_dependency.is_some() {
            summary.parent_edges += 1;
        } else {
            summary.dependency_free_members += 1;
        }
    }

    summary
}

fn parent_satisfied(
    member: &RestorePlanMember,
    group_sources: &BTreeSet<String>,
    emitted: &BTreeSet<String>,
) -> bool {
    match &member.parent_source_canister {
        Some(parent) if group_sources.contains(parent) => emitted.contains(parent),
        _ => true,
    }
}