ic_backup/model/operation_plan/operation/
mod.rs1use 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#[derive(Clone, Debug)]
11pub struct PlannedOperationRequest {
12 pub operation_sequence: u64,
14 pub depends_on: Vec<u64>,
16 pub target: String,
18 pub request: String,
20 pub budget: AttemptBudgetRecord,
22}
23#[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 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 #[must_use]
84 pub const fn operation_sequence(&self) -> u64 {
85 self.operation_sequence
86 }
87 #[must_use]
89 pub fn depends_on(&self) -> &[u64] {
90 &self.depends_on
91 }
92 #[must_use]
94 pub fn target(&self) -> &str {
95 &self.target
96 }
97 #[must_use]
99 pub fn request(&self) -> &str {
100 &self.request
101 }
102 #[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}