canic_backup/plan/
error.rs1use crate::discovery::DiscoveryError;
8
9use thiserror::Error as ThisError;
10
11#[derive(Debug, ThisError)]
19pub enum BackupPlanError {
20 #[error("unsupported backup plan version {0}")]
21 UnsupportedVersion(u16),
22
23 #[error("field {0} must not be empty")]
24 EmptyField(&'static str),
25
26 #[error("field {field} must be a valid principal: {value}")]
27 InvalidPrincipal { field: &'static str, value: String },
28
29 #[error("field {field} must be a 64-character hex topology hash: {value}")]
30 InvalidTopologyHash { field: &'static str, value: String },
31
32 #[error("field {field} must be a unix timestamp marker: {value}")]
33 InvalidTimestamp { field: &'static str, value: String },
34
35 #[error("backup plan has no targets")]
36 EmptyTargets,
37
38 #[error("duplicate backup target {0}")]
39 DuplicateTarget(String),
40
41 #[error("backup plan has {actual} operations, expected {expected}")]
42 OperationCountMismatch { expected: usize, actual: usize },
43
44 #[error("backup plan operation {index} field {field} does not match its canonical projection")]
45 OperationProjectionMismatch { index: usize, field: &'static str },
46
47 #[error("normal backup scope must not include root")]
48 RootIncludedWithoutMaintenance,
49
50 #[error("maintenance root scope must include root")]
51 MaintenanceRootExcludesRoot,
52
53 #[error("selected scope root {0} is not present in plan targets")]
54 SelectedRootNotInTargets(String),
55
56 #[error("non-root-deployment scope must not declare a selected subtree root")]
57 NonRootDeploymentHasSelectedRoot,
58
59 #[error("backup target {canister_id} has a parent cycle")]
60 TargetParentCycle { canister_id: String },
61
62 #[error(
63 "backup target {canister_id} has depth {actual}, expected {expected} below parent {parent_canister_id}"
64 )]
65 TargetDepthMismatch {
66 canister_id: String,
67 parent_canister_id: String,
68 expected: u64,
69 actual: u32,
70 },
71
72 #[error("backup target {canister_id} is disconnected from expected root {expected_root}")]
73 TargetDisconnected {
74 canister_id: String,
75 expected_root: String,
76 },
77
78 #[error("selected backup root {selected_root} has selected parent {parent_canister_id}")]
79 SelectedRootHasInternalParent {
80 selected_root: String,
81 parent_canister_id: String,
82 },
83
84 #[error("target {0} has no proven control authority")]
85 UnprovenControlAuthority(String),
86
87 #[error("target {0} has no proven snapshot read authority")]
88 UnprovenTargetSnapshotReadAuthority(String),
89
90 #[error("target {0} must be controllable by root for this plan")]
91 MissingRootController(String),
92
93 #[error("target {0} has no control authority receipt")]
94 MissingControlAuthorityReceipt(String),
95
96 #[error("target {0} has no snapshot read authority receipt")]
97 MissingSnapshotReadAuthorityReceipt(String),
98
99 #[error("authority receipt targets unknown canister {0}")]
100 UnknownAuthorityReceiptTarget(String),
101
102 #[error("duplicate authority receipt for target {0}")]
103 DuplicateAuthorityReceipt(String),
104
105 #[error("authority receipt plan id {actual} does not match plan {expected}")]
106 AuthorityReceiptPlanMismatch { expected: String, actual: String },
107
108 #[error("authority receipt preflight id {actual} does not match preflight {expected}")]
109 AuthorityReceiptPreflightMismatch { expected: String, actual: String },
110
111 #[error("preflight receipt plan id {actual} does not match plan {expected}")]
112 PreflightReceiptPlanMismatch { expected: String, actual: String },
113
114 #[error("preflight receipt id {actual} does not match preflight {expected}")]
115 PreflightReceiptIdMismatch { expected: String, actual: String },
116
117 #[error(
118 "preflight receipt {preflight_id} is not valid yet at {as_of}; validated at {validated_at}"
119 )]
120 PreflightReceiptNotYetValid {
121 preflight_id: String,
122 validated_at: String,
123 as_of: String,
124 },
125
126 #[error("preflight receipt {preflight_id} expired at {expires_at}; checked at {as_of}")]
127 PreflightReceiptExpired {
128 preflight_id: String,
129 expires_at: String,
130 as_of: String,
131 },
132
133 #[error("preflight receipt {preflight_id} has invalid validity window")]
134 PreflightReceiptInvalidWindow { preflight_id: String },
135
136 #[error("topology preflight hash drifted from {expected} to {actual}")]
137 TopologyPreflightHashMismatch { expected: String, actual: String },
138
139 #[error("topology preflight targets do not match selected plan targets")]
140 TopologyPreflightTargetsMismatch,
141
142 #[error("quiescence preflight policy does not match plan")]
143 QuiescencePolicyMismatch,
144
145 #[error("quiescence preflight was not accepted")]
146 QuiescencePreflightRejected,
147
148 #[error("quiescence preflight targets do not match selected plan targets")]
149 QuiescencePreflightTargetsMismatch,
150
151 #[error("backup selector {0} did not match a live topology node")]
152 UnknownSelector(String),
153
154 #[error("backup selector {selector} matched multiple canisters: {matches:?}")]
155 AmbiguousSelector {
156 selector: String,
157 matches: Vec<String>,
158 },
159
160 #[error(transparent)]
161 Discovery(#[from] DiscoveryError),
162}