ic-backup 0.64.1

Host-side snapshot backup and same-release recovery for Internet Computer canisters
Documentation
//! Exact declared target/request and original per-operation finite allowance.

use super::MAX_PLAN_DEPENDENCIES;
use super::OperationPlanError;
use crate::model::artifacts::canonical_hash;
use crate::model::attempt_journal::AttemptBudgetRecord;
use serde::{Deserialize, Deserializer, Serialize};

/// Passive exact operation binding supplied by its qualified request codec owner.
#[derive(Clone, Debug)]
pub struct PlannedOperationRequest {
    /// Exact operation identity, including zero/nonconsecutive sequences.
    pub operation_sequence: u64,
    /// Exact prerequisite operation identities; duplicates reject.
    pub depends_on: Vec<u64>,
    /// Exact physical principal, admitted against explicit selection by the plan.
    pub target: String,
    /// Canonical exact mutating request digest from its owner; not rendered text.
    pub request: String,
    /// Original finite mutation/observation limits, never replenished on derivation.
    pub budget: AttemptBudgetRecord,
}
/// Immutable canonical declared target/request/allowance for one dependency-bearing operation.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(try_from = "OperationFields")]
pub struct PlannedOperationRecord {
    operation_sequence: u64,
    depends_on: Vec<u64>,
    target: String,
    request: String,
    budget: AttemptBudgetRecord,
}
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct OperationFields {
    operation_sequence: u64,
    #[serde(deserialize_with = "bounded_dependencies")]
    depends_on: Vec<u64>,
    target: String,
    request: String,
    budget: AttemptBudgetRecord,
}
impl TryFrom<OperationFields> for PlannedOperationRecord {
    type Error = OperationPlanError;
    fn try_from(fields: OperationFields) -> Result<Self, Self::Error> {
        Self::new(PlannedOperationRequest {
            operation_sequence: fields.operation_sequence,
            depends_on: fields.depends_on,
            target: fields.target,
            request: fields.request,
            budget: fields.budget,
        })
    }
}
impl PlannedOperationRecord {
    /// Admit canonical declared operation identity; the plan validates selection and dependency closure.
    ///
    /// # Errors
    /// Rejects malformed principal/request digests and excessive or duplicate prerequisites.
    pub fn new(mut request: PlannedOperationRequest) -> Result<Self, OperationPlanError> {
        if request.depends_on.len() > MAX_PLAN_DEPENDENCIES {
            return Err(OperationPlanError::TooManyDependencies);
        }
        request.depends_on.sort_unstable();
        for pair in request.depends_on.windows(2) {
            if pair[0] == pair[1] {
                return Err(OperationPlanError::DuplicateDependency {
                    operation_sequence: request.operation_sequence,
                    dependency: pair[0],
                });
            }
        }
        Ok(Self {
            operation_sequence: request.operation_sequence,
            depends_on: request.depends_on,
            target: crate::model::principal::canonical_text(&request.target)
                .ok_or(OperationPlanError::InvalidPrincipal("target"))?,
            request: canonical_hash(&request.request)?,
            budget: request.budget,
        })
    }
    /// Read the exact opaque operation sequence.
    #[must_use]
    pub const fn operation_sequence(&self) -> u64 {
        self.operation_sequence
    }
    /// Read canonical sorted prerequisites, admitted for closure and cycles by the plan.
    #[must_use]
    pub fn depends_on(&self) -> &[u64] {
        &self.depends_on
    }
    /// Read the exact canonical physical target principal.
    #[must_use]
    pub fn target(&self) -> &str {
        &self.target
    }
    /// Read the exact canonical integration-owned request digest.
    #[must_use]
    pub fn request(&self) -> &str {
        &self.request
    }
    /// Read the original finite operation attempt limits.
    #[must_use]
    pub const fn budget(&self) -> &AttemptBudgetRecord {
        &self.budget
    }
}

fn bounded_dependencies<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Vec<u64>, D::Error> {
    crate::model::bounded_sequence::deserialize::<_, _, MAX_PLAN_DEPENDENCIES>(
        deserializer,
        "bounded exact operation dependencies",
        OperationPlanError::TooManyDependencies,
    )
}