Skip to main content

ic_backup/model/operation_plan/operation/
mod.rs

1//! Exact declared target/request and original per-operation finite allowance.
2
3use super::MAX_PLAN_DEPENDENCIES;
4use super::OperationPlanError;
5use crate::model::artifacts::canonical_hash;
6use crate::model::attempt_journal::AttemptBudgetRecord;
7use serde::{Deserialize, Deserializer, Serialize};
8
9/// Passive exact operation binding supplied by its qualified request codec owner.
10#[derive(Clone, Debug)]
11pub struct PlannedOperationRequest {
12    /// Exact operation identity, including zero/nonconsecutive sequences.
13    pub operation_sequence: u64,
14    /// Exact prerequisite operation identities; duplicates reject.
15    pub depends_on: Vec<u64>,
16    /// Exact physical principal, admitted against explicit selection by the plan.
17    pub target: String,
18    /// Canonical exact mutating request digest from its owner; not rendered text.
19    pub request: String,
20    /// Original finite mutation/observation limits, never replenished on derivation.
21    pub budget: AttemptBudgetRecord,
22}
23/// Immutable canonical declared target/request/allowance for one dependency-bearing operation.
24#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
25#[serde(try_from = "OperationFields")]
26pub struct PlannedOperationRecord {
27    operation_sequence: u64,
28    depends_on: Vec<u64>,
29    target: String,
30    request: String,
31    budget: AttemptBudgetRecord,
32}
33#[derive(Deserialize)]
34#[serde(deny_unknown_fields)]
35struct OperationFields {
36    operation_sequence: u64,
37    #[serde(deserialize_with = "bounded_dependencies")]
38    depends_on: Vec<u64>,
39    target: String,
40    request: String,
41    budget: AttemptBudgetRecord,
42}
43impl TryFrom<OperationFields> for PlannedOperationRecord {
44    type Error = OperationPlanError;
45    fn try_from(fields: OperationFields) -> Result<Self, Self::Error> {
46        Self::new(PlannedOperationRequest {
47            operation_sequence: fields.operation_sequence,
48            depends_on: fields.depends_on,
49            target: fields.target,
50            request: fields.request,
51            budget: fields.budget,
52        })
53    }
54}
55impl PlannedOperationRecord {
56    /// Admit canonical declared operation identity; the plan validates selection and dependency closure.
57    ///
58    /// # Errors
59    /// Rejects malformed principal/request digests and excessive or duplicate prerequisites.
60    pub fn new(mut request: PlannedOperationRequest) -> Result<Self, OperationPlanError> {
61        if request.depends_on.len() > MAX_PLAN_DEPENDENCIES {
62            return Err(OperationPlanError::TooManyDependencies);
63        }
64        request.depends_on.sort_unstable();
65        for pair in request.depends_on.windows(2) {
66            if pair[0] == pair[1] {
67                return Err(OperationPlanError::DuplicateDependency {
68                    operation_sequence: request.operation_sequence,
69                    dependency: pair[0],
70                });
71            }
72        }
73        Ok(Self {
74            operation_sequence: request.operation_sequence,
75            depends_on: request.depends_on,
76            target: crate::model::principal::canonical_text(&request.target)
77                .ok_or(OperationPlanError::InvalidPrincipal("target"))?,
78            request: canonical_hash(&request.request)?,
79            budget: request.budget,
80        })
81    }
82    /// Read the exact opaque operation sequence.
83    #[must_use]
84    pub const fn operation_sequence(&self) -> u64 {
85        self.operation_sequence
86    }
87    /// Read canonical sorted prerequisites, admitted for closure and cycles by the plan.
88    #[must_use]
89    pub fn depends_on(&self) -> &[u64] {
90        &self.depends_on
91    }
92    /// Read the exact canonical physical target principal.
93    #[must_use]
94    pub fn target(&self) -> &str {
95        &self.target
96    }
97    /// Read the exact canonical integration-owned request digest.
98    #[must_use]
99    pub fn request(&self) -> &str {
100        &self.request
101    }
102    /// Read the original finite operation attempt limits.
103    #[must_use]
104    pub const fn budget(&self) -> &AttemptBudgetRecord {
105        &self.budget
106    }
107}
108
109fn bounded_dependencies<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Vec<u64>, D::Error> {
110    crate::model::bounded_sequence::deserialize::<_, _, MAX_PLAN_DEPENDENCIES>(
111        deserializer,
112        "bounded exact operation dependencies",
113        OperationPlanError::TooManyDependencies,
114    )
115}